🚧 Rspress 2.0 document is under development
close

Static site generation (SSG)

What is SSG

SSG (Static Site Generation) refers to pre-rendering pages into HTML files during the build phase, rather than rendering them when users visit.

Advantages of SSG:

  • Faster First Contentful Paint: Users don't need to wait for JavaScript to load and execute; they see complete content as soon as the browser loads the HTML
  • SEO Friendly: Search engine crawlers can directly access complete HTML content
  • Easy to Deploy: Output consists of pure static files with no server required, can be directly hosted on any static hosting service

Rspress enables SSG by default, which means when you run rspress build, each page will be pre-rendered into an HTML file containing complete content. The following details explain the implementation of SSG, helping you gain a deeper understanding of how it works.

Differences between dev and build

Rspress employs different rendering strategies depending on the mode: it uses Client-Side Rendering (CSR) during development for a better experience, while defaulting to SSG during production builds for optimal performance.

AspectDev Mode (Development)Build Mode (Production)
Commandrspress devrspress build
RenderingPure CSR (Client-Side Rendering)SSG (default) or CSR
Pre-renderingNonePre-renders all pages when SSG enabled
FocusDebugging, HMRPerformance, SEO
Preview MethodAccess dev server directlyrspress preview

Dev mode

rspress dev

Dev mode uses pure Client-Side Rendering (CSR) without pre-rendering. This prioritizes iteration speed and Hot Module Replacement (HMR) capabilities.

Tip

If your code works fine in Dev mode but throws errors in Build mode, it's usually because SSG renders in a Node.js environment and cannot access browser APIs (like window or document). See "Common Issues and Solutions" below.

Build mode

rspress build

Build mode enables SSG by default. You can control this via the ssg configuration:

  • ssg: true (Default): Enable SSG. During the build, Rspress executes React component rendering in a Node.js environment, converting each page into an HTML file with complete content.
  • ssg: false: Disable SSG. Use pure CSR. The generated HTML contains only an empty container, waiting for client-side rendering.

After building:

  • Local Preview: Use rspress preview to start a local static server for previewing the output

    rspress preview
  • Server Deployment: Deploy the doc_build directory to static hosting services (GitHub Pages, Netlify, Vercel, etc.)

SSG vs CSR output

Output directory structure

Whether using SSG or CSR mode, the output directory structure is the same:

doc_build
static
js
main.[hash].js
async
css
main.[hash].css
index.html
404.html
guide
getting-started.html
api
config.html

The 404.html is automatically generated by Rspress to handle non-existent routes. This file plays an important role in SPA deployment, see "Page shows 404 after refresh" for details.

HTML content differences

The core difference between the two modes lies in the HTML file content:

SSG Output HTML (Pre-rendered complete content):

<body>
  <div id="__rspress_root">
    <!-- Pre-rendered complete page content -->
    <nav>...</nav>
    <main>
      <article>
        <h1>Getting Started</h1>
        <p>Welcome to Rspress...</p>
      </article>
    </main>
  </div>
  <script src="/static/js/main.[hash].js"></script>
</body>

CSR Output HTML (only empty container, waiting for JS to render):

<body>
  <div id="__rspress_root"></div>
  <script src="/static/js/main.[hash].js"></script>
</body>

Loading flow differences

SSG Loading Flow:

  1. Browser loads HTML β†’ User immediately sees complete content
  2. JavaScript finishes loading β†’ React hydrates, binds event interactions
  3. Subsequent navigation β†’ SPA mode, client-side rendering

CSR Loading Flow:

  1. Browser loads HTML β†’ User sees blank page
  2. JavaScript finishes loading β†’ React renders page content
  3. Subsequent navigation β†’ SPA mode, client-side rendering

Common issues and solutions

window is not defined / document is not defined

Cause: SSG renders pages in a Node.js environment, where browser-specific global objects like window and document don't exist.

Solutions:

  1. Use useEffect for delayed execution: Place browser API calls inside useEffect to ensure they only run on the client

    import { useEffect, useState } from 'react';
    
    function MyComponent() {
      const [width, setWidth] = useState(0);
    
      useEffect(() => {
        // Only runs on client
        setWidth(window.innerWidth);
      }, []);
    
      return <div>Window width: {width}</div>;
    }
  2. Conditional check: Check the environment before accessing browser APIs

    if (typeof window !== 'undefined') {
      // Browser environment
      console.log(window.location.href);
    }
  3. Dynamic import: For third-party libraries that depend on browser APIs, use dynamic imports

    import { useEffect, useState } from 'react';
    
    function MyComponent() {
      const [Editor, setEditor] = useState(null);
    
      useEffect(() => {
        import('some-browser-only-library').then((mod) => {
          setEditor(() => mod.default);
        });
      }, []);
    
      if (!Editor) return <div>Loading...</div>;
      return <Editor />;
    }

Hydration mismatch

Cause: The server-rendered HTML content doesn't match the client's first render. React checks for consistency during hydration, and mismatches will cause warnings or errors.

Common scenarios:

  • Using Date.now() or random numbers
  • Rendering different content based on window object properties (like window.innerWidth)
  • Using data that only exists on the client (like localStorage)

Solution: Ensure the first render output is consistent between server and client. For content that needs to change dynamically on the client, use useEffect to update after hydration completes.

import { useEffect, useState } from 'react';

function MyComponent() {
  // First render uses default value for server/client consistency
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    // Read localStorage after hydration completes
    const savedTheme = localStorage.getItem('theme');
    if (savedTheme) {
      setTheme(savedTheme);
    }
  }, []);

  return <div className={theme}>...</div>;
}

Page shows 404 after refresh

Symptom: Navigating to other pages within the site works normally, but refreshing the page results in a 404 error.

Cause: When you refresh the page or directly visit a URL, the request is sent to the server, but the server may not have a corresponding file for that path (especially on some static hosting services).

Solution: Most static hosting services (such as GitHub Pages, Netlify, Vercel, etc.) use 404.html by default to handle all unmatched routes, requiring no additional configuration. If your server doesn't handle this automatically, you need to manually configure it to redirect unmatched requests to 404.html. The 404.html generated by Rspress contains the complete application code, which can correctly handle routing on the client side and display the corresponding page.

Here is an example of a _redirects file configuring 404.html redirect, which you can use if your hosting service supports it:

docs/public/_redirects
/*    /404.html   200

Configuration

You can control whether SSG is enabled through the ssg configuration:

rspress.config.ts
import { defineConfig } from '@rspress/core';

export default defineConfig({
  ssg: true, // Default value, SSG enabled
});

If your site has special requirements, you can disable SSG:

rspress.config.ts
import { defineConfig } from '@rspress/core';

export default defineConfig({
  ssg: false, // Disable SSG, use CSR
});
Warning

Please be cautious when disabling SSG, as this will forfeit the advantages of faster first contentful paint and SEO benefits.

Custom HTML content

Through builderConfig.html.tags, you can inject custom content into HTML, such as adding analytics code, scripts, or styles:

rspress.config.ts
import { defineConfig } from '@rspress/core';

export default defineConfig({
  builderConfig: {
    html: {
      tags: [
        {
          tag: 'script',
          attrs: {
            src: 'https://cdn.example.com/analytics.js',
          },
        },
      ],
    },
  },
});

For more configuration details, please refer to the Rsbuild html.tags documentation.