[PR #1049] [MERGED] fix(routing): decode matched params with decodeURIComponent #1049

Closed
opened 2026-05-06 13:11:44 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1049
Author: @NathanDrake2406
Created: 5/4/2026
Status: Merged
Merged: 5/4/2026
Merged by: @james-elicx

Base: mainHead: fix/decode-dynamic-params


📝 Commits (2)

  • 5e88c1b fix(routing): decode matched params with decodeURIComponent
  • 3c18ddd test(app-rsc-route-matching): update param decode assertion to match new behavior

📊 Changes

5 files changed (+125 additions, -4 deletions)

View changed files

📝 packages/vinext/src/routing/route-pattern.ts (+22 -1)
📝 packages/vinext/src/routing/route-trie.ts (+32 -1)
📝 tests/app-rsc-route-matching.test.ts (+4 -2)
📝 tests/route-pattern.test.ts (+28 -0)
📝 tests/route-trie.test.ts (+39 -0)

📄 Description

Summary

  • Dynamic params captured by the route matcher are now decoded with decodeURIComponent, matching Next.js behavior
  • %2F/, %23#, %3F? in param values (pages, route handlers, metadata, useParams)
  • Decode applies to both single dynamic params and individual catch-all array elements

Problem

Vinext's segment-safe normalization preserves encoded delimiters for correct route matching (so /files/a%2Fb matches /files/[name] instead of splitting on the encoded slash). However, the captured param values were returned undecoded: the trie would correctly match but return name = "a%2Fb" instead of name = "a/b".

Next.js applies decodeURIComponent at the route-matcher level:
https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/route-matcher.ts#L25-L27

This divergence affects every consumer of matched params: page components, generateMetadata, route handlers, and useParams.

Fix

Add decodeURIComponent wrappers at the two route matching entry points:

  • trieMatch in packages/vinext/src/routing/route-trie.ts — used by the App Router and Pages Router trie-based matchers
  • matchRoutePattern in packages/vinext/src/routing/route-pattern.ts — used for slot-specific param extraction, route handlers, and metadata routes

The decode helpers handle both string and string[] (catch-all) param values and gracefully preserve malformed percent escapes (the normalization layer already validates encoding in the strict request path).

Tests

Edge case Coverage
%2F, %23, %3F in dynamic segment Decoded without splitting segments
Catch-all and optional catch-all arrays Each element individually decoded
Malformed %GG Preserved without throwing
Double-encoded %252F Single decode pass only (→ %2F)

Verification

  • vp test run tests/route-trie.test.ts — 64 tests pass
  • vp test run tests/route-pattern.test.ts — 9 tests pass
  • vp test run tests/routing.test.ts tests/route-sorting.test.ts — 143 tests pass
  • vp test run tests/app-router.test.ts — 300 tests pass
  • vp test run tests/pages-router.test.ts — 200 tests pass
  • vp lint — 0 warnings, 0 errors

References


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1049 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 5/4/2026 **Status:** ✅ Merged **Merged:** 5/4/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/decode-dynamic-params` --- ### 📝 Commits (2) - [`5e88c1b`](https://github.com/cloudflare/vinext/commit/5e88c1b1ced20da2064e0dab4e8fe9eed7c126e8) fix(routing): decode matched params with decodeURIComponent - [`3c18ddd`](https://github.com/cloudflare/vinext/commit/3c18ddd413050311148cae24587e577bbf42ae6d) test(app-rsc-route-matching): update param decode assertion to match new behavior ### 📊 Changes **5 files changed** (+125 additions, -4 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/routing/route-pattern.ts` (+22 -1) 📝 `packages/vinext/src/routing/route-trie.ts` (+32 -1) 📝 `tests/app-rsc-route-matching.test.ts` (+4 -2) 📝 `tests/route-pattern.test.ts` (+28 -0) 📝 `tests/route-trie.test.ts` (+39 -0) </details> ### 📄 Description ## Summary - Dynamic params captured by the route matcher are now decoded with `decodeURIComponent`, matching Next.js behavior - `%2F` → `/`, `%23` → `#`, `%3F` → `?` in param values (pages, route handlers, metadata, `useParams`) - Decode applies to both single dynamic params and individual catch-all array elements ## Problem Vinext's segment-safe normalization preserves encoded delimiters for correct route matching (so `/files/a%2Fb` matches `/files/[name]` instead of splitting on the encoded slash). However, the captured param values were returned undecoded: the trie would correctly match but return `name = "a%2Fb"` instead of `name = "a/b"`. Next.js applies `decodeURIComponent` at the route-matcher level: https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/route-matcher.ts#L25-L27 This divergence affects every consumer of matched params: page components, `generateMetadata`, route handlers, and `useParams`. ## Fix Add `decodeURIComponent` wrappers at the two route matching entry points: - `trieMatch` in `packages/vinext/src/routing/route-trie.ts` — used by the App Router and Pages Router trie-based matchers - `matchRoutePattern` in `packages/vinext/src/routing/route-pattern.ts` — used for slot-specific param extraction, route handlers, and metadata routes The decode helpers handle both string and `string[]` (catch-all) param values and gracefully preserve malformed percent escapes (the normalization layer already validates encoding in the strict request path). ## Tests | Edge case | Coverage | |---|---| | `%2F`, `%23`, `%3F` in dynamic segment | Decoded without splitting segments | | Catch-all and optional catch-all arrays | Each element individually decoded | | Malformed `%GG` | Preserved without throwing | | Double-encoded `%252F` | Single decode pass only (→ `%2F`) | ## Verification - `vp test run tests/route-trie.test.ts` — 64 tests pass - `vp test run tests/route-pattern.test.ts` — 9 tests pass - `vp test run tests/routing.test.ts tests/route-sorting.test.ts` — 143 tests pass - `vp test run tests/app-router.test.ts` — 300 tests pass - `vp test run tests/pages-router.test.ts` — 200 tests pass - `vp lint` — 0 warnings, 0 errors ## References - Next.js route-matcher.ts decode: https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/route-matcher.ts#L25-L27 - Next.js test for failed param decoding: https://github.com/vercel/next.js/blob/canary/test/production/pages-dir/production/test/index.test.ts#L1082 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:44 +02:00
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#1049
No description provided.