Vite React SSG
Static-site generation for React on Vite.
See demo(also document): docs
Table of contents
Usage
npm i -D vite-react-ssg react-router-dom
// package.json
{
"scripts": {
- "dev": "vite",
- "build": "vite build"
+ "dev": "vite-react-ssg dev",
+ "build": "vite-react-ssg build"
// OR if you want to use another vite config file
+ "build": "vite-react-ssg build -c another-vite.config.ts"
}
}
import { ViteReactSSG } from 'vite-react-ssg'
import routes from './App.tsx'
export const createRoot = ViteReactSSG(
{ routes },
({ router, routes, isClient, initialState }) => {
},
)
import React from 'react'
import type { RouteRecord } from 'vite-react-ssg'
import './App.css'
const Layout = React.lazy(() => import('./Layout'))
export const routes: RouteRecord[] = [
{
path: '/',
element: <Layout />,
entry: 'src/Layout.tsx',
children: [
{
path: 'a',
Component: React.lazy(() => import('./pages/a')),
entry: 'src/pages/a.tsx',
},
{
index: true,
Component: React.lazy(() => import('./pages/index')),
entry: 'src/pages/index.tsx',
},
{
path: 'nest/:b',
Component: React.lazy(() => import('./pages/nest/[b]')),
entry: 'src/pages/nest/[b].tsx',
getStaticPaths: () => ['nest/b1', 'nest/b2'],
},
],
},
]
The RouteObject of vite-react-ssg is based on react-router, and vite-react-ssg receives some additional properties.
entry
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. It should be the path from root to the target file.
eg: src/pages/page1.tsx
getStaticPaths
The getStaticPaths()
function should return an array of path
to determine which paths will be pre-rendered by vite-react-ssg.
This function is only valid for dynamic route.
const route = {
path: 'nest/:b',
Component: React.lazy(() => import('./pages/nest/[b]')),
entry: 'src/pages/nest/[b].tsx',
getStaticPaths: () => ['nest/b1', 'nest/b2'],
},
lazy
These options work well with the lazy
field.
export function Component() {
return (
<div>{/* your component */}</div>
)
}
export function getStaticPaths() {
return ['page1', 'page2']
}
export const entry = 'src/pages/[page].tsx'
const routes = [
{
path: '/:page',
lazy: () => import('./pages/[page]')
}
]
See example.
<ClientOnly/>
If you need to render some component in browser only, you can wrap your component with <ClientOnly>
.
import { ClientOnly } from 'vite-react-ssg'
function MyComponent() {
return (
<ClientOnly>
{() => {
return <div>{window.location.href}</div>
}}
</ClientOnly>
)
}
It's important that the children of <ClientOnly>
is not a JSX element, but a function that returns an element.
Because React will try to render children, and may use the client's API on the server.
Document head
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.
import { Head } from 'vite-react-ssg'
const MyHead = () => (
<Head>
<meta property="og:description" content="My custom description" />
<meta charSet="utf-8" />
<title>My Title</title>
<link rel="canonical" href="http://mysite.com/example" />
</Head>
)
Nested or latter components will override duplicate usages:
import { Head } from 'vite-react-ssg'
const MyHead = () => (
<parent>
<Head>
<title>My Title</title>
<meta name="description" content="Helmet application" />
</Head>
<child>
<Head>
<title>Nested Title</title>
<meta name="description" content="Nested component" />
</Head>
</child>
</parent>
)
Outputs:
<head>
<title>Nested Title</title>
<meta name="description" content="Nested component" />
</head>
Reactive head
import { useState } from 'react'
import { Head } from 'vite-react-ssg'
export default function MyHead() {
const [state, setState] = useState(false)
return (
<Head>
<meta charSet="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<title>head test {state ? 'A' : 'B'}</title>
{/* You can also set the 'body' attributes here */}
<body className={`body-class-in-head-${state ? 'a' : 'b'}`} />
</Head>
)
}
CSS in JS
Use the getStyleCollector
option to specify an SSR/SSG style collector. Currently only supports 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 })
You can provide your own by looking at the implementation of any of the existing collectors.
Critical CSS
Vite SSG has built-in support for generating Critical CSS inlined in the HTML via the critters
package.
Install it with:
npm i -D critters
Critical CSS generation will automatically be enabled for you.
To configure critters
, pass its options into ssgOptions.crittersOptions
in vite.config.ts
:
export default defineConfig({
ssgOptions: {
crittersOptions: {
preload: 'media',
},
},
})
Configuration
You can pass options to Vite SSG in the ssgOptions
field of your vite.config.js
export default {
plugins: [],
ssgOptions: {
script: 'async',
},
}
interface ViteReactSSGOptions {
script?: 'sync' | 'async' | 'defer' | 'async defer'
format?: 'esm' | 'cjs'
entry?: string
mock?: boolean
formatting?: 'minify' | 'prettify' | 'none'
mode?: string
dirStyle?: 'flat' | 'nested'
includeAllRoutes?: boolean
crittersOptions?: CrittersOptions | false
includedRoutes?: (paths: string[], routes: Readonly<RouteRecord[]>) => Promise<string[]> | string[]
onBeforePageRender?: (route: string, indexHTML: string, appCtx: ViteReactSSGContext<true>) => Promise<string | null | undefined> | string | null | undefined
onPageRendered?: (route: string, renderedHTML: string, appCtx: ViteReactSSGContext<true>) => Promise<string | null | undefined> | string | null | undefined
onFinished?: () => Promise<void> | void
rootContainerId?: string
concurrency?: number
}
See src/types.ts. for more options available.
Custom Routes to Render
You can use the includedRoutes
hook to include or exclude route paths to render, or even provide some completely custom ones.
export default {
plugins: [],
ssgOptions: {
includedRoutes(paths, routes) {
return paths.filter(i => !i.includes('foo'))
},
},
}
export default {
plugins: [],
ssgOptions: {
includedRoutes(paths, routes) {
return routes.flatMap((route) => {
return route.name === 'Blog'
? myBlogSlugs.map(slug => `/blog/${slug}`)
: route.path
})
},
},
}
Https
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.
export default defineConfig({
server: {
https: true,
},
})
Use CSR in development environment
If you want to use CSR during development, just:
import { ViteReactSSG } from 'vite-react-ssg'
import routes from './App.tsx'
export const createRoot = ViteReactSSG(
{ routes },
({ router, routes, isClient, initialState }) => {
},
{
ssrWhenDev: false
}
)
// package.json
{
"scripts": {
- "dev": "vite-react-ssg dev",
+ "dev": "vite",
"build": "vite-react-ssg build"
}
}
Then, you can start the application with CSR in the development environment.
Roadmap
Credits
This project inspired by vite-ssg, thanks to @antfu for his awesome work.
License
MIT License © 2023 Riri