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:

```bash
npm install --prefix web
npm run dev --prefix web
```

The script starts Vite on port `4173` and Go on port `8080`. It sets:

```text
WAHOO_SSR_URL=http://127.0.0.1:4173/__wahoo_ssr
```

For a page request, the Go renderer:

1. Parses `WAHOO_SSR_URL`.
2. Adds the original request path and query as the `url` query value.
3. Sends the browser cookie header to the worker.
4. Requests the private `/__wahoo_ssr` route.
5. 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:

1. Authenticate and authorize in Go.
2. Load the small, safe view model in Go.
3. Send only that view model to the worker.
4. Render the view model in React.
5. 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:

```bash
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:

```bash
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:

1. Replace `renderToString` with a React streaming API.
2. Make the worker send a chunked response.
3. Stream the worker response through Go.
4. Set a render timeout and error boundary.
5. Test client hydration and cancellation.

Keep the first implementation small. SSR correctness matters more than streaming speed.