A complete React static site

A full site whose pages are React-rendered at build time and hydrated in the browser: fast first paint (real HTML), full interactivity after hydration, and no runtime SSR server โ€” it's static files.

This page builds directly on Build a React App. Same project, same App.jsx, same styles and markup config. Three changes turn the client-only SPA into a pre-rendered, hydrated static site:

1. Swap scripts for reactor

Replace the SPA's scripts entry with a reactor entry. Everything else in the config โ€” styles, markup, serve, watch โ€” stays exactly as it was:

{
  "reactor": [
    {
      "component": "src/js/App.jsx",
      "inject": "app_html",
      "in": "src/js/app-hydrate.jsx",
      "out": "dist/js/app-hydrate.js",
      "options": { "minify": true, "target": "es2019" }
    }
  ]
}

component is rendered to HTML at build time; the result is exposed to templates as app_html. in/out bundle the client entry that hydrates it. Full option reference in React โ€บ Build-time pre-rendering.

2. Hydrate instead of mount

The SPA's main.tsx called createRoot. Here the client entry hydrates the HTML that is already on the page:

// src/js/app-hydrate.jsx
import { hydrateRoot } from 'react-dom/client'
import App from './App.jsx'

hydrateRoot(document.getElementById('root'), <App />)

3. Inject the rendered HTML

The SPA shipped an empty <div id="root">. Now the template drops the build-time HTML into it, then loads the hydration bundle:

{% extends "default.html" %}
{% block content %}
  <div id="root">{{ app_html | safe }}</div>
  <script src="{{ relativePathPrefix }}js/app-hydrate.min.js"></script>
{% endblock %}

How the build flows

  1. Poops bundles App.jsx with react-dom/server, calls renderToString, stores the HTML as app_html.
  2. Markup renders index.html, injecting app_html into #root.
  3. app-hydrate.jsx is bundled to dist/js/app-hydrate.min.js.
  4. In the browser, React hydrates the pre-rendered HTML โ€” the page is interactive.

Tip

This gives you SSG with hydration and no separate server. Deploy the dist/ folder to any static host (GitHub Pages, Netlify, S3).

Warning

The server-rendered markup and the client's first render must match, or React logs a hydration mismatch. Keep App.jsx deterministic at build time โ€” no Date.now(), Math.random() or browser-only APIs during the initial render.

Full runnable examples live in the Poops repository's example/ directory (react.html and react-client.html).