Skip to content

fix(cache): round-trip Infinity revalidate in kvDataAdapter - #3315

Open
snhsish wants to merge 2 commits into
cloudflare:mainfrom
snhsish:fix/kv-data-adapter-infinity-revalidate
Open

snhsish wants to merge 2 commits into
cloudflare:mainfrom
snhsish:fix/kv-data-adapter-infinity-revalidate

Conversation

@snhsish

@snhsish snhsish commented Sep 17, 2026

Copy link
Copy Markdown

Summary

  • Round-trip the Infinity revalidate value used by runtime-rendered static App Router pages through Cloudflare KV.
  • Accept JSON's existing null representation at the cache-entry validation boundary and restore it to Infinity before callers see the entry.
  • Reject NaN and -Infinity writes so invalid values cannot be confused with the static-page sentinel.

Why

JSON.stringify(Infinity) produces null. The KV adapter previously wrote that value for a static page, then rejected the same row on its next read. Each request therefore became a miss followed by a delete, render, and full KV rewrite.

This also repairs beta.9/beta.10 rows already stored with cacheControl.revalidate: null.

Fixes #3311.

Simplification

The original patch added 91 production lines and 57 test lines. The final cumulative diff adds 12 production lines (and removes 1) plus 38 test lines.

The implementation uses native JSON serialization and normalizes once at the existing schema boundary. This removes the custom serialized metadata type, four number codec helpers, duplicate return-path decoding, raw/encoded timestamp pairs, and a redundant legacy-row test. The retained production code is only:

  1. a guard that permits positive Infinity but rejects other non-finite revalidate values;
  2. validator acceptance of the persisted null representation; and
  3. normalization back to Infinity before returning a validated entry.

Next.js parity and runtime scope

Next.js represents non-revalidating cache policy as false or its finite INFINITE_CACHE sentinel internally. Vinext's existing runtime App Router path intentionally uses positive Infinity, and its response-header logic already treats that value as the static policy. This PR preserves that current vinext contract rather than changing cache semantics.

The Cloudflare KV runtime adapter is shared by the App/Pages dev and production registrations. Deploy-time prerender KV seeding continues to represent static routes as an absent cache-control claim, while TPR only writes finite revalidate values; neither path needs a change.

Red / green proof

  • Base e97de291c: the focused regression failed with Invalid cache entry shape and returned no cache hit.
  • Head: the same set -> JSON KV storage -> parse -> validate -> get round trip passes, preserves the 30-day KV TTL, and does not delete the entry.

Testing

  • vp test run tests/kv-cache-handler.test.ts tests/isr-cache.test.ts tests/fetch-cache.test.ts (314 passed)
  • vp test run tests/app-page-cache.test.ts -t "indefinite|non-finite" (2 passed)
  • vp check packages/cloudflare/src/cache/kv-data-adapter.runtime.ts tests/kv-cache-handler.test.ts
  • vp run @vinext/cloudflare#build

The focused test exercises the complete adapter serialization boundary directly; there is no separate worker-specific branch between the KV string and this parser.

@pkg-pr-new

pkg-pr-new Bot commented Sep 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vinext/cloudflare

npm i https://pkg.pr.new/@vinext/cloudflare@3315

create-vinext-app

npm i https://pkg.pr.new/create-vinext-app@3315

@vinext/types

npm i https://pkg.pr.new/@vinext/types@3315

vinext

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

@cloudflare/workers-response-store

npm i https://pkg.pr.new/@cloudflare/workers-response-store@3315

commit: 845edc8

@github-actions

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 845edc8 against base e97de29 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 142.2 KB 142.2 KB ⚫ -0.0%
Client entry size (gzip) vinext 129.6 KB 129.6 KB ⚫ 0.0%
Dev server cold start vinext 3.12 s 3.10 s ⚫ -0.4%
Production build time vinext 3.51 s 3.52 s ⚫ +0.0%
RSC entry closure size (gzip) vinext 121.0 KB 121.0 KB ⚫ -0.0%
Server bundle size (gzip) vinext 218.3 KB 218.3 KB ⚫ +0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx

Copy link
Copy Markdown
Member

/bigbonk review for issues

Time limit: 8 minutes. Complete the review and post all findings within that limit.

@ask-bonk

ask-bonk Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

LGTM!

github run

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.

kvDataAdapter rejects its own entries for static App Router pages (revalidate: Infinity is stored as null) since 1.0.0-beta.9

2 participants