Repository navigation
fix(config): substitute matched params into headers() keys and values - #4087
Conversation
Next.js compiles each header key and value with compileNonPath using the source params (raw path text) merged with matchHas captures. vinext sent the templates verbatim, and a param in a header key made Headers throw. Where Next.js fails the request (a template it cannot compile, or a substituted value that is not a valid header), vinext omits only that header.
|
/bigbonk review this PR. You have an 8-minute time limit. Head SHA: 664de28 |
@vinext/cloudflare
create-vinext-app
@vinext/types
vinext
@cloudflare/workers-response-store
commit: |
|
LGTM! |
|
Performance benchmarksCompared 0 improved · 0 regressed · 7 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
|
💬 Approved with comments — The port of Next.js Findings: 1 info The decision is advisory: this comment never approves, blocks or requests changes on the pull request. 🔧 Fix with your agentPaste the following into your coding agent to investigate and fix these issues. ⚡ Performance: 💬 1 infoOne optional note: header templates are re-parsed on every matching request. Recommendation: 💬 Optional Nits
View full reviewHeader templates are recompiled on every matching request (
The cost is a few microseconds per header and the fix is optional. ✅ All other checks passed🔒 Security: ✅ LGTMValues from 📖 AGENTS.md: ✅ LGTMThis pull request does not change agent workflows, commands or architecture, so AGENTS.md does not need an update. 📋 Code Quality: ✅ LGTMI read the changed code but did not run the tests or build it.
📝 Documentation: ✅ LGTMThe new module has JSDoc that links to the Next.js sources it ports. The pull request description explains the known limitation. Reviewed commit: |
Summary
headers()rules now substitute matched params into header keys and values, like Next.js. Before this change,{ source: "/blog/:slug", headers: [{ key: "x-slug", value: ":slug" }] }sent the literal text:slug.The port follows Next.js 16.2.7 (
resolve-routes→compileNonPath/safeCompileinprepare-destination.ts/route-match-utils.ts):hasnamed groups, merged in the same order asmatchHas. Unnamed source params are dropped (removeUnnamedParams)./blog/a%20bgivesa%20b. Nothing is encoded or validated, matching path-to-regexpcompilewithvalidate: false.*/+params are arrays and are joined with the template's prefix/suffix (:path*givesa/b).:namemarkers are substituted. Every other path-to-regexp metacharacter in the template stays literal, andsafeCompile's adjacent-param retry is ported.:skip substitution entirely.Known limitations
When Next.js's
compilethrows (for example, a repeated param referenced without*/+, or anundefinedhascapture), or when the substituted pair is not a valid HTTP header, Next.js fails the request with a 500. vinext omits that one header instead and warns once per rule. WebHeaderswould otherwise throw, or silently accept some control characters, so dropping the header is the predictable choice.Tests
tests/shims.test.ts: "matchHeaders param substitution parity with Next.js", with cases checked against real Next.js behaviour.tests/e2e/app-router/config-redirect.spec.tson the existingapp-basicfixture covers:/blog/:slugpage with params in both key and value;:path*+hasnamed-group rule.