vite-react-ssg

repository·main·Indexed 42 days ago

https://github.com/daydreamer-riri/vite-react-ssg

Static-site generation for React applications using Vite, specifically targeting react-router-dom. Version 0.9.2 provides features for pre-rendering routes, build-time data fetching via loaders, SEO management with a <Head/> component, and support for dynamic routes using getStaticPaths(). It includes a CLI for building and development, as well as support for Critical CSS via beasties and CSS-in-JS style collectors.

Tokens
18.3K
Snippets
65
Records
77
Agent score
53%

What's inside vite-react-ssg

  1. React 17 and 18 compatibility

    main

    Vite React SSG supports both React 17 and 18:

    • React 18: Use the useLegacyRender: true flag to use the legacy render and hydrate methods.
    • React 17: Requires useLegacyRender: true AND a polyfill for the mjs import and react-dom/client (e.g., via a custom script to define React exports).
  2. Fetch data at build time using `loader`

    main

    You can use React Router's loader function to fetch data at build time. This allows you to perform server-side tasks (like reading files with node:fs) that should not be sent to the client.

    Behavior:

    • Production: The loader runs only at build time. Data is fetched via a manifest generated during the build during subsequent browser navigations.
    • Development: The loader runs on the server. It provides data to the HTML during initial SSR and makes server-side fetch calls during client-side route navigations.

    Usage:

    1. Define an async function loader() in your page file.
    2. Use the useLoaderData hook inside your component to access the returned data.
    import { useLoaderData } from 'react-router-dom'
    
    export default function Docs() {
      const data = useLoaderData() as Awaited<ReturnType<typeof loader>>
    
      return (
        <>
          <div>{data.key}</div>
          <div dangerouslySetInnerHTML={{ __html: data.packageCodeHtml }} style={{ textAlign: 'start' }}></div>
        </>
      )
    }
    
    export const Component = Docs
    
    export const entry = 'src/pages/json.tsx'
    
    export async function loader() {
      // The code here will not be executed on the client side
      const fs = (await import('node:fs'))
      const cwd = process.cwd()
      const json = (await import('../docs/test.json')).default
    
      const packageJson = await fs.promises.readFile(`${cwd}/package.json`, 'utf-8')
      const { codeToHtml } = await import('shiki')
      const packageJsonHtml = await codeToHtml(packageJson, { lang: 'json', theme: 'vitesse-light' })
    
      return {
        ...json,
        packageCodeHtml: packageJsonHtml,
      }
    }
  3. Configure scripts for vite-react-ssg

    main

    Update your package.json scripts to use the vite-react-ssg CLI instead of the standard vite command for building and development.

    By default, vite-react-ssg dev provides SSR (Server-Side Rendering) during development to ensure consistency with the production SSG environment. If you prefer standard CSR (Client-Side Rendering) during development, use vite for the dev script instead.

    {
      "scripts": {
        "build": "vite-react-ssg build",
        "dev": "vite-react-ssg dev"
      }
    }
  4. Configure the Public Base Path

    main

    To host your application under a sub-path, you must synchronize the Vite base configuration with the basename option in ViteReactSSG.

    1. Set the base property in vite.config.ts.
    2. Pass import.meta.env.BASE_URL to the basename property in the ViteReactSSG constructor within your entry file (e.g., main.ts).

    This ensures that react-router correctly handles routing relative to your base URL.

    // vite.config.ts
    import react from '@vitejs/plugin-react-swc'
    import { defineConfig } from 'vite'
    
    export default defineConfig({
      plugins: [react()],
      base: '/base-path',
    })
    // main.ts
    import { ViteReactSSG } from 'vite-react-ssg'
    import { routes } from './App'
    
    export const createRoot = ViteReactSSG(
      {
        routes,
        basename: import.meta.env.BASE_URL,
      },
    )
  5. Integrate styled-components with Vite React SSG

    main

    To prevent style flickering during SSG, use the getStyleCollector option in the ViteReactSSG configuration. This allows you to provide an SSR/SSG style collector that gathers styles during the build process. Currently, the project provides a built-in collector for styled-components via vite-react-ssg/style-collectors/styled-components.

    import { ViteReactSSG } from 'vite-react-ssg'
    import getStyledComponentsCollector from 'vite-react-ssg/style-collectors/styled-components'
    import { routes } from './App.js'
    import './index.css'
    
    export const createRoot = ViteReactSSG(
      { routes },
      () => { },
      { getStyleCollector: getStyledComponentsCollector }
    )
  6. Handle redirects on the client side

    main

    Do not use redirects inside a loader, as loaders only execute during the build process for data fetching. To perform a redirect, use a component with useNavigate from react-router-dom inside a useEffect hook.

    import { useNavigate } from 'react-router-dom'
    import { useEffect } from 'react'
    
    // Inside your routes definition
    {
      path: '/',
      Component: () => {
        const navigate = useNavigate()
        useEffect(() => {
          navigate('/en', { replace: true })
        }, [navigate])
        return null
      },
    }
    export const routes: RouteRecord[] = [
      {
        path: '/:lng',
        Component: Layout,
        getStaticPaths: () => Object.keys(resources),
        children: [
          // ... some routes
        ],
      },
      {
        path: '/',
        Component: () => {
          const navigate = useNavigate()
          useEffect(() => {
            navigate('/en', { replace: true })
          }, [navigate])
    
          return null
        },
      },
    ]
  7. Enable and configure Critical CSS

    main

    Vite React SSG supports inlining Critical CSS in the generated HTML using the critters package.

    1. Install dependencies: You must install critters as a dev dependency.
    2. Automatic activation: Once installed, Critical CSS generation is enabled automatically.
    3. Configuration: You can customize the behavior by passing options to ssgOptions.crittersOptions within your vite.config.ts file. These options follow the standard critters API.
    npm i -D critters
  8. Fetch data at build time using loaders

    main

    You can use react-router-dom's loader to fetch data during the build process. In production, the loader runs only at build time, and data is served via a generated manifest. In development, the loader runs on the server.

    Important: To prevent server-only modules (like node:fs or shiki) from being sent to the client, wrap the logic in an if (!import.meta.ssr) check.

    import { useLoaderData } from 'react-router-dom'
    
    export default function Docs() {
      const data = useLoaderData() as Awaited<ReturnType<typeof loader>>
      return <div>{data.key}</div>
    }
    
    export const loader = async () => {
      if (!import.meta.ssr) return null
      
      const fs = await import('node:fs')
      // ... perform server-side logic
      return { key: 'some-data' }
    }
    import { useLoaderData } from 'react-router-dom'
    
    export default function Docs() {
      const data = useLoaderData() as Awaited<ReturnType<typeof loader>>
    
      return (
        <>
          <div>{data.key}</div>
          <div dangerouslySetInnerHTML={{ __html: data.packageCodeHtml }} style={{ textAlign: 'start' }}></div>
        </>
      )
    }
    
    export const Component = Docs
    
    export const entry = 'src/pages/json.tsx'
    
    export async function loader() {
      if (!import.meta.ssr) {
        return null
      }
      const fs = (await import('node:fs'))
      const cwd = process.cwd()
      const json = (await import('../docs/test.json')).default
    
      const packageJson = await fs.promises.readFile(`${cwd}/package.json`, 'utf-8')
      const { codeToHtml } = await import('shiki')
      const packageJsonHtml = await codeToHtml(packageJson, { lang: 'json', theme: 'vitesse-light' })
    
      return {
        ...json,
        packageCodeHtml: packageJsonHtml,
      }
    }
  9. Install and Setup vite-react-ssg

    main

    To use vite-react-ssg for static site generation with react-router-dom, install the package and its peer dependency, then update your build and dev scripts in package.json.

    Installation:

    npm i -D vite-react-ssg react-router-dom

    Script Configuration: Update your package.json to use the vite-react-ssg CLI for building and developing. Using vite-react-ssg dev provides SSR (Server-Side Rendering) during development to ensure consistency with production.

    {
      "scripts": {
        "build": "vite-react-ssg build",
        "dev": "vite-react-ssg dev"
      }
    }

    If you need to use a specific Vite configuration file, you can pass the -c flag:

    npm run build -- -c another-vite.config.ts
  10. Implement lazy loading with React Router `lazy`

    main

    You can combine vite-react-ssg route options with React Router's lazy field. When using lazy, vite-react-ssg automatically detects files dynamically imported within the lazy function to help gather static resources (like style files) during the build, which helps prevent Flash of Unstyled Content (FOUC).

    If your component fails to load, ensure it or its parent is wrapped in a React <Suspense> component.

    Pattern for lazy routes:

    1. Define the component and getStaticPaths/entry in the page file.
    2. Import the page file using lazy in your routes configuration.
    // src/pages/[page].tsx
    export function Component() {
      return (
        <div>{/* your component */}</div>
      )
    }
    
    export function getStaticPaths() {
      return ['page1', 'page2']
    }
    
    export const entry = 'src/pages/[page].tsx'
    // src/routes.ts
    const routes = [
      {
        path: '/:page',
        lazy: () => import('./pages/[page]')
      }
    ]