React SSR
Wahoo renders React through a private Node worker. Go stays the public server.
Why Wahoo Uses a Worker
React runs in JavaScript. Go does not run a Vite React bundle by itself. The worker gives React a normal Node environment and lets Go keep control of HTTP, cookies, access checks, and deployment.
Development Flow
Start the generated application:
npm install --prefix web
npm run dev --prefix web
The script starts Vite on port 4173 and Go on port 8080. It sets:
WAHOO_SSR_URL=http://127.0.0.1:4173/__wahoo_ssr
For a page request, the Go renderer:
- Parses
WAHOO_SSR_URL. - Adds the original request path and query as the
urlquery value. - Sends the browser cookie header to the worker.
- Requests the private
/__wahoo_ssrroute. - Returns the worker HTML when the response succeeds.
The Vite worker loads src/entry-server.tsx. React renders the App component. The browser then hydrates with src/entry-client.tsx.
SSR Files
| File | Function |
|---|---|
web/src/entry-client.tsx |
Starts browser hydration. |
web/src/entry-server.tsx |
Renders the React document. |
web/dev-server.mjs |
Runs Vite SSR in development. |
web/ssr-server.mjs |
Runs the built SSR entry in production. |
internal/ssr/renderer.go |
Calls the private worker from Go. |
Add Page Data
The starter renders a static page. Add page data in a controlled sequence:
- Authenticate and authorize in Go.
- Load the small, safe view model in Go.
- Send only that view model to the worker.
- Render the view model in React.
- Serialize the same safe view model for browser hydration.
Do not send session tokens, password hashes, raw provider payloads, or unrestricted database records to the worker or browser.
Use a request deadline for each worker call. Use a small response size limit. Add these controls before production use.
Build for Production
Build the frontend from the application root:
npm run build --prefix web
The build creates:
| Path | Content |
|---|---|
web/dist/assets |
Hashed browser CSS and JavaScript assets. |
web/dist/.vite/manifest.json |
Client asset manifest. |
web/dist/server/entry-server.js |
Node SSR entry point. |
Go serves GET /assets/ from web/dist/assets. The production worker reads the manifest and inserts the correct asset paths into each document.
Start the worker and Go process from the application root:
npm run start:ssr --prefix web
WAHOO_SSR_URL=http://127.0.0.1:4173/__wahoo_ssr go run .
Do not expose port 4173 to the public network.
Fallback Shell
If WAHOO_SSR_URL is unset or the worker request fails, the generated renderer returns a development client shell. This supports Go-only local work.
Do not use this fallback as a production recovery method. It references development modules. Treat a missing SSR worker as a deployment error for public pages.
Add Streaming Later
The starter uses renderToString. Use streaming only after you measure a page that needs it.
To add streaming:
- Replace
renderToStringwith a React streaming API. - Make the worker send a chunked response.
- Stream the worker response through Go.
- Set a render timeout and error boundary.
- Test client hydration and cancellation.
Keep the first implementation small. SSR correctness matters more than streaming speed.