perf(web): prerender the site instead of rendering it per request

apps/web had no request-dependent output — one route, all copy imported
from src/content/*.json at build time, and not a server function or loader
in the app — so every request paid for an SSR invocation that produced
byte-identical HTML.

tanstackStart({ prerender: { enabled: true } }) runs that same server
bundle once at build time and writes dist/client/index.html, which is
already the worker's assets directory. Cloudflare now serves the page from
the asset layer: verified under wrangler dev, GET / returns
CF-Cache-Status: HIT, so the worker never runs. It is still built and
deployed, and still answers what the assets miss — GET /nope returns a
worker-rendered 404 exactly as before.

Also ignore .wrangler/, the local state dir `make web:preview` writes.
This commit is contained in:
Nayan
2026-08-11 04:29:09 +05:30
parent d970ee8bd5
commit e55edb3757
3 changed files with 30 additions and 1 deletions
+4
View File
@@ -16,6 +16,10 @@ dist/
.nitro/
.tanstack/
# Wrangler's local state and scratch dirs, written by `make web:preview`
# (wrangler dev). Machine-local simulation of the Cloudflare runtime.
.wrangler/
# Turbo
.turbo/
+12
View File
@@ -300,6 +300,18 @@ until the merge. Build logs live in the dashboard, not in the Actions tab.
This is why `apps/web/wrangler.jsonc` declares no `env` block — one worker, and the branch
decides the command. `make web:deploy` remains as a manual override that authenticates as you.
**What gets deployed is prerendered, not server-rendered.** `vite.config.ts` passes
`prerender: { enabled: true }` to `tanstackStart()`, so the build runs the server bundle once and
writes `dist/client/index.html` — which is the worker's assets directory, so Cloudflare serves
the page as a static file and the worker is never invoked for a normal view. It is still built
and still deployed: it answers whatever the assets do not match, which is what renders the 404.
That has a consequence worth knowing before you reach for one. **A server function or route
loader added to this app will run at build time, not per request** — its result gets baked into
the HTML. That is correct for this site, which has one route and imports all of its copy from
`src/content/*.json`, but the day the page genuinely needs request-dependent output, turning
prerendering off is the change to make, not working around it.
## Releases
`@airshiplabs/cli` is the only package published to npm. Everything else in the workspace is
+14 -1
View File
@@ -11,7 +11,20 @@ import { defineConfig } from "vite";
//
// Port 5173; airship's overlay always takes TARGET + 1, so `make run` serves the
// editor on 5174. See the root Makefile.
//
// Prerendered, because nothing on this page depends on the request. There is one
// route, its copy is imported from content/*.json at build time, and there is not
// a server function or loader in the app — so SSR was rendering the same bytes on
// every hit. `prerender` runs that same server bundle once at build time and
// writes dist/client/index.html, which is already the worker's assets directory:
// Cloudflare serves it as a static file and the worker never wakes up for a normal
// page view. The worker is still built and still deployed — it is what answers
// paths the assets do not match, which is how the 404 keeps rendering.
export default defineConfig({
plugins: [tailwindcss(), tanstackStart(), react()],
plugins: [
tailwindcss(),
tanstackStart({ prerender: { enabled: true } }),
react(),
],
server: { port: 5173 },
});