Deploy Build Time
Keep `npm run deploy` short as the lab grows. The prerender crawler renders every page it can reach, and the build's time goes wherever the crawl goes.
Why
On 2026-11-03 a deploy ran past 20 minutes, and two concurrent deploys collided in .nuxt-deploy. The phase-4 lab links to itself with query knobs (?findingId=…&policyId=…&team=…). Nitro's crawler rendered every permutation, about 19,000 of them, and then threw each away, because a query URL can't be written as a file. The lab keeps growing, so the build will drift back up unless something trims it.
Objective
- A deploy build in a few minutes, outputting the same pages.
- A ranked list of what to trim next, measured rather than guessed, ready for when the build grows again.
Scope
In: nuxt.config.ts prerender settings (deploy builds only), the server data the cycle pages read, slow or broken pages the crawler reaches.
Out: the deploy script's shape (local build → deploy branch), Cloudflare, dev-server behaviour.
Key Changes
- 2026-11-03 — skip query links in the deploy crawl (commit
a7323795dfonclaude/jovial-dubinsky-36e37e, unpushed). UnderDESIGN_DEPLOY_OUT,nitro.prerender.ignoreskips any link with a?and queues its bare path instead (captured from theprerender:routeshook), so a page linked only with a query still prerenders. Dev is untouched.- Measured on
c123/owasp-agentic-tourplus the main checkout's 23 uncommitted lab paths (nuxt generate, deploy.sh step 1):Before After Total build 1,051 s 262 s Prerender phase 967 s 191 s index.html3,126 3,125 - Gained
pages/phase4/mock/inventory/agents/detailandinventory/tools/detail: they were linked only with a query, so the live site was missing them. Dropped/cycles/design/c0and/cycles/design/c97-harness-chip(+record): 404 pages reached only from?cycle=links on the c124 mock. Posture report and time-window pages are byte-identical. - The 1,660-page figure in the original log came from a build that was cut short; a full build writes about 3,125 pages.
- Measured on
- 2026-11-03 —
DataTimelinedemo fixed (same commit). The demo passedtimeframe: 'week', but Timeline matchesTimevalues ('7days'), so its computed was undefined and SSR threwreading 'labels'. The demo now passesObject.values(Time). - 2026-11-03 — where the rest of the time goes (from the baseline log, summed render time across 12 cores, so per-page numbers are inflated by queueing):
Bucket Routes Summed render time Query permutations (now skipped) 19,034 ~21,000 s Path-only pages 3,516 ~10,100 s └ /cycles/design/*1,099 ~7,800 s (doc pages avg 11 s, cycle pages avg 3.7 s) └ /components/_demos/*386 ~80 s /pages/data-swarm-ideas,-2-,-3-3 ~284 s each
Top query offenders before the fix:findings(2,160),compliance/detail(1,787),policy-config/config-file(1,515). - 2026-11-03 — first real deploy with the fix (
bd48264c3eondeploy, fromc123/owasp-agentic-tour0a2cb25691+ 23 uncommitted).nuxt generatetook about 4 min (client 29 s, server 34 s, prerender 176 s, 6,256 routes, 3,126index.html), butdeploy.shran 931 s end to end. The other ~11 min came after the build: committing 17,863 files (820 MB) as a fresh tree and force-pushing it. The build is no longer the slow part of a deploy; the commit and push are.
Outcome
(open — parked after the first fix)
Options (ranked; none started)
- Time and trim the commit + push (now the biggest cost: ~11 of 15.5 min). Add timestamps to
deploy.shsteps 3–4 first to see whethergit addof 17k files or the upload dominates. Then: content-stable asset names so unchanged blobs dedupe, drop what Cloudflare doesn't need (e.g._payload.jsonif unused, source maps), or upload withwrangler pages deploy(hash-based, only changed files) instead of a git push. - Cache cycle data during prerender. Every cycle doc page calls
listProjectCycles(server/data/cycles/buildCycles.ts), which rereads and parses all ~130CYCLE.mdfiles plusstatusIndexandimageIndex, on every request. The week pages also rungit logper request (window.ts,timeline.ts). Nothing changes mid-build, so memoise per process, gated onimport.meta.prerender. Biggest next lever, invisible on the site. - The three data-swarm pages (~284 s each). They read
/api/ai-usageand/api/commits/detail(c62). Likely heavy git scans or a timeout; cache, fix, orprerender: false. - Client-render the lab on deploy.
routeRules: { '/pages/**': { ssr: false } }underDESIGN_DEPLOY_OUTmakes each lab mock a cheap shell. Big cut; the mocks stop shipping as static HTML. Mark's call. - Prune. Retire old phase-0 mocks or old cycles' doc pages; stop the crawler following direct
/components/_demos/*links (small). - Incremental deploys (reuse unchanged output): most moving parts, last resort.
Remaining prerender errors (content, not crawl cost)
Folder links in cycle records resolve to routes that don't exist: on a week page to /cycles/weeks/<folder> (400 "week must be a Monday"), on a cycle page to /cycles/design/<folder> (404), and through siteLinks to doc?path=<folder>/.md (404). No site route serves a cycle folder.
(mcp-guide-u4/)— c125 CYCLE.md (onc123/owasp-agentic-tour)(tours-walk/)— c123(todos-2026-09-23/),(todos-2026-09-24/)— c92;(todos-2026-09-30/)— c103(sources/)— c121claude-code-opus-5.5/phase2/studies/,phase3/bom/— c99- c107:
crates/qdash/ui/docs/data-interface.md,EXECUTIVE-OVERVIEW.md(not in the cycle dir) /api/cycles/design/c105— 404 in that build/cycles/weeks/1999-12-27— something links a week far in the past
Fix per record (point at a file inside the folder), or teach siteLinks to send folder links somewhere real.
Artifacts
- initial-prompt.md
- PICKUP.md — parked-state re-entry note
- PICKUP.md
- initial-prompt.md