Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

Static-site generation for React on Vite.
https://github.com/Daydreamer-riri/vite-react-ssg

build react react-ssg ssg vite vite-ssg

Last synced: about 2 months ago
JSON representation

Static-site generation for React on Vite.

Awesome Lists containing this project

README

        

# Vite React SSG

Static-site generation for React on Vite.

See demo(also document): [docs](https://vite-react-ssg.netlify.app/)

**🎈 Support for [`@tanstack/router`](https://tanstack.com/router/latest/docs/framework/react/overview) and [`wouter`](https://github.com/molefrog/wouter) is in progress!**

[![NPM version](https://img.shields.io/npm/v/vite-react-ssg?color=a1b858&label=)](https://www.npmjs.com/package/vite-react-ssg)

# Table of contents

- [Usage](#usage)
- [Use CSR during development](#use-csr-during-development)
- [Extra route options](#extra-route-options)
- [`entry`](#entry)
- [`getStaticPaths`](#getstaticpaths)
- [Data fetch](#data-fetch)
- [lazy](#lazy)
- [``](#clientonly)
- [Document head](#document-head)
- [Reactive head](#reactive-head)
- [Public Base Path](#public-base-path)
- [CSS in JS](#css-in-js)
- [Critical CSS](#critical-css)
- [Configuration](#configuration)
- [Custom Routes to Render](#custom-routes-to-render)
- [Https](#https)
- [Roadmap](#roadmap)
- [Credits](#credits)

## Usage


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

```diff
// package.json
{
"scripts": {
- "build": "vite build"
+ "build": "vite-react-ssg build"
// If you need ssr when dev
- "dev": "vite",
+ "dev": "vite-react-ssg dev",

// OR if you want to use another vite config file
+ "build": "vite-react-ssg build -c another-vite.config.ts"
}
}
```

```ts
// src/main.ts
import { ViteReactSSG } from 'vite-react-ssg'
import routes from './App.tsx'

export const createRoot = ViteReactSSG(
// react-router-dom data routes
{ routes },
// function to have custom setups
({ router, routes, isClient, initialState }) => {
// do something.
},
)
```

```tsx
// src/App.tsx
import React from 'react'
import type { RouteRecord } from 'vite-react-ssg'
import './App.css'
import Layout from './Layout'

export const routes: RouteRecord[] = [
{
path: '/',
element: ,
entry: 'src/Layout.tsx',
children: [
{
path: 'a',
lazy: () => import('./pages/a'),
},
{
index: true,
Component: React.lazy(() => import('./pages/index')),
},
{
path: 'nest/:b',
lazy: () => {
const Component = await import('./pages/nest/[b]')
return { Component }
},
// To determine which paths will be pre-rendered
getStaticPaths: () => ['nest/b1', 'nest/b2'],
},
],
},
]
```

### Use CSR during development

Vite React SSG provide SSR (Server-Side Rendering) during development to ensure consistency between development and production as much as possible.

But if you want to use CSR during development, just:

```diff
// package.json
{
"scripts": {
- "dev": "vite-react-ssg dev",
+ "dev": "vite",
"build": "vite-react-ssg build"
}
}
```

### Single Page SSG

For SSG of an index page only (i.e. without `react-router-dom`); import `vite-react-ssg/single-page` instead.

```tsx
// src/main.tsx
import { ViteReactSSG } from 'vite-react-ssg/single-page'
import App from './App.tsx'

export const createRoot = ViteReactSSG()
```

## Extra route options

The RouteObject of vite-react-ssg is based on react-router, and vite-react-ssg receives some additional properties.

#### `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.

```tsx
const route = {
path: 'nest/:b',
Component: React.lazy(() => import('./pages/nest/[b]')),
entry: 'src/pages/nest/[b].tsx',
// To determine which paths will be pre-rendered
getStaticPaths: () => ['nest/b1', 'nest/b2'],
}
```

#### `entry`

**You are not required to use this field. It is only necessary when "prehydration style loss" occurs.**
It should be the path from root to the target file.

eg: `src/pages/page1.tsx`

## lazy

These options work well with the `lazy` field.

```tsx
// src/pages/[page].tsx
export function Component() {
return (

{/* your component */}

)
}

export function getStaticPaths() {
return ['page1', 'page2']
}
```

```ts
// src/routes.ts
const routes = [
{
path: '/:page',
lazy: () => import('./pages/[page]')
}
]
```

See [example](./examples/lazy-pages/src/App.tsx).

## Data fetch

You can use react-router-dom's `loader` to fetch data at build time and use `useLoaderData` to get the data in the component.

In production, the `loader` will only be executed at build time, and the data will be fetched by the manifest generated at build time during the browser navigations .

In the development environment, the `loader` also runs only on the server.It provides data to the HTML during initial server rendering, and during browser route navigations , it makes calls to the server by initiating a fetch on the service.

```tsx
import { useLoaderData } from 'react-router-dom'

export default function Docs() {
const data = useLoaderData() as Awaited>

return (
<>

{data.key}

{/* eslint-disable-next-line react-dom/no-dangerously-set-innerhtml */}

>
)
}

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, and the modules imported will not be sent to the client.
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,
}
}
```

See [example | with-loader](./examples/with-loader/src/pages/[docs].tsx).

## ``

If you need to render some component in browser only, you can wrap your component with ``.

```tsx
import { ClientOnly } from 'vite-react-ssg'

function MyComponent() {
return (

{() => {
return

{window.location.href}

}}

)
}
```

> It's important that the children of `` 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 `` 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).

```tsx
import { Head } from 'vite-react-ssg'

function MyHead() {
return (



My Title


)
}
```

Nested or latter components will override duplicate usages:

```tsx
import { Head } from 'vite-react-ssg'

function MyHead() {
return (


My Title




Nested Title




)
}
```

Outputs:

```html

Nested Title

```

### Reactive head

```tsx
import { useState } from 'react'
import { Head } from 'vite-react-ssg'

export default function MyHead() {
const [state, setState] = useState(false)

return (



head test {state ? 'A' : 'B'}
{/* You can also set the 'body' attributes here */}


)
}
```

## Public Base Path

Just set `base` in vite.config.ts like:

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
base: '/base-path',
})
```

```ts
// main.ts
import { ViteReactSSG } from 'vite-react-ssg'
import { routes } from './App'
import './index.css'

export const createRoot = ViteReactSSG(
{
routes,
// pass your BASE_URL
basename: import.meta.env.BASE_URL,
},
)
```

Vite React SSG will give it to the react-router's `basename`.

See: [react-router's create-browser-router](https://reactrouter.com/en/main/routers/create-browser-router#basename)

[Example](./examples/lazy-pages/vite.config.ts)

## CSS in JS

Use the `getStyleCollector` option to specify an SSR/SSG style collector. Currently only supports `styled-components`.

```tsx
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](./src/style-collectors/) of any of the existing collectors.

## Critical CSS

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.
Install it with:

```bash
npm i -D critters
```

Critical CSS generation will automatically be enabled for you.

To configure `critters`, pass [its options](https://github.com/GoogleChromeLabs/critters#usage) into `ssgOptions.crittersOptions` in `vite.config.ts`:

```ts
// vite.config.ts
export default defineConfig({
ssgOptions: {
crittersOptions: {
// E.g., change the preload strategy
preload: 'media',
// Other options: https://github.com/GoogleChromeLabs/critters#usage
},
},
})
```

## Configuration

You can pass options to Vite SSG in the `ssgOptions` field of your `vite.config.js`

```js
// vite.config.js

export default {
plugins: [],
ssgOptions: {
script: 'async',
},
}
```

```ts
interface ViteReactSSGOptions {
/**
* Set the scripts' loading mode. Only works for `type="module"`.
*
* @default 'sync'
*/
script?: 'sync' | 'async' | 'defer' | 'async defer'
/**
* Build format.
*
* @default 'esm'
*/
format?: 'esm' | 'cjs'
/**
* The path of the main entry file (relative to the project root).
*
* @default 'src/main.ts'
*/
entry?: string
/**
* Mock browser global variables (window, document, etc...) from SSG.
*
* @default false
*/
mock?: boolean
/**
* Apply formatter to the generated index file.
*
* **It will cause Hydration Failed.**
*
* @default 'none'
*/
formatting?: 'minify' | 'prettify' | 'none'
/**
* Vite environmeng mode.
*/
mode?: string
/**
* Directory style of the output directory.
*
* flat: `/foo` -> `/foo.html`
* nested: `/foo` -> `/foo/index.html`
*
* @default 'flat'
*/
dirStyle?: 'flat' | 'nested'
/**
* Generate for all routes, including dynamic routes.
* If enabled, you will need to configGure your serve
* manually to handle dynamic routes properly.
*
* @default false
*/
includeAllRoutes?: boolean
/**
* Options for the critters packages.
*
* @see https://github.com/GoogleChromeLabs/critters
*/
crittersOptions?: CrittersOptions | false
/**
* Custom function to modify the routes to do the SSG.
*
* Works only when `includeAllRoutes` is set to false.
*
* Defaults to a handler that filters out all the dynamic routes.
* When passing your custom handler, you should also take care of the dynamic routes yourself.
*/
includedRoutes?: (paths: string[], routes: Readonly) => Promise | string[]
/**
* Callback to be called before every page render.
*
* It can be used to transform the project's `index.html` file before passing it to the renderer.
*
* To do so, you can change the 'index.html' file contents (passed in through the `indexHTML` parameter), and return it.
* The returned value will then be passed to renderer.
*/
onBeforePageRender?: (route: string, indexHTML: string, appCtx: ViteReactSSGContext) => Promise | string | null | undefined
/**
* Callback to be called on every rendered page.
*
* It can be used to transform the current route's rendered HTML.
*
* To do so, you can transform the route's rendered HTML (passed in through the `renderedHTML` parameter), and return it.
* The returned value will be used as the HTML of the route.
*/
onPageRendered?: (route: string, renderedHTML: string, appCtx: ViteReactSSGContext) => Promise | string | null | undefined

onFinished?: () => Promise | void
/**
* The application's root container `id`.
*
* @default `root`
*/
rootContainerId?: string
/**
* The size of the SSG processing queue.
*
* @default 20
*/
concurrency?: number
}
```

See [src/types.ts](./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.

```js
// vite.config.js

export default {
plugins: [],
ssgOptions: {
includedRoutes(paths, routes) {
// exclude all the route paths that contains 'foo'
return paths.filter(i => !i.includes('foo'))
},
},
}
```

```js
// vite.config.js

export default {
plugins: [],
ssgOptions: {
includedRoutes(paths, routes) {
// use original route records
return routes.flatMap(route => {
return route.name === 'Blog'
? myBlogSlugs.map(slug => `/blog/${slug}`)
: route.path
})
},
},
}
```

```ts
export default defineConfig({
server: {
https: true,
},
})
```

## Roadmap

- [x] Preload assets
- [x] Document head
- [x] SSR in dev environment
- [x] More Client components, such as ``
- [x] `getStaticPaths` for dynamic routes

## Credits

This project inspired by [vite-ssg](https://github.com/antfu/vite-ssg), thanks to [@antfu](https://github.com/antfu) for his awesome work.

## License

[MIT](./LICENSE) License © 2023 [Riri](https://github.com/Daydreamer-riri)