Skip to content

fix: percent-encode X-Vinext-Params to allow non-ASCII route params#677

Merged
southpolesteve merged 1 commit intomainfrom
fix/non-ascii-params-bytestring-676
Mar 24, 2026
Merged

fix: percent-encode X-Vinext-Params to allow non-ASCII route params#677
southpolesteve merged 1 commit intomainfrom
fix/non-ascii-params-bytestring-676

Conversation

@southpolesteve
Copy link
Copy Markdown
Collaborator

Summary

  • buildAppPageRscResponse was calling Headers.set("X-Vinext-Params", JSON.stringify(params)) with raw Unicode characters in the value
  • The Fetch spec requires header values to be ByteStrings (each char ≤ U+00FF), so non-ASCII params (Korean, Japanese, etc.) caused a TypeError: Cannot convert argument to a ByteString
  • Only RSC/client-side navigation was affected — direct SSR requests (buildAppPageHtmlResponse) never set this header

Fix

  • Encode with encodeURIComponent when setting X-Vinext-Params in app-page-response.ts
  • Decode with decodeURIComponent in both read sites in app-browser-entry.ts (initial hydration and client-side navigation)

Test

Added a unit test to tests/app-page-response.test.ts that verifies:

  1. The header value is ASCII-safe after encoding
  2. The params round-trip correctly through encode/decode

Closes #676

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 24, 2026

Open in StackBlitz

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

commit: edc637b

@github-actions
Copy link
Copy Markdown

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

…676)

Headers.set() requires ByteString values (chars <= U+00FF). When dynamic
route params contain non-ASCII characters (Korean, Japanese, etc.),
JSON.stringify preserves them verbatim which causes a TypeError on the
Headers.set() call in buildAppPageRscResponse.

Fix: encodeURIComponent the JSON on the server side before setting the
header, and decodeURIComponent on the client side when reading it back
during initial hydration and client-side navigation.

Closes #676
@southpolesteve southpolesteve force-pushed the fix/non-ascii-params-bytestring-676 branch from 049fb18 to edc637b Compare March 24, 2026 14:58
@southpolesteve
Copy link
Copy Markdown
Collaborator Author

/bigbonk review please

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Clean, well-scoped fix. The diagnosis is correct: Headers.set() requires ByteString values (each char ≤ U+00FF per the Fetch spec), and JSON.stringify of non-ASCII params like Korean characters produces code points well above that limit.

What I verified:

  1. All read/write sites are covered. There is exactly one write site (app-page-response.ts:172) and two read sites (app-browser-entry.ts:104 for initial hydration fallback, :243 for client-side navigation). All three are updated symmetrically with encodeURIComponent/decodeURIComponent.

  2. No other consumers exist. Grepped the entire packages/vinext/src/ tree for Vinext-Params — only the three sites above. The generated entry code and prod server don't read this header directly.

  3. The encoding choice is appropriate. encodeURIComponent produces only ASCII characters (letters, digits, and percent-encoded sequences), which trivially satisfies the ByteString constraint. It round-trips cleanly through decodeURIComponent.

  4. Existing test updated correctly. The ASCII-params test now asserts the encoded form (encodeURIComponent('{"slug":"test"}')), and the new non-ASCII test validates both the ASCII-safety invariant and the encode/decode round-trip with Korean text.

  5. Error handling is preserved. Both read sites already had try/catch blocks that gracefully handle malformed headers — decodeURIComponent on a corrupted value will throw, which falls into those existing catch blocks.

LGTM.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Mar 24, 2026

Reviewed and approved PR #677. The fix is correct and complete:

  • Root cause: Headers.set() requires ByteString values (chars ≤ U+00FF), but JSON.stringify of non-ASCII route params (Korean, Japanese, etc.) produces code points far above that limit, causing a TypeError.
  • Fix: encodeURIComponent on write, decodeURIComponent on read — all three sites (1 write, 2 read) are updated symmetrically.
  • Coverage: No other consumers of X-Vinext-Params exist in the codebase. The existing try/catch blocks at both read sites naturally handle any decodeURIComponent failures on corrupted values.
  • Test: New test validates ASCII-safety of the encoded header value and round-trip correctness with Korean text. Existing test updated to assert the encoded form.

github run

@southpolesteve southpolesteve merged commit 6cfdaa6 into main Mar 24, 2026
30 checks passed
@southpolesteve southpolesteve deleted the fix/non-ascii-params-bytestring-676 branch March 24, 2026 15:06
@seoJing
Copy link
Copy Markdown

seoJing commented Mar 24, 2026

Ah, I was struggling with my local setup and just uploaded the PR, but I see you already fixed it! Thanks for the quick response. I'll close my PR.

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.

Non-ASCII dynamic route params crash RSC streaming with ByteString error

2 participants