|
1 | 1 | # Static site generation (SSG) |
2 | 2 |
|
3 | | -## Introduction |
| 3 | +## What is SSG |
4 | 4 |
|
5 | | -In the production build, Rspress will automatically generate a static site for you, that is, generate the HTML content of each page. After the build is completed, the HTML will appear in the default product directory, such as: |
| 5 | +SSG (Static Site Generation) refers to pre-rendering pages into HTML files during the **build phase**, rather than rendering them when users visit. |
| 6 | + |
| 7 | +**Advantages of SSG:** |
| 8 | + |
| 9 | +- **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 |
| 10 | +- **SEO Friendly**: Search engine crawlers can directly access complete HTML content |
| 11 | +- **Easy to Deploy**: Output consists of pure static files with no server required, can be directly hosted on any static hosting service |
| 12 | + |
| 13 | +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. |
| 14 | + |
| 15 | +## Differences between dev and build |
| 16 | + |
| 17 | +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. |
| 18 | + |
| 19 | +| Aspect | Dev Mode (Development) | Build Mode (Production) | |
| 20 | +| -------------- | -------------------------------- | -------------------------------------- | |
| 21 | +| Command | `rspress dev` | `rspress build` | |
| 22 | +| Rendering | Pure CSR (Client-Side Rendering) | SSG (default) or CSR | |
| 23 | +| Pre-rendering | None | Pre-renders all pages when SSG enabled | |
| 24 | +| Focus | Debugging, HMR | Performance, SEO | |
| 25 | +| Preview Method | Access dev server directly | `rspress preview` | |
| 26 | + |
| 27 | +### Dev mode |
| 28 | + |
| 29 | +```bash |
| 30 | +rspress dev |
| 31 | +``` |
| 32 | + |
| 33 | +Dev mode uses **pure Client-Side Rendering (CSR)** without pre-rendering. This prioritizes iteration speed and Hot Module Replacement (HMR) capabilities. |
| 34 | + |
| 35 | +:::tip |
| 36 | +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"](#common-issues-and-solutions) below. |
| 37 | +::: |
| 38 | + |
| 39 | +### Build mode |
| 40 | + |
| 41 | +```bash |
| 42 | +rspress build |
| 43 | +``` |
| 44 | + |
| 45 | +Build mode enables SSG by default. You can control this via the [`ssg` configuration](#configuration): |
| 46 | + |
| 47 | +- `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. |
| 48 | +- `ssg: false`: Disable SSG. Use pure CSR. The generated HTML contains only an empty container, waiting for client-side rendering. |
| 49 | + |
| 50 | +**After building:** |
| 51 | + |
| 52 | +- **Local Preview**: Use `rspress preview` to start a local static server for previewing the output |
| 53 | + |
| 54 | + ```bash |
| 55 | + rspress preview |
| 56 | + ``` |
| 57 | + |
| 58 | +- **Server Deployment**: Deploy the `doc_build` directory to static hosting services (GitHub Pages, Netlify, Vercel, etc.) |
| 59 | + |
| 60 | +## SSG vs CSR output |
| 61 | + |
| 62 | +### Output directory structure |
| 63 | + |
| 64 | +Whether using SSG or CSR mode, the output directory structure is the same: |
6 | 65 |
|
7 | 66 | ```tree |
8 | | -doc_build |
9 | | -├── static |
10 | | -│ ├── main.js |
11 | | -│ └── ... |
| 67 | +doc_build/ |
| 68 | +├── static/ |
| 69 | +│ ├── js/ |
| 70 | +│ │ ├── main.[hash].js |
| 71 | +│ │ └── async/ |
| 72 | +│ └── css/ |
| 73 | +│ └── main.[hash].css |
12 | 74 | ├── index.html |
13 | | -├── about.html |
14 | | -├── posts |
15 | | -│ ├── hello-world.html |
16 | | -│ └── ... |
| 75 | +├── guide/ |
| 76 | +│ └── getting-started.html |
| 77 | +└── api/ |
| 78 | + └── config.html |
17 | 79 | ``` |
18 | 80 |
|
19 | | -You can deploy the contents of this product directory to any static site hosting service, such as GitHub Pages, Netlify, Vercel, etc. |
| 81 | +### HTML content differences |
20 | 82 |
|
21 | | -## Advantages of SSG |
| 83 | +The core difference between the two modes lies in the HTML file content: |
22 | 84 |
|
23 | | -The essence of static site generation is to pre-render components at the build stage, render the components into HTML strings, and then write them into HTML files. This has many benefits, such as: |
| 85 | +**SSG Output HTML** (Pre-rendered complete content): |
24 | 86 |
|
25 | | -- Faster FCP, because there is no need to wait for JS to load before rendering. |
26 | | -- More conducive to SEO, because search engine spiders can directly crawl the complete HTML content. |
| 87 | +```html |
| 88 | +<body> |
| 89 | + <div id="__rspress_root"> |
| 90 | + <!-- Pre-rendered complete page content --> |
| 91 | + <nav>...</nav> |
| 92 | + <main> |
| 93 | + <article> |
| 94 | + <h1>Getting Started</h1> |
| 95 | + <p>Welcome to Rspress...</p> |
| 96 | + </article> |
| 97 | + </main> |
| 98 | + </div> |
| 99 | + <script src="/static/js/main.[hash].js"></script> |
| 100 | +</body> |
| 101 | +``` |
27 | 102 |
|
28 | | -Considering the cost of static site generation, Rspress only pre-renders during production environment builds. In the development environment, it still uses the traditional SPA rendering mode without pre-rendering. |
| 103 | +**CSR Output HTML** (only empty container, waiting for JS to render): |
29 | 104 |
|
30 | | -## Adding custom site content |
| 105 | +```html |
| 106 | +<body> |
| 107 | + <div id="__rspress_root"></div> |
| 108 | + <script src="/static/js/main.[hash].js"></script> |
| 109 | +</body> |
| 110 | +``` |
31 | 111 |
|
32 | | -Through `builderConfig.html.tags`, you can customize the site HTML content, such as adding statistical code, adding scripts and styles, etc. |
| 112 | +### Loading flow differences |
33 | 113 |
|
34 | | -```js title="rspress.config.ts" |
35 | | -import { defineConfig } from '@rspress/core'; |
| 114 | +**SSG Loading Flow:** |
36 | 115 |
|
37 | | -export default defineConfig({ |
38 | | - // ... |
39 | | - builderConfig: { |
40 | | - html: { |
41 | | - tags: [ |
42 | | - { |
43 | | - tag: 'script', |
44 | | - attrs: { |
45 | | - src: 'https://cdn.example.com/your-script.js', |
46 | | - }, |
47 | | - }, |
48 | | - { |
49 | | - tag: 'link', |
50 | | - attrs: { |
51 | | - rel: 'stylesheet', |
52 | | - href: 'https://cdn.example.com/your-style.css', |
53 | | - }, |
54 | | - }, |
55 | | - ], |
56 | | - }, |
57 | | - }, |
58 | | -}); |
59 | | -``` |
| 116 | +1. Browser loads HTML → User **immediately sees complete content** |
| 117 | +2. JavaScript finishes loading → React hydrates, binds event interactions |
| 118 | +3. Subsequent navigation → SPA mode, client-side rendering |
| 119 | + |
| 120 | +**CSR Loading Flow:** |
| 121 | + |
| 122 | +1. Browser loads HTML → User sees **blank page** |
| 123 | +2. JavaScript finishes loading → React renders page content |
| 124 | +3. Subsequent navigation → SPA mode, client-side rendering |
60 | 125 |
|
61 | | -For more detailed config of `builderConfig.html.tags`, please refer to the [documentation](https://rsbuild.rs/config/html/tags). |
| 126 | +## Common issues and solutions |
62 | 127 |
|
63 | | -## Preview |
| 128 | +### `window is not defined` / `document is not defined` |
64 | 129 |
|
65 | | -After the production build is complete, you can preview the output by using the `rspress preview` command. This command will start a local static site service, and you can access this service in your browser to preview the output. |
| 130 | +**Cause**: SSG renders pages in a Node.js environment, where browser-specific global objects like `window` and `document` don't exist. |
66 | 131 |
|
67 | | -```shell |
68 | | -> rspress preview |
| 132 | +**Solutions**: |
69 | 133 |
|
70 | | -Preview server running at http://localhost:4173/ |
| 134 | +1. **Use `useEffect` for delayed execution**: Place browser API calls inside `useEffect` to ensure they only run on the client |
| 135 | + |
| 136 | + ```tsx |
| 137 | + import { useEffect, useState } from 'react'; |
| 138 | + |
| 139 | + function MyComponent() { |
| 140 | + const [width, setWidth] = useState(0); |
| 141 | + |
| 142 | + useEffect(() => { |
| 143 | + // Only runs on client |
| 144 | + setWidth(window.innerWidth); |
| 145 | + }, []); |
| 146 | + |
| 147 | + return <div>Window width: {width}</div>; |
| 148 | + } |
| 149 | + ``` |
| 150 | + |
| 151 | +2. **Conditional check**: Check the environment before accessing browser APIs |
| 152 | + |
| 153 | + ```tsx |
| 154 | + if (typeof window !== 'undefined') { |
| 155 | + // Browser environment |
| 156 | + console.log(window.location.href); |
| 157 | + } |
| 158 | + ``` |
| 159 | + |
| 160 | +3. **Dynamic import**: For third-party libraries that depend on browser APIs, use dynamic imports |
| 161 | + |
| 162 | + ```tsx |
| 163 | + import { useEffect, useState } from 'react'; |
| 164 | + |
| 165 | + function MyComponent() { |
| 166 | + const [Editor, setEditor] = useState(null); |
| 167 | + |
| 168 | + useEffect(() => { |
| 169 | + import('some-browser-only-library').then((mod) => { |
| 170 | + setEditor(() => mod.default); |
| 171 | + }); |
| 172 | + }, []); |
| 173 | + |
| 174 | + if (!Editor) return <div>Loading...</div>; |
| 175 | + return <Editor />; |
| 176 | + } |
| 177 | + ``` |
| 178 | + |
| 179 | +### Hydration mismatch |
| 180 | + |
| 181 | +**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. |
| 182 | + |
| 183 | +**Common scenarios**: |
| 184 | + |
| 185 | +- Using `Date.now()` or random numbers |
| 186 | +- Rendering different content based on `window` object properties (like `window.innerWidth`) |
| 187 | +- Using data that only exists on the client (like localStorage) |
| 188 | + |
| 189 | +**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. |
| 190 | + |
| 191 | +```tsx |
| 192 | +import { useEffect, useState } from 'react'; |
| 193 | + |
| 194 | +function MyComponent() { |
| 195 | + // First render uses default value for server/client consistency |
| 196 | + const [theme, setTheme] = useState('light'); |
| 197 | + |
| 198 | + useEffect(() => { |
| 199 | + // Read localStorage after hydration completes |
| 200 | + const savedTheme = localStorage.getItem('theme'); |
| 201 | + if (savedTheme) { |
| 202 | + setTheme(savedTheme); |
| 203 | + } |
| 204 | + }, []); |
| 205 | + |
| 206 | + return <div className={theme}>...</div>; |
| 207 | +} |
71 | 208 | ``` |
72 | 209 |
|
73 | | -## Disabling SSG |
| 210 | +## Configuration |
74 | 211 |
|
75 | | -If you do not want to use Static Site Generation, you can disable it through the `ssg` config. |
| 212 | +You can control whether SSG is enabled through the `ssg` configuration: |
76 | 213 |
|
77 | | -```js title="rspress.config.ts" |
| 214 | +```ts title="rspress.config.ts" |
78 | 215 | import { defineConfig } from '@rspress/core'; |
79 | 216 |
|
80 | 217 | export default defineConfig({ |
81 | | - // ... |
82 | | - ssg: false, |
| 218 | + ssg: true, // Default value, SSG enabled |
83 | 219 | }); |
84 | 220 | ``` |
85 | 221 |
|
86 | | -:::warning |
| 222 | +If your site has special requirements, you can disable SSG: |
87 | 223 |
|
88 | | -Please be cautious when disabling SSG, as this will forfeit many of the advantages of Static Site Generation mentioned above. |
| 224 | +```ts title="rspress.config.ts" |
| 225 | +import { defineConfig } from '@rspress/core'; |
89 | 226 |
|
| 227 | +export default defineConfig({ |
| 228 | + ssg: false, // Disable SSG, use CSR |
| 229 | +}); |
| 230 | +``` |
| 231 | + |
| 232 | +:::warning |
| 233 | +Please be cautious when disabling SSG, as this will forfeit the advantages of faster first contentful paint and SEO benefits. |
90 | 234 | ::: |
| 235 | + |
| 236 | +## Custom HTML content |
| 237 | + |
| 238 | +Through `builderConfig.html.tags`, you can inject custom content into HTML, such as adding analytics code, scripts, or styles: |
| 239 | + |
| 240 | +```ts title="rspress.config.ts" |
| 241 | +import { defineConfig } from '@rspress/core'; |
| 242 | + |
| 243 | +export default defineConfig({ |
| 244 | + builderConfig: { |
| 245 | + html: { |
| 246 | + tags: [ |
| 247 | + { |
| 248 | + tag: 'script', |
| 249 | + attrs: { |
| 250 | + src: 'https://cdn.example.com/analytics.js', |
| 251 | + }, |
| 252 | + }, |
| 253 | + ], |
| 254 | + }, |
| 255 | + }, |
| 256 | +}); |
| 257 | +``` |
| 258 | + |
| 259 | +For more configuration details, please refer to the [Rsbuild html.tags documentation](https://rsbuild.rs/config/html/tags). |
0 commit comments