Skip to content

feat(prerender): seed KV cache with pre-rendered routes at deploy time#734

Closed
raed04 wants to merge 1 commit intocloudflare:mainfrom
raed04:feat/seed-kv-cache-562
Closed

feat(prerender): seed KV cache with pre-rendered routes at deploy time#734
raed04 wants to merge 1 commit intocloudflare:mainfrom
raed04:feat/seed-kv-cache-562

Conversation

@raed04
Copy link
Copy Markdown
Contributor

@raed04 raed04 commented Apr 1, 2026

Summary

  • Adds automatic KV cache population during vinext deploy so pre-rendered pages are cache HITs from the first request
  • New deploy-time step reads vinext-prerender.json and uploads HTML/RSC entries to Workers KV via the Cloudflare bulk REST API
  • Correct cache key format matching runtime KVCacheHandler.get(): cache:app:<buildId>:<pathname>:html

How it works

  1. After pre-rendering completes (Step 6a) and before wrangler deploys the worker (Step 7), a new Step 6c runs automatically
  2. Reads vinext-prerender.json manifest and pre-rendered HTML/RSC files from dist/server/prerendered-routes/
  3. Constructs ISR cache keys via isrCacheKey() (same function the runtime uses)
  4. Base64-encodes RSC data to match KVCacheHandler serialization format
  5. Uploads in batches of 10,000 pairs via Cloudflare KV bulk REST API

Automatic when: CLOUDFLARE_API_TOKEN env var is set and VINEXT_CACHE KV namespace exists in wrangler config.
Opt-out: vinext deploy --no-seed-cache
Non-fatal: Seeding failures log a warning but never block deployment.

Key design decisions

  • Correct key formatcache:app:<buildId>:<pathname>:html matches what KVCacheHandler.get() reads at runtime (unlike TPR which uses cache:<pathname> — a known pre-existing bug)
  • Static routes omit expiration_ttl — matches runtime KVCacheHandler.set() behavior (no expiry for static pages)
  • ISR routes get 10x revalidate TTL clamped to [60s, 30d]
  • Deploy-time only — this module runs in Node.js during deploy, never bundled into the worker (addresses the bundle contamination concern from Populating remote cache during deployment #562)
  • App Router only — Pages Router seeding is left for future work

Security

  • Path traversal guard: resolved file paths must stay within prerenderDir
  • TOCTOU-safe: uses try/catch on readFileSync (no existsSync + readFileSync race)
  • Error messages capped to 500 chars to prevent sensitive data leakage
  • Checks Cloudflare API success: false responses (HTTP 200 with semantic errors)

Files

File Change
packages/vinext/src/cloudflare/seed-kv-cache.ts New — core seeding module (300 lines)
packages/vinext/src/deploy.ts Step 6c integration + --no-seed-cache flag
packages/vinext/src/cli.ts Wire noSeedCache flag to deploy options
tests/seed-kv-cache.test.ts New — 27 unit tests

Test plan

  • 27 unit tests covering:
    • Correct KV key format with isrCacheKey()
    • Binary RSC base64 round-trip
    • FNV1a hash for long pathnames (>200 chars)
    • Static routes: null revalidateAt, no expiration_ttl
    • ISR routes: correct revalidateAt, TTL clamping (min 60s)
    • revalidate=0 treated as static
    • Unicode pathnames
    • Empty HTML files
    • Mixed manifest (App + Pages + skipped + error routes)
    • Batch splitting at 10,000 pairs with order preservation
    • API success:false rejection
    • API HTTP error handling
  • All existing tests pass
  • Formatting passes vp fmt --check

Closes #562

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 1, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@734

commit: fc51d75

@raed04 raed04 force-pushed the feat/seed-kv-cache-562 branch from 554d801 to 43295ba Compare April 1, 2026 03:34
Adds automatic KV cache population during `vinext deploy`. After
pre-rendering completes and before wrangler deploys the worker,
pre-rendered HTML/RSC files are uploaded to Workers KV via the
Cloudflare bulk REST API so pages are cache HITs from the first request.

- New module: seed-kv-cache.ts reads vinext-prerender.json, constructs
  correct ISR cache keys via isrCacheKey(), base64-encodes RSC data,
  and uploads in batches of 10,000 pairs
- Integrates as Step 6c in deploy.ts between prerender and wrangler deploy
- Automatic when CLOUDFLARE_API_TOKEN is set and VINEXT_CACHE KV namespace
  exists in wrangler config; opt-out via --no-seed-cache
- Non-fatal: seeding failures log a warning but don't block deployment
- Security: path traversal guard, TOCTOU-safe file reads, capped error
  messages, Cloudflare success:false response checking
- Static routes omit expiration_ttl to match runtime KVCacheHandler behavior
- 27 unit tests covering key format, binary RSC, TTL clamping, unicode
  paths, batch splitting, API error handling, and mixed manifests

Closes cloudflare#562
@raed04 raed04 force-pushed the feat/seed-kv-cache-562 branch from 43295ba to fc51d75 Compare April 1, 2026 03:41
@NathanDrake2406
Copy link
Copy Markdown
Contributor

Hey bro, I've already attempted this #670
But for vinext, we're aiming for something that is more universally supported than just hardcoding KV like James said.

@raed04 raed04 closed this Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Populating remote cache during deployment

2 participants