[GH-ISSUE #676] Non-ASCII dynamic route params crash RSC streaming with ByteString error #145

Closed
opened 2026-05-06 12:37:38 +02:00 by BreizhHardware · 0 comments

Originally created by @seoJing on GitHub (Mar 24, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/676

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

Bug Description

When a dynamic route parameter contains non-ASCII characters (e.g., Korean, Japanese, Chinese), RSC (React Server Component) streaming requests fail with a 500 Internal Server Error. Direct URL access (full SSR) works correctly, but client-side navigation (.rsc requests) crashes.

Root Cause

In src/server/app-page-response.ts, the buildAppPageRscResponse() function serializes route params directly into an HTTP header:

if (options.params && Object.keys(options.params).length > 0)
  headers.set("X-Vinext-Params", JSON.stringify(options.params));

Per the Fetch specification, Headers.set() requires the value to be a ByteString (each character code ≤ 255). When params contain non-ASCII characters, JSON.stringify() preserves them as-is, causing a TypeError.

Reproduction

  1. Create a catch-all route: app/blog/[...slug]/page.tsx
  2. Navigate to a non-ASCII slug via client-side navigation (e.g., click a <Link> to /blog/frontend/hooks/useState-완전정복)
  3. The .rsc request fails with:
TypeError: Cannot convert argument to a ByteString because the character at
index 38 has a value of 50756 which is greater than 255.
    at webidl.converters.ByteString (node:internal/deps/undici/undici:3889:17)

Verification: index 38 of JSON.stringify({"slug":["frontend","hooks","useState-완전정복"]}) is (U+C644 = 50756), confirming the exact failure point.

Minimal reproduction

const headers = new Headers();
// This throws TypeError:
headers.set("X-Vinext-Params", JSON.stringify({ slug: ["useState-완전정복"] }));

Impact

Scenario Result
Non-ASCII slug, direct URL access (SSR) Works — buildAppPageHtmlResponse() does not set X-Vinext-Params
Non-ASCII slug, client-side navigation (.rsc) 500 error — buildAppPageRscResponse() crashes
ASCII-only slugs Works in all cases

This affects any locale that uses non-ASCII characters in URL slugs (Korean, Japanese, Chinese, Arabic, Cyrillic, etc.).

Suggested Fix

Percent-encode the JSON string before setting the header:

// Before
headers.set("X-Vinext-Params", JSON.stringify(options.params));

// After
headers.set("X-Vinext-Params", encodeURIComponent(JSON.stringify(options.params)));

The consumer side would need a corresponding decodeURIComponent() call. This is the same approach Next.js uses for non-ASCII values in HTTP headers (ref: vercel/next.js#27003).

Environment

  • vinext: 0.0.33
  • Node.js: v22.x
  • OS: Linux (WSL2)
  • Browser: Chrome

Additional Context

I discovered this while building a Korean-language MDX blog. The blog list page (/blog) triggers client-side prefetch/navigation for each post link, and all non-ASCII slugs fail simultaneously. I'd be happy to submit a PR with the fix if the suggested approach looks right.

Originally created by @seoJing on GitHub (Mar 24, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/676 # Non-ASCII dynamic route params crash RSC streaming with ByteString error ## Bug Description When a dynamic route parameter contains non-ASCII characters (e.g., Korean, Japanese, Chinese), RSC (React Server Component) streaming requests fail with a 500 Internal Server Error. Direct URL access (full SSR) works correctly, but client-side navigation (`.rsc` requests) crashes. ## Root Cause In `src/server/app-page-response.ts`, the `buildAppPageRscResponse()` function serializes route params directly into an HTTP header: ```ts if (options.params && Object.keys(options.params).length > 0) headers.set("X-Vinext-Params", JSON.stringify(options.params)); ``` Per the [Fetch specification](https://fetch.spec.whatwg.org/#concept-header-value), `Headers.set()` requires the value to be a **ByteString** (each character code ≤ 255). When params contain non-ASCII characters, `JSON.stringify()` preserves them as-is, causing a `TypeError`. ## Reproduction 1. Create a catch-all route: `app/blog/[...slug]/page.tsx` 2. Navigate to a non-ASCII slug via client-side navigation (e.g., click a `<Link>` to `/blog/frontend/hooks/useState-완전정복`) 3. The `.rsc` request fails with: ``` TypeError: Cannot convert argument to a ByteString because the character at index 38 has a value of 50756 which is greater than 255. at webidl.converters.ByteString (node:internal/deps/undici/undici:3889:17) ``` **Verification:** index 38 of `JSON.stringify({"slug":["frontend","hooks","useState-완전정복"]})` is `완` (U+C644 = 50756), confirming the exact failure point. ### Minimal reproduction ```js const headers = new Headers(); // This throws TypeError: headers.set("X-Vinext-Params", JSON.stringify({ slug: ["useState-완전정복"] })); ``` ## Impact | Scenario | Result | |----------|--------| | Non-ASCII slug, direct URL access (SSR) | ✅ Works — `buildAppPageHtmlResponse()` does not set `X-Vinext-Params` | | Non-ASCII slug, client-side navigation (.rsc) | ❌ 500 error — `buildAppPageRscResponse()` crashes | | ASCII-only slugs | ✅ Works in all cases | This affects any locale that uses non-ASCII characters in URL slugs (Korean, Japanese, Chinese, Arabic, Cyrillic, etc.). ## Suggested Fix Percent-encode the JSON string before setting the header: ```ts // Before headers.set("X-Vinext-Params", JSON.stringify(options.params)); // After headers.set("X-Vinext-Params", encodeURIComponent(JSON.stringify(options.params))); ``` The consumer side would need a corresponding `decodeURIComponent()` call. This is the same approach Next.js uses for non-ASCII values in HTTP headers (ref: [vercel/next.js#27003](https://github.com/vercel/next.js/pull/27003)). ## Environment - vinext: 0.0.33 - Node.js: v22.x - OS: Linux (WSL2) - Browser: Chrome ## Additional Context I discovered this while building a Korean-language MDX blog. The blog list page (`/blog`) triggers client-side prefetch/navigation for each post link, and all non-ASCII slugs fail simultaneously. I'd be happy to submit a PR with the fix if the suggested approach looks right.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#145
No description provided.