vite-react-ssg 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,557 +1,555 @@
1
- # Vite React SSG
2
-
3
- Static-site generation for React on Vite.
4
-
5
- See demo(also document): [docs](https://vite-react-ssg.netlify.app/)
6
-
7
- [![NPM version](https://img.shields.io/npm/v/vite-react-ssg?color=a1b858&label=)](https://www.npmjs.com/package/vite-react-ssg)
8
-
9
- # Table of contents
10
-
11
- - [Usage](#usage)
12
- - [Use CSR during development](#use-csr-during-development)
13
- - [Extra route options](#extra-route-options)
14
- - [`entry`](#entry)
15
- - [`getStaticPaths`](#getstaticpaths)
16
- - [Data fetch](#data-fetch)
17
- - [lazy](#lazy)
18
- - [`<ClientOnly/>`](#clientonly)
19
- - [Document head](#document-head)
20
- - [Reactive head](#reactive-head)
21
- - [Public Base Path](#public-base-path)
22
- - [CSS in JS](#css-in-js)
23
- - [Critical CSS](#critical-css)
24
- - [Configuration](#configuration)
25
- - [Custom Routes to Render](#custom-routes-to-render)
26
- - [Https](#https)
27
- - [Roadmap](#roadmap)
28
- - [Credits](#credits)
29
-
30
- ## Usage
31
-
32
- <pre>
33
- <b>npm i -D vite-react-ssg</b> <em>react-router-dom</em>
34
- </pre>
35
-
36
- ```diff
37
- // package.json
38
- {
39
- "scripts": {
40
- - "build": "vite build"
41
- + "build": "vite-react-ssg build"
42
- // If you need ssr when dev
43
- - "dev": "vite",
44
- + "dev": "vite-react-ssg dev",
45
-
46
- // OR if you want to use another vite config file
47
- + "build": "vite-react-ssg build -c another-vite.config.ts"
48
- }
49
- }
50
- ```
51
-
52
- ```ts
53
- // src/main.ts
54
- import { ViteReactSSG } from 'vite-react-ssg'
55
- import routes from './App.tsx'
56
-
57
- export const createRoot = ViteReactSSG(
58
- // react-router-dom data routes
59
- { routes },
60
- // function to have custom setups
61
- ({ router, routes, isClient, initialState }) => {
62
- // do something.
63
- },
64
- )
65
- ```
66
-
67
- ```tsx
68
- // src/App.tsx
69
- import React from 'react'
70
- import type { RouteRecord } from 'vite-react-ssg'
71
- import './App.css'
72
-
73
- const Layout = React.lazy(() => import('./Layout'))
74
-
75
- export const routes: RouteRecord[] = [
76
- {
77
- path: '/',
78
- element: <Layout />,
79
- entry: 'src/Layout.tsx',
80
- children: [
81
- {
82
- path: 'a',
83
- Component: React.lazy(() => import('./pages/a')),
84
- entry: 'src/pages/a.tsx',
85
- },
86
- {
87
- index: true,
88
- Component: React.lazy(() => import('./pages/index')),
89
- // Used to obtain static resources through manifest
90
- entry: 'src/pages/index.tsx',
91
- },
92
- {
93
- path: 'nest/:b',
94
- Component: React.lazy(() => import('./pages/nest/[b]')),
95
- entry: 'src/pages/nest/[b].tsx',
96
- // To determine which paths will be pre-rendered
97
- getStaticPaths: () => ['nest/b1', 'nest/b2'],
98
- },
99
- ],
100
- },
101
- ]
102
- ```
103
-
104
- ### Use CSR during development
105
-
106
- Vite React SSG provide SSR (Server-Side Rendering) during development to ensure consistency between development and production as much as possible.
107
-
108
- But if you want to use CSR during development, just:
109
-
110
- ```diff
111
- // package.json
112
- {
113
- "scripts": {
114
- - "dev": "vite-react-ssg dev",
115
- + "dev": "vite",
116
- "build": "vite-react-ssg build"
117
- }
118
- }
119
- ```
120
-
121
- ### Single Page SSG
122
-
123
- For SSG of an index page only (i.e. without `react-router-dom`); import `vite-react-ssg/single-page` instead.
124
-
125
- ```tsx
126
- // src/main.tsx
127
- import { ViteReactSSG } from 'vite-react-ssg/single-page'
128
- import App from './App.tsx'
129
-
130
- export const createRoot = ViteReactSSG(<App />)
131
- ```
132
-
133
- ## Extra route options
134
-
135
- The RouteObject of vite-react-ssg is based on react-router, and vite-react-ssg receives some additional properties.
136
-
137
- #### `entry`
138
-
139
- Used to obtain static resources.If you introduce static resources (such as css files) in that route and use lazy loading (such as React.lazy or route.lazy), you should set the entry field.
140
- It should be the path from root to the target file.
141
-
142
- eg: `src/pages/page1.tsx`
143
-
144
- #### `getStaticPaths`
145
-
146
- The `getStaticPaths()` function should return an array of path
147
- to determine which paths will be pre-rendered by vite-react-ssg.
148
-
149
- This function is only valid for dynamic route.
150
-
151
- ```tsx
152
- const route = {
153
- path: 'nest/:b',
154
- Component: React.lazy(() => import('./pages/nest/[b]')),
155
- entry: 'src/pages/nest/[b].tsx',
156
- // To determine which paths will be pre-rendered
157
- getStaticPaths: () => ['nest/b1', 'nest/b2'],
158
- },
159
- ```
160
-
161
- ## lazy
162
-
163
- These options work well with the `lazy` field.
164
-
165
- ```tsx
166
- // src/pages/[page].tsx
167
- export function Component() {
168
- return (
169
- <div>{/* your component */}</div>
170
- )
171
- }
172
-
173
- export function getStaticPaths() {
174
- return ['page1', 'page2']
175
- }
176
-
177
- export const entry = 'src/pages/[page].tsx'
178
- ```
179
-
180
- ```ts
181
- // src/routes.ts
182
- const routes = [
183
- {
184
- path: '/:page',
185
- lazy: () => import('./pages/[page]')
186
- }
187
- ]
188
- ```
189
-
190
- See [example](./examples/lazy-pages/src/App.tsx).
191
-
192
- ## Data fetch
193
-
194
- You can use react-router-dom's `loader` to fetch data at build time and use `useLoaderData` to get the data in the component.
195
-
196
- See [example | with-loader](./examples/with-loader/src/pages/[docs].tsx).
197
-
198
- ## `<ClientOnly/>`
199
-
200
- If you need to render some component in browser only, you can wrap your component with `<ClientOnly>`.
201
-
202
- ```tsx
203
- import { ClientOnly } from 'vite-react-ssg'
204
-
205
- function MyComponent() {
206
- return (
207
- <ClientOnly>
208
- {() => {
209
- return <div>{window.location.href}</div>
210
- }}
211
- </ClientOnly>
212
- )
213
- }
214
- ```
215
-
216
- > It's important that the children of `<ClientOnly>` is not a JSX element, but a function that returns an element.
217
- > Because React will try to render children, and may use the client's API on the server.
218
-
219
- ## Document head
220
-
221
- You can use `<Head/>` to manage all of your changes to the document head. It takes plain HTML tags and outputs plain HTML tags. It is a wrapper around [React Helmet](https://github.com/nfl/react-helmet).
222
-
223
- ```tsx
224
- import { Head } from 'vite-react-ssg'
225
-
226
- function MyHead() {
227
- return (
228
- <Head>
229
- <meta property="og:description" content="My custom description" />
230
- <meta charSet="utf-8" />
231
- <title>My Title</title>
232
- <link rel="canonical" href="http://mysite.com/example" />
233
- </Head>
234
- )
235
- }
236
- ```
237
-
238
- Nested or latter components will override duplicate usages:
239
-
240
- ```tsx
241
- import { Head } from 'vite-react-ssg'
242
-
243
- function MyHead() {
244
- return (
245
- <parent>
246
- <Head>
247
- <title>My Title</title>
248
- <meta name="description" content="Helmet application" />
249
- </Head>
250
- <child>
251
- <Head>
252
- <title>Nested Title</title>
253
- <meta name="description" content="Nested component" />
254
- </Head>
255
- </child>
256
- </parent>
257
- )
258
- }
259
- ```
260
-
261
- Outputs:
262
-
263
- ```html
264
- <head>
265
- <title>Nested Title</title>
266
- <meta name="description" content="Nested component" />
267
- </head>
268
- ```
269
-
270
- ### Reactive head
271
-
272
- ```tsx
273
- import { useState } from 'react'
274
- import { Head } from 'vite-react-ssg'
275
-
276
- export default function MyHead() {
277
- const [state, setState] = useState(false)
278
-
279
- return (
280
- <Head>
281
- <meta charSet="UTF-8" />
282
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
283
- <title>head test {state ? 'A' : 'B'}</title>
284
- {/* You can also set the 'body' attributes here */}
285
- <body className={`body-class-in-head-${state ? 'a' : 'b'}`} />
286
- </Head>
287
- )
288
- }
289
- ```
290
-
291
- ## Public Base Path
292
-
293
- Just set `base` in vite.config.ts like:
294
-
295
- ```ts
296
- // vite.config.ts
297
- import { defineConfig } from 'vite'
298
- import react from '@vitejs/plugin-react-swc'
299
-
300
- // https://vitejs.dev/config/
301
- export default defineConfig({
302
- plugins: [react()],
303
- base: '/base-path',
304
- })
305
- ```
306
-
307
- ```ts
308
- // main.ts
309
- import { ViteReactSSG } from 'vite-react-ssg'
310
- import { routes } from './App'
311
- import './index.css'
312
-
313
- export const createRoot = ViteReactSSG(
314
- {
315
- routes,
316
- // pass your BASE_URL
317
- basename: import.meta.env.BASE_URL,
318
- },
319
- )
320
- ```
321
-
322
- Vite React SSG will give it to the react-router's `basename`.
323
-
324
- See: [react-router's create-browser-router](https://reactrouter.com/en/main/routers/create-browser-router#basename)
325
-
326
- [Example](./examples/lazy-pages/vite.config.ts)
327
-
328
- ## CSS in JS
329
-
330
- Use the `getStyleCollector` option to specify an SSR/SSG style collector. Currently only supports `styled-components`.
331
-
332
- ```tsx
333
- import { ViteReactSSG } from 'vite-react-ssg'
334
- import getStyledComponentsCollector from 'vite-react-ssg/style-collectors/styled-components'
335
- import { routes } from './App.js'
336
- import './index.css'
337
-
338
- export const createRoot = ViteReactSSG(
339
- { routes },
340
- () => { },
341
- { getStyleCollector: getStyledComponentsCollector }
342
- )
343
- ```
344
-
345
- You can provide your own by looking at the [implementation](./src/style-collectors/) of any of the existing collectors.
346
-
347
- ## Critical CSS
348
-
349
- Vite React SSG has built-in support for generating [Critical CSS](https://web.dev/extract-critical-css/) inlined in the HTML via the [`critters`](https://github.com/GoogleChromeLabs/critters) package.
350
- Install it with:
351
-
352
- ```bash
353
- npm i -D critters
354
- ```
355
-
356
- Critical CSS generation will automatically be enabled for you.
357
-
358
- To configure `critters`, pass [its options](https://github.com/GoogleChromeLabs/critters#usage) into `ssgOptions.crittersOptions` in `vite.config.ts`:
359
-
360
- ```ts
361
- // vite.config.ts
362
- export default defineConfig({
363
- ssgOptions: {
364
- crittersOptions: {
365
- // E.g., change the preload strategy
366
- preload: 'media',
367
- // Other options: https://github.com/GoogleChromeLabs/critters#usage
368
- },
369
- },
370
- })
371
- ```
372
-
373
- ## Configuration
374
-
375
- You can pass options to Vite SSG in the `ssgOptions` field of your `vite.config.js`
376
-
377
- ```js
378
- // vite.config.js
379
-
380
- export default {
381
- plugins: [],
382
- ssgOptions: {
383
- script: 'async',
384
- },
385
- }
386
- ```
387
-
388
- ```ts
389
- interface ViteReactSSGOptions {
390
- /**
391
- * Set the scripts' loading mode. Only works for `type="module"`.
392
- *
393
- * @default 'sync'
394
- */
395
- script?: 'sync' | 'async' | 'defer' | 'async defer'
396
- /**
397
- * Build format.
398
- *
399
- * @default 'esm'
400
- */
401
- format?: 'esm' | 'cjs'
402
- /**
403
- * The path of the main entry file (relative to the project root).
404
- *
405
- * @default 'src/main.ts'
406
- */
407
- entry?: string
408
- /**
409
- * Mock browser global variables (window, document, etc...) from SSG.
410
- *
411
- * @default false
412
- */
413
- mock?: boolean
414
- /**
415
- * Apply formatter to the generated index file.
416
- *
417
- * **It will cause Hydration Failed.**
418
- *
419
- * @default 'none'
420
- */
421
- formatting?: 'minify' | 'prettify' | 'none'
422
- /**
423
- * Vite environmeng mode.
424
- */
425
- mode?: string
426
- /**
427
- * Directory style of the output directory.
428
- *
429
- * flat: `/foo` -> `/foo.html`
430
- * nested: `/foo` -> `/foo/index.html`
431
- *
432
- * @default 'flat'
433
- */
434
- dirStyle?: 'flat' | 'nested'
435
- /**
436
- * Generate for all routes, including dynamic routes.
437
- * If enabled, you will need to configGure your serve
438
- * manually to handle dynamic routes properly.
439
- *
440
- * @default false
441
- */
442
- includeAllRoutes?: boolean
443
- /**
444
- * Options for the critters packages.
445
- *
446
- * @see https://github.com/GoogleChromeLabs/critters
447
- */
448
- crittersOptions?: CrittersOptions | false
449
- /**
450
- * Custom function to modify the routes to do the SSG.
451
- *
452
- * Works only when `includeAllRoutes` is set to false.
453
- *
454
- * Defaults to a handler that filters out all the dynamic routes.
455
- * When passing your custom handler, you should also take care of the dynamic routes yourself.
456
- */
457
- includedRoutes?: (paths: string[], routes: Readonly<RouteRecord[]>) => Promise<string[]> | string[]
458
- /**
459
- * Callback to be called before every page render.
460
- *
461
- * It can be used to transform the project's `index.html` file before passing it to the renderer.
462
- *
463
- * To do so, you can change the 'index.html' file contents (passed in through the `indexHTML` parameter), and return it.
464
- * The returned value will then be passed to renderer.
465
- */
466
- onBeforePageRender?: (route: string, indexHTML: string, appCtx: ViteReactSSGContext<true>) => Promise<string | null | undefined> | string | null | undefined
467
- /**
468
- * Callback to be called on every rendered page.
469
- *
470
- * It can be used to transform the current route's rendered HTML.
471
- *
472
- * To do so, you can transform the route's rendered HTML (passed in through the `renderedHTML` parameter), and return it.
473
- * The returned value will be used as the HTML of the route.
474
- */
475
- onPageRendered?: (route: string, renderedHTML: string, appCtx: ViteReactSSGContext<true>) => Promise<string | null | undefined> | string | null | undefined
476
-
477
- onFinished?: () => Promise<void> | void
478
- /**
479
- * The application's root container `id`.
480
- *
481
- * @default `root`
482
- */
483
- rootContainerId?: string
484
- /**
485
- * The size of the SSG processing queue.
486
- *
487
- * @default 20
488
- */
489
- concurrency?: number
490
- }
491
- ```
492
-
493
- See [src/types.ts](./src/types.ts). for more options available.
494
-
495
- ### Custom Routes to Render
496
-
497
- You can use the `includedRoutes` hook to include or exclude route paths to render, or even provide some completely custom ones.
498
-
499
- ```js
500
- // vite.config.js
501
-
502
- export default {
503
- plugins: [],
504
- ssgOptions: {
505
- includedRoutes(paths, routes) {
506
- // exclude all the route paths that contains 'foo'
507
- return paths.filter(i => !i.includes('foo'))
508
- },
509
- },
510
- }
511
- ```
512
-
513
- ```js
514
- // vite.config.js
515
-
516
- export default {
517
- plugins: [],
518
- ssgOptions: {
519
- includedRoutes(paths, routes) {
520
- // use original route records
521
- return routes.flatMap(route => {
522
- return route.name === 'Blog'
523
- ? myBlogSlugs.map(slug => `/blog/${slug}`)
524
- : route.path
525
- })
526
- },
527
- },
528
- }
529
- ```
530
-
531
- ### Https
532
-
533
- If you set `https` to true in Vite, we will by default use `devcert` to generate a local HTTPS service for you. Of course, if you pass in your own custom https parameters, we will also help you pass them through to the Express server.
534
-
535
- ```ts
536
- export default defineConfig({
537
- server: {
538
- https: true,
539
- },
540
- })
541
- ```
542
-
543
- ## Roadmap
544
-
545
- - [x] Preload assets
546
- - [x] Document head
547
- - [x] SSR in dev environment
548
- - [x] More Client components, such as `<ClientOnly />`
549
- - [x] `getStaticPaths` for dynamic routes
550
-
551
- ## Credits
552
-
553
- This project inspired by [vite-ssg](https://github.com/antfu/vite-ssg), thanks to [@antfu](https://github.com/antfu) for his awesome work.
554
-
555
- ## License
556
-
557
- [MIT](./LICENSE) License © 2023 [Riri](https://github.com/Daydreamer-riri)
1
+ # Vite React SSG
2
+
3
+ Static-site generation for React on Vite.
4
+
5
+ See demo(also document): [docs](https://vite-react-ssg.netlify.app/)
6
+
7
+ **🎈 Support for [`@tanstack/router`](https://tanstack.com/router/latest/docs/framework/react/overview) and [`wouter`](https://github.com/molefrog/wouter) is in progress!**
8
+
9
+ [![NPM version](https://img.shields.io/npm/v/vite-react-ssg?color=a1b858&label=)](https://www.npmjs.com/package/vite-react-ssg)
10
+
11
+ # Table of contents
12
+
13
+ - [Usage](#usage)
14
+ - [Use CSR during development](#use-csr-during-development)
15
+ - [Extra route options](#extra-route-options)
16
+ - [`entry`](#entry)
17
+ - [`getStaticPaths`](#getstaticpaths)
18
+ - [Data fetch](#data-fetch)
19
+ - [lazy](#lazy)
20
+ - [`<ClientOnly/>`](#clientonly)
21
+ - [Document head](#document-head)
22
+ - [Reactive head](#reactive-head)
23
+ - [Public Base Path](#public-base-path)
24
+ - [CSS in JS](#css-in-js)
25
+ - [Critical CSS](#critical-css)
26
+ - [Configuration](#configuration)
27
+ - [Custom Routes to Render](#custom-routes-to-render)
28
+ - [Https](#https)
29
+ - [Roadmap](#roadmap)
30
+ - [Credits](#credits)
31
+
32
+ ## Usage
33
+
34
+ <pre>
35
+ <b>npm i -D vite-react-ssg</b> <em>react-router-dom</em>
36
+ </pre>
37
+
38
+ ```diff
39
+ // package.json
40
+ {
41
+ "scripts": {
42
+ - "build": "vite build"
43
+ + "build": "vite-react-ssg build"
44
+ // If you need ssr when dev
45
+ - "dev": "vite",
46
+ + "dev": "vite-react-ssg dev",
47
+
48
+ // OR if you want to use another vite config file
49
+ + "build": "vite-react-ssg build -c another-vite.config.ts"
50
+ }
51
+ }
52
+ ```
53
+
54
+ ```ts
55
+ // src/main.ts
56
+ import { ViteReactSSG } from 'vite-react-ssg'
57
+ import routes from './App.tsx'
58
+
59
+ export const createRoot = ViteReactSSG(
60
+ // react-router-dom data routes
61
+ { routes },
62
+ // function to have custom setups
63
+ ({ router, routes, isClient, initialState }) => {
64
+ // do something.
65
+ },
66
+ )
67
+ ```
68
+
69
+ ```tsx
70
+ // src/App.tsx
71
+ import React from 'react'
72
+ import type { RouteRecord } from 'vite-react-ssg'
73
+ import './App.css'
74
+
75
+ const Layout = React.lazy(() => import('./Layout'))
76
+
77
+ export const routes: RouteRecord[] = [
78
+ {
79
+ path: '/',
80
+ element: <Layout />,
81
+ entry: 'src/Layout.tsx',
82
+ children: [
83
+ {
84
+ path: 'a',
85
+ Component: React.lazy(() => import('./pages/a')),
86
+ entry: 'src/pages/a.tsx',
87
+ },
88
+ {
89
+ index: true,
90
+ Component: React.lazy(() => import('./pages/index')),
91
+ // Used to obtain static resources through manifest
92
+ entry: 'src/pages/index.tsx',
93
+ },
94
+ {
95
+ path: 'nest/:b',
96
+ Component: React.lazy(() => import('./pages/nest/[b]')),
97
+ entry: 'src/pages/nest/[b].tsx',
98
+ // To determine which paths will be pre-rendered
99
+ getStaticPaths: () => ['nest/b1', 'nest/b2'],
100
+ },
101
+ ],
102
+ },
103
+ ]
104
+ ```
105
+
106
+ ### Use CSR during development
107
+
108
+ Vite React SSG provide SSR (Server-Side Rendering) during development to ensure consistency between development and production as much as possible.
109
+
110
+ But if you want to use CSR during development, just:
111
+
112
+ ```diff
113
+ // package.json
114
+ {
115
+ "scripts": {
116
+ - "dev": "vite-react-ssg dev",
117
+ + "dev": "vite",
118
+ "build": "vite-react-ssg build"
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### Single Page SSG
124
+
125
+ For SSG of an index page only (i.e. without `react-router-dom`); import `vite-react-ssg/single-page` instead.
126
+
127
+ ```tsx
128
+ // src/main.tsx
129
+ import { ViteReactSSG } from 'vite-react-ssg/single-page'
130
+ import App from './App.tsx'
131
+
132
+ export const createRoot = ViteReactSSG(<App />)
133
+ ```
134
+
135
+ ## Extra route options
136
+
137
+ The RouteObject of vite-react-ssg is based on react-router, and vite-react-ssg receives some additional properties.
138
+
139
+ #### `entry`
140
+
141
+ Used to obtain static resources.If you introduce static resources (such as css files) in that route and use lazy loading (such as React.lazy or route.lazy), you should set the entry field.
142
+ It should be the path from root to the target file.
143
+
144
+ eg: `src/pages/page1.tsx`
145
+
146
+ #### `getStaticPaths`
147
+
148
+ The `getStaticPaths()` function should return an array of path
149
+ to determine which paths will be pre-rendered by vite-react-ssg.
150
+
151
+ This function is only valid for dynamic route.
152
+
153
+ ```tsx
154
+ const route = {
155
+ path: 'nest/:b',
156
+ Component: React.lazy(() => import('./pages/nest/[b]')),
157
+ entry: 'src/pages/nest/[b].tsx',
158
+ // To determine which paths will be pre-rendered
159
+ getStaticPaths: () => ['nest/b1', 'nest/b2'],
160
+ },
161
+ ```
162
+
163
+ ## lazy
164
+
165
+ These options work well with the `lazy` field.
166
+
167
+ ```tsx
168
+ // src/pages/[page].tsx
169
+ export function Component() {
170
+ return (
171
+ <div>{/* your component */}</div>
172
+ )
173
+ }
174
+
175
+ export function getStaticPaths() {
176
+ return ['page1', 'page2']
177
+ }
178
+
179
+ export const entry = 'src/pages/[page].tsx'
180
+ ```
181
+
182
+ ```ts
183
+ // src/routes.ts
184
+ const routes = [
185
+ {
186
+ path: '/:page',
187
+ lazy: () => import('./pages/[page]')
188
+ }
189
+ ]
190
+ ```
191
+
192
+ See [example](./examples/lazy-pages/src/App.tsx).
193
+
194
+ ## Data fetch
195
+
196
+ You can use react-router-dom's `loader` to fetch data at build time and use `useLoaderData` to get the data in the component.
197
+
198
+ See [example | with-loader](./examples/with-loader/src/pages/[docs].tsx).
199
+
200
+ ## `<ClientOnly/>`
201
+
202
+ If you need to render some component in browser only, you can wrap your component with `<ClientOnly>`.
203
+
204
+ ```tsx
205
+ import { ClientOnly } from 'vite-react-ssg'
206
+
207
+ function MyComponent() {
208
+ return (
209
+ <ClientOnly>
210
+ {() => {
211
+ return <div>{window.location.href}</div>
212
+ }}
213
+ </ClientOnly>
214
+ )
215
+ }
216
+ ```
217
+
218
+ > It's important that the children of `<ClientOnly>` is not a JSX element, but a function that returns an element.
219
+ > Because React will try to render children, and may use the client's API on the server.
220
+
221
+ ## Document head
222
+
223
+ You can use `<Head/>` to manage all of your changes to the document head. It takes plain HTML tags and outputs plain HTML tags. It is a wrapper around [React Helmet](https://github.com/nfl/react-helmet).
224
+
225
+ ```tsx
226
+ import { Head } from 'vite-react-ssg'
227
+
228
+ function MyHead() {
229
+ return (
230
+ <Head>
231
+ <meta property="og:description" content="My custom description" />
232
+ <meta charSet="utf-8" />
233
+ <title>My Title</title>
234
+ <link rel="canonical" href="http://mysite.com/example" />
235
+ </Head>
236
+ )
237
+ }
238
+ ```
239
+
240
+ Nested or latter components will override duplicate usages:
241
+
242
+ ```tsx
243
+ import { Head } from 'vite-react-ssg'
244
+
245
+ function MyHead() {
246
+ return (
247
+ <parent>
248
+ <Head>
249
+ <title>My Title</title>
250
+ <meta name="description" content="Helmet application" />
251
+ </Head>
252
+ <child>
253
+ <Head>
254
+ <title>Nested Title</title>
255
+ <meta name="description" content="Nested component" />
256
+ </Head>
257
+ </child>
258
+ </parent>
259
+ )
260
+ }
261
+ ```
262
+
263
+ Outputs:
264
+
265
+ ```html
266
+ <head>
267
+ <title>Nested Title</title>
268
+ <meta name="description" content="Nested component" />
269
+ </head>
270
+ ```
271
+
272
+ ### Reactive head
273
+
274
+ ```tsx
275
+ import { useState } from 'react'
276
+ import { Head } from 'vite-react-ssg'
277
+
278
+ export default function MyHead() {
279
+ const [state, setState] = useState(false)
280
+
281
+ return (
282
+ <Head>
283
+ <meta charSet="UTF-8" />
284
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
285
+ <title>head test {state ? 'A' : 'B'}</title>
286
+ {/* You can also set the 'body' attributes here */}
287
+ <body className={`body-class-in-head-${state ? 'a' : 'b'}`} />
288
+ </Head>
289
+ )
290
+ }
291
+ ```
292
+
293
+ ## Public Base Path
294
+
295
+ Just set `base` in vite.config.ts like:
296
+
297
+ ```ts
298
+ // vite.config.ts
299
+ import { defineConfig } from 'vite'
300
+ import react from '@vitejs/plugin-react-swc'
301
+
302
+ // https://vitejs.dev/config/
303
+ export default defineConfig({
304
+ plugins: [react()],
305
+ base: '/base-path',
306
+ })
307
+ ```
308
+
309
+ ```ts
310
+ // main.ts
311
+ import { ViteReactSSG } from 'vite-react-ssg'
312
+ import { routes } from './App'
313
+ import './index.css'
314
+
315
+ export const createRoot = ViteReactSSG(
316
+ {
317
+ routes,
318
+ // pass your BASE_URL
319
+ basename: import.meta.env.BASE_URL,
320
+ },
321
+ )
322
+ ```
323
+
324
+ Vite React SSG will give it to the react-router's `basename`.
325
+
326
+ See: [react-router's create-browser-router](https://reactrouter.com/en/main/routers/create-browser-router#basename)
327
+
328
+ [Example](./examples/lazy-pages/vite.config.ts)
329
+
330
+ ## CSS in JS
331
+
332
+ Use the `getStyleCollector` option to specify an SSR/SSG style collector. Currently only supports `styled-components`.
333
+
334
+ ```tsx
335
+ import { ViteReactSSG } from 'vite-react-ssg'
336
+ import getStyledComponentsCollector from 'vite-react-ssg/style-collectors/styled-components'
337
+ import { routes } from './App.js'
338
+ import './index.css'
339
+
340
+ export const createRoot = ViteReactSSG(
341
+ { routes },
342
+ () => { },
343
+ { getStyleCollector: getStyledComponentsCollector }
344
+ )
345
+ ```
346
+
347
+ You can provide your own by looking at the [implementation](./src/style-collectors/) of any of the existing collectors.
348
+
349
+ ## Critical CSS
350
+
351
+ Vite React SSG has built-in support for generating [Critical CSS](https://web.dev/extract-critical-css/) inlined in the HTML via the [`critters`](https://github.com/GoogleChromeLabs/critters) package.
352
+ Install it with:
353
+
354
+ ```bash
355
+ npm i -D critters
356
+ ```
357
+
358
+ Critical CSS generation will automatically be enabled for you.
359
+
360
+ To configure `critters`, pass [its options](https://github.com/GoogleChromeLabs/critters#usage) into `ssgOptions.crittersOptions` in `vite.config.ts`:
361
+
362
+ ```ts
363
+ // vite.config.ts
364
+ export default defineConfig({
365
+ ssgOptions: {
366
+ crittersOptions: {
367
+ // E.g., change the preload strategy
368
+ preload: 'media',
369
+ // Other options: https://github.com/GoogleChromeLabs/critters#usage
370
+ },
371
+ },
372
+ })
373
+ ```
374
+
375
+ ## Configuration
376
+
377
+ You can pass options to Vite SSG in the `ssgOptions` field of your `vite.config.js`
378
+
379
+ ```js
380
+ // vite.config.js
381
+
382
+ export default {
383
+ plugins: [],
384
+ ssgOptions: {
385
+ script: 'async',
386
+ },
387
+ }
388
+ ```
389
+
390
+ ```ts
391
+ interface ViteReactSSGOptions {
392
+ /**
393
+ * Set the scripts' loading mode. Only works for `type="module"`.
394
+ *
395
+ * @default 'sync'
396
+ */
397
+ script?: 'sync' | 'async' | 'defer' | 'async defer'
398
+ /**
399
+ * Build format.
400
+ *
401
+ * @default 'esm'
402
+ */
403
+ format?: 'esm' | 'cjs'
404
+ /**
405
+ * The path of the main entry file (relative to the project root).
406
+ *
407
+ * @default 'src/main.ts'
408
+ */
409
+ entry?: string
410
+ /**
411
+ * Mock browser global variables (window, document, etc...) from SSG.
412
+ *
413
+ * @default false
414
+ */
415
+ mock?: boolean
416
+ /**
417
+ * Apply formatter to the generated index file.
418
+ *
419
+ * **It will cause Hydration Failed.**
420
+ *
421
+ * @default 'none'
422
+ */
423
+ formatting?: 'minify' | 'prettify' | 'none'
424
+ /**
425
+ * Vite environmeng mode.
426
+ */
427
+ mode?: string
428
+ /**
429
+ * Directory style of the output directory.
430
+ *
431
+ * flat: `/foo` -> `/foo.html`
432
+ * nested: `/foo` -> `/foo/index.html`
433
+ *
434
+ * @default 'flat'
435
+ */
436
+ dirStyle?: 'flat' | 'nested'
437
+ /**
438
+ * Generate for all routes, including dynamic routes.
439
+ * If enabled, you will need to configGure your serve
440
+ * manually to handle dynamic routes properly.
441
+ *
442
+ * @default false
443
+ */
444
+ includeAllRoutes?: boolean
445
+ /**
446
+ * Options for the critters packages.
447
+ *
448
+ * @see https://github.com/GoogleChromeLabs/critters
449
+ */
450
+ crittersOptions?: CrittersOptions | false
451
+ /**
452
+ * Custom function to modify the routes to do the SSG.
453
+ *
454
+ * Works only when `includeAllRoutes` is set to false.
455
+ *
456
+ * Defaults to a handler that filters out all the dynamic routes.
457
+ * When passing your custom handler, you should also take care of the dynamic routes yourself.
458
+ */
459
+ includedRoutes?: (paths: string[], routes: Readonly<RouteRecord[]>) => Promise<string[]> | string[]
460
+ /**
461
+ * Callback to be called before every page render.
462
+ *
463
+ * It can be used to transform the project's `index.html` file before passing it to the renderer.
464
+ *
465
+ * To do so, you can change the 'index.html' file contents (passed in through the `indexHTML` parameter), and return it.
466
+ * The returned value will then be passed to renderer.
467
+ */
468
+ onBeforePageRender?: (route: string, indexHTML: string, appCtx: ViteReactSSGContext<true>) => Promise<string | null | undefined> | string | null | undefined
469
+ /**
470
+ * Callback to be called on every rendered page.
471
+ *
472
+ * It can be used to transform the current route's rendered HTML.
473
+ *
474
+ * To do so, you can transform the route's rendered HTML (passed in through the `renderedHTML` parameter), and return it.
475
+ * The returned value will be used as the HTML of the route.
476
+ */
477
+ onPageRendered?: (route: string, renderedHTML: string, appCtx: ViteReactSSGContext<true>) => Promise<string | null | undefined> | string | null | undefined
478
+
479
+ onFinished?: () => Promise<void> | void
480
+ /**
481
+ * The application's root container `id`.
482
+ *
483
+ * @default `root`
484
+ */
485
+ rootContainerId?: string
486
+ /**
487
+ * The size of the SSG processing queue.
488
+ *
489
+ * @default 20
490
+ */
491
+ concurrency?: number
492
+ }
493
+ ```
494
+
495
+ See [src/types.ts](./src/types.ts). for more options available.
496
+
497
+ ### Custom Routes to Render
498
+
499
+ You can use the `includedRoutes` hook to include or exclude route paths to render, or even provide some completely custom ones.
500
+
501
+ ```js
502
+ // vite.config.js
503
+
504
+ export default {
505
+ plugins: [],
506
+ ssgOptions: {
507
+ includedRoutes(paths, routes) {
508
+ // exclude all the route paths that contains 'foo'
509
+ return paths.filter(i => !i.includes('foo'))
510
+ },
511
+ },
512
+ }
513
+ ```
514
+
515
+ ```js
516
+ // vite.config.js
517
+
518
+ export default {
519
+ plugins: [],
520
+ ssgOptions: {
521
+ includedRoutes(paths, routes) {
522
+ // use original route records
523
+ return routes.flatMap(route => {
524
+ return route.name === 'Blog'
525
+ ? myBlogSlugs.map(slug => `/blog/${slug}`)
526
+ : route.path
527
+ })
528
+ },
529
+ },
530
+ }
531
+ ```
532
+
533
+ ```ts
534
+ export default defineConfig({
535
+ server: {
536
+ https: true,
537
+ },
538
+ })
539
+ ```
540
+
541
+ ## Roadmap
542
+
543
+ - [x] Preload assets
544
+ - [x] Document head
545
+ - [x] SSR in dev environment
546
+ - [x] More Client components, such as `<ClientOnly />`
547
+ - [x] `getStaticPaths` for dynamic routes
548
+
549
+ ## Credits
550
+
551
+ This project inspired by [vite-ssg](https://github.com/antfu/vite-ssg), thanks to [@antfu](https://github.com/antfu) for his awesome work.
552
+
553
+ ## License
554
+
555
+ [MIT](./LICENSE) License © 2023 [Riri](https://github.com/Daydreamer-riri)