Static Export

Pre-render your Bini.js app to static HTML with bini-ssg, ready for any static host.

bini-ssg pre-renders every route — static and dynamic — to static HTML as part of npm run build. There is no separate export command or export mode. The output is real server-rendered markup, not a client-only shell, ready for GitHub Pages, S3, Firebase, Surge, and any other static host.

Web target only. Static export applies to the Node.js/web target. Desktop and mobile builds (Windows, macOS, Linux, Android, iOS) don't use bini-ssg — they package the same routes into a native binary instead.

How It Works

bini-ssg is a Vite build plugin that runs during vite build. It:

  • Reads your route list from bini-router's generateRouteManifest()
  • Calls your render() function for every static route
  • Creates shell pages for dynamic routes with a hydration marker
  • Writes one index.html per route into your output directory

Your render() Function

The render() function is exported from src/main.tsx and called by bini-ssg for every static route:

src/main.tsx
import { createRoot } from 'react-dom/client'
import App from './App'

// Client mount
createRoot(document.getElementById('root')!).render(<App />)

// SSG render (called by bini-ssg, Node-only)
export async function render(url: string): Promise<string> {
  const { renderToString } = await import('react-dom/server')
  const { StaticRouter } = await import('react-router-dom/server')
  const { AppRoutes } = await import('./App')

  return renderToString(
    <StaticRouter location={url}>
      <AppRoutes />
    </StaticRouter>
  )
}

This function uses React 19's renderToPipeableStream under the hood with StaticRouter from React Router, producing real server-rendered HTML.

Already scaffolded: The render() function is already in your project. You only need to modify it if you need custom server rendering logic.

Build Command

CommandWhen to use
npm run buildPre-renders every route to static HTML — GitHub Pages, S3, Firebase, Surge, and any static host
npm run startServes the production build with API routes — Node.js hosts (Railway, Render, Fly.io, VPS)

npm run build type-checks (TypeScript projects) and then runs vite build. The bini-ssg plugin drives pre-rendering as part of that same build.

Output Structure

dist/
├── index.html                   ← Pre-rendered '/'
├── about/
│   └── index.html               ← Pre-rendered '/about'
├── blog/
│   └── [slug]/
│       └── index.html           ← Shell page for '/blog/:slug'
├── docs/
│   └── [...slug]/
│       └── index.html           ← Shell page for '/docs/*'
├── js/                          ← Your compiled JavaScript files
│   └── index-[hash].js
└── css/                         ← Your compiled CSS files
    └── index-[hash].css

Shell Pages & Hydration

For dynamic routes (e.g., /blog/:slug), bini-ssg creates a shell page with a marker script:

<script>window.__BINI_SHELL__=true;</script>

Your client entry checks this flag to decide between createRoot and hydrateRoot:

// src/main.tsx
const root = document.getElementById('root')!

if (window.__BINI_SHELL__) {
  createRoot(root).render(<App />)
} else {
  hydrateRoot(root, <App />)
}
No hydration errors: The shell marker prevents React from trying to hydrate an empty #root div against your component tree.

404 Handling

You can enable 404.html generation with the fallback option:

// vite.config.ts
import { defineConfig } from 'vite'
import { biniSSG } from 'bini-ssg'

export default defineConfig({
  plugins: [
    // ...other plugins
    biniSSG({
      fallback: true,  // Render '/404' as 404.html
    }),
  ],
})
SituationWhat gets written to 404.html
src/app/not-found.tsx existsYour custom not-found page is pre-rendered to HTML
No custom not-found fileBuilt-in 404 page is used
Default: fallback is false. Enable it to generate 404.html for static hosts that support it.

Works on Any Fully Static Host

HostStatic routesDynamic routes
GitHub Pages pre-rendered shell pages
AWS S3 + CloudFront pre-rendered shell pages
Firebase Hosting pre-rendered shell pages
Surge.sh pre-rendered shell pages

Complete Example

A full setup for deploying to GitHub Pages with true SSG:

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { biniroute } from 'bini-router'
import { biniEnv } from 'bini-env'
import { biniSSG } from 'bini-ssg'

export default defineConfig({
  base: '/my-app/',  // GitHub Pages subpath
  plugins: [
    react(),
    biniEnv(),
    ...biniroute(),
    biniSSG({
      fallback: true,        // Generate 404.html
    }),
  ],
})

Run npm run build, then push the contents of dist/ to your GitHub Pages branch (or upload them through the GitHub Pages UI).