Skip to content

fix(cache): encode non-ASCII characters in cache tags at construction#1143

Merged
james-elicx merged 1 commit into
mainfrom
claude/modest-ptolemy-9c99ef
May 8, 2026
Merged

fix(cache): encode non-ASCII characters in cache tags at construction#1143
james-elicx merged 1 commit into
mainfrom
claude/modest-ptolemy-9c99ef

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Summary

  • Adds encodeCacheTag / encodeCacheTags helper (mirrors vercel/next.js#93601) that percent-encodes any byte outside \t\x20-\x7e, fast-paths ASCII, handles surrogate pairs as whole code points, and is idempotent on %xx input.
  • Applies it at every public cache-tag boundary so storage, comparison, and any downstream x-next-cache-tags / Cloudflare cache-tag header see the same canonical ASCII-safe form: cacheTag, revalidateTag, revalidatePath, updateTag, unstable_cache options.tags, fetch(..., { next: { tags } }), and the path-derived _N_T_ tag builders (buildPageCacheTags, buildAppPageCacheTags).
  • Adds tests/encode-cache-tag.test.ts covering ASCII fast-path, multi-script encoding (Hebrew/Arabic/Japanese), surrogate-pair safety, %xx round-trip preservation, control-char encoding, idempotence, and that the path-derived builders produce ASCII-only output for non-ASCII pathnames + extra tags.

Closes #1138.

Test plan

  • pnpm test:unit tests/encode-cache-tag.test.ts — 11 cases pass
  • pnpm test:unit tests/page-cache-tags.test.ts tests/app-page-cache.test.ts tests/fetch-cache.test.ts — existing suites unaffected (121 pass)
  • pnpm test:integration tests/kv-cache-handler.test.ts — 56 pass

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review

@pkg-pr-new

pkg-pr-new Bot commented May 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 73e8b47

@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped fix. Reviewed for correctness, completeness, and Next.js parity.

Encoding implementation (encode-cache-tag.ts): Faithful port of Next.js's packages/next/src/server/lib/encode-cache-tag.ts from vercel/next.js#93601. The two-regex pattern (non-g for the test fast-path, g for the replace) avoids the stale-lastIndex pitfall. Matching runs of out-of-class code units prevents URIError on surrogate pairs. Idempotence on %xx input is a natural consequence of % being in \x20-\x7e — good that the tests verify this explicitly.

Boundary coverage: Encoding is applied at every user-facing entry point:

  • revalidateTag, revalidatePath, updateTag — single-tag encoding before handler.revalidateTag()
  • cacheTag() — batch encoding before pushing into ctx.tags
  • unstable_cache — batch encoding of options.tags at wrapper creation time
  • fetch(..., { next: { tags } }) — batch encoding in createPatchedFetch
  • buildPageCacheTags / buildAppPageCacheTagstags.map(encodeCacheTag) on the output array

All downstream consumers (ISR cache, KV cache handler, dispatch, RSC handler, route handler, soft tags) only receive already-encoded tags and never introduce new unencoded user input. No missed boundaries.

Next.js parity: Next.js applies encoding at the same boundaries — getImplicitTags, validateTags, revalidatePath, revalidateTag, updateTag, and the patched fetch. The vinext equivalents line up correctly.

Tests: Good coverage — ASCII fast-path, multi-script (Hebrew/Arabic/Japanese), surrogate pairs, %xx round-trip, control characters, idempotence, and integration with both tag builders. The path-derived builder tests verify the critical invariant: tags stored at render time match what revalidatePath/revalidateTag produce.

Ship it.

@ask-bonk

ask-bonk Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Reviewed and approved PR #1143. The fix is a correct, complete port of Next.js's encodeCacheTag (from vercel/next.js#93601), applied at every public cache-tag boundary with no missed callsites. All 11 tests pass.

github run

Tags with non-ASCII characters (Hebrew, Arabic, CJK, emoji) can crash ISR
responses with ERR_INVALID_CHAR when written to x-next-cache-tags, and
silently break invalidation when storage form diverges from the form
revalidateTag/revalidatePath produce.

Add encodeCacheTag/encodeCacheTags helper (matches behavior of
vercel/next.js#93601) and apply it at every public boundary: cacheTag,
revalidateTag, revalidatePath, updateTag, unstable_cache options.tags,
fetch next.tags, and the path-derived _N_T_ tag builders.

Closes #1138
@james-elicx james-elicx force-pushed the claude/modest-ptolemy-9c99ef branch from 4cb76c9 to 73e8b47 Compare May 8, 2026 20:33
@james-elicx james-elicx enabled auto-merge (squash) May 8, 2026 20:33
@james-elicx james-elicx merged commit 15cff6d into main May 8, 2026
33 checks passed
@james-elicx james-elicx deleted the claude/modest-ptolemy-9c99ef branch May 8, 2026 20:36
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.

Encode non-ASCII characters in cache tags at construction (revalidateTag, cacheTag, fetch tags)

1 participant