# {{projectName}} / {{appName}}

Server-rendered pages (template: **frontend-ssr**) — the **SSR** and **ISR**
render modes. Unlike a static site, these render on a runtime, so you serve
them with `voltro start` (or the cloud), NOT a bare CDN.

## Boot

```bash
pnpm install
pnpm --filter @{{projectName}}/{{appName}} dev      # dev server with HMR

# Production behaviour (the SSR/ISR cache only runs here):
pnpm --filter @{{projectName}}/{{appName}} build
pnpm --filter @{{projectName}}/{{appName}} start    # → voltro start
```

Then watch the response headers (`curl -i`):

```bash
curl -i http://localhost:5190/        | grep x-voltro    # x-voltro-rendered-by: ssr
curl -i http://localhost:5190/feed    | grep x-voltro    # x-voltro-cache: MISS, then HIT
```

## What this template demonstrates

| Page | Mode | Concept |
|---|---|---|
| `/` | `ssr` | Fresh per request; `useServerRequest()` reads cookies + headers server-side |
| `/feed` | `isr` + `revalidate` + `tenantAware` | Cached HTML, revalidated on a window, per-tenant cache key |
| `/feed-swr` | `isr` + `staleWhileRevalidate` | Serve stale instantly, refresh in the background |

Response headers to look for: `x-voltro-rendered-by`, `x-voltro-cache`
(`HIT` / `MISS` / `STALE` / `BYPASS`), `x-voltro-cache-age-ms`.

## Pulling fresh data from your api

This template is self-contained (loaders compute their own data), so it boots
with zero infra. To render from YOUR reactive backend, declare an `apis` entry
in `app.config.ts` and call `ctx.query` in an ssr/isr loader — it runs
server-side, invokes the api's rpc directly, and forwards the session cookie:

```ts
// app.config.ts:  apis: { app: { package: '@{{projectName}}/api' } }
export const loader: LoaderFn<Data> = async ({ query }) => ({
  post: await query!('posts.get', { id: '…' }),   // `query` is server-only
})
```

Scaffold the pair in one command:

```bash
voltro create-project {{projectName}} --api api-backend --web frontend-ssr
```

## Also available: CDC-driven cache invalidation

An ISR page can declare `export const cacheInvalidatesOn = ['todos']` — the
cached HTML drops the instant ANY write to those tables fires (needs an api
with CDC). It pairs with a long `revalidate` for "reactive cache" semantics:
cached arbitrarily long, fresh the moment the source data changes.

## Don't ship these to a pure CDN

`voltro static deploy` will refuse — SSR/ISR pages need a runtime to render per
request. That gate is correct; serve them on `voltro start` / a container, and
push only your `static` pages to a CDN. `voltro deploy plan` tells you which
tier each page lands in.
