SSR, SSG & ISR
@pyreon/zero renders your app four ways, app-wide or per route:
SSG (
'ssg') — prerender to static HTML at build time. Fastest, cheapest, CDN-friendly.SSR (
'ssr') — render on each request. For per-request/auth-gated/personalized pages.ISR (
'isr') — render on first request, cache with TTL, revalidate in the background.SPA (
'spa') — ship a client shell, render entirely in the browser.
Choosing a mode
Content that's the same for everyone and changes at build time → SSG.
Content that depends on the request (cookies, auth, query) → SSR.
Mostly-static content that updates periodically → ISR.
A dashboard behind a login with no SEO needs → SPA.
App-wide mode
import { zero } from '@pyreon/zero'
export default {
plugins: [pyreon(), zero({ mode: 'ssg' })],
}Per-route / hybrid rendering
Export renderMode from any route or layout to override the app default (it cascades to descendants):
// src/routes/dashboard.tsx — server-render just this route inside an SSG site
export const renderMode = 'ssr'One resolver drives both build and runtime, so they never disagree. Inside an SSG app, an 'ssr'/'isr' route declaration is a build error (a static deploy has no server) — the error names the route and the fix.
SSG: static paths
Dynamic routes need to enumerate their concrete URLs at build time:
// src/routes/posts/[id].tsx
export const getStaticPaths = () => [
{ params: { id: 'hello' } },
{ params: { id: 'world' } },
]Catch-all routes use { params: { slug: 'a/b' } } → /blog/a/b. A _404.tsx co-located with _layout.tsx is emitted as dist/404.html with layout chrome.
ISR caching
// per-route revalidation window (seconds)
export const revalidate = 3600ISR keys the cache by pathname + search by default. For auth-gated pages, supply cacheKey so one user's HTML isn't served to another:
createISRHandler(handler, { cacheKey: (req) => `${new URL(req.url).pathname}::${sessionOf(req)}` })Common pitfalls
mode: 'ssr'/'isr'route in an SSG app. Build error by design — a static deploy has no server.Dynamic route without
getStaticPathsunder SSG. Silently skipped (no HTML emitted).pyreon doctor --check-ssgand themissing-get-static-pathslint rule catch it.revalidateas a non-literal.export const revalidate = TTLis dropped from the build manifest — inline the number:export const revalidate = 3600.ISR on an auth page without
cacheKey. Leaks one user's cached HTML to others. SupplycacheKey.