Background
PR #387 fixed the dominant per-request CPU bottleneck in matchConfigPattern by adding a module-level Map cache so the tokeniser, isSafeRegex scan, and new RegExp() call run at most once per unique pattern string per isolate lifetime.
That fix is sufficient for long-lived isolates (Cloudflare Workers reuses isolates aggressively for popular routes). The remaining opportunity is to move pattern compilation entirely to build time so even the first request after a cold start pays zero compilation cost.
What this would look like
Redirect/rewrite source patterns are already baked into the bundle as JSON-serialized arrays:
// Current output in the generated virtual module
const __configRedirects = [
{ source: "/:locale(en|es|fr|id|ja|ko|pt-br|pt|ro|ta|tr|uk|zh-cn|zh-tw)?/security",
destination: "/security", permanent: true },
// ...87 more rules
];
With build-time compilation, the code generator would also emit a parallel compiled array as raw JS regex literals, completely bypassing matchConfigPattern's regex branch at runtime:
// Proposed: emitted alongside __configRedirects
const __compiledRedirects = [
{ re: /^\/(en|es|fr|id|ja|ko|pt-br|pt|ro|ta|tr|uk|zh-cn|zh-tw)?\/security$/,
paramNames: ["locale"], destination: "/security", permanent: true },
// null for simple segment rules that don't use the regex branch
null,
// ...
];
matchRedirect would accept an optional pre-compiled array and skip matchConfigPattern entirely when it's present.
What needs to change
-
Extract a compileConfigPattern(pattern) function from matchConfigPattern that returns { re: RegExp; paramNames: string[] } | null — the same logic currently cached by _compiledPatternCache, made callable from the code generators.
-
generateRscEntry (App Router) — call compileConfigPattern for each redirect/rewrite/header source at code-gen time; emit __compiledRedirects, __compiledRewrites, __compiledHeaders as JS regex literals alongside the existing __configRedirects JSON arrays.
-
generateServerEntry (Pages Router) — same: embed compiled forms into the vinextConfig export or as separate module-level constants.
-
matchRedirect / matchRewrite — add an overload (or a new matchRedirectCompiled) that accepts the pre-compiled array and skips to re.exec directly.
-
Dev server (index.ts connect handler) — the dev server calls applyRedirects/applyRewrites which call matchRedirect/matchRewrite with the live nextConfig arrays. Pre-compilation here would mean compiling once at plugin init time (after resolveNextConfig) and keeping a compiled copy alongside nextConfig. Worth doing for consistency, though the module-level cache in config-matchers.ts already covers this path.
-
Emit regex source correctly — re.source + re.flags can be used to reconstruct a regex literal as a string: `/${re.source}/${re.flags}`. Flags are "" for all current patterns (no i, g, etc.), so the output is simply /${re.source}/.
Why the current cache is sufficient for most cases
The module-level Map cache (PR #387) means:
- Steady-state (warm isolate): O(1)
Map.get per rule — identical to reading a module constant
- Cold start / first request: each unique pattern compiled once, amortised over all subsequent requests
Build-time emission only helps the first request after a cold start on low-traffic routes where isolates are recycled frequently. For high-traffic routes Workers keeps isolates alive long enough that the cache is always warm.
When to prioritise this
Worth revisiting if profiling shows measurable p99 cold-start latency from pattern compilation, or if the codebase moves toward a model where isolates are intentionally short-lived (e.g. per-request isolate mode).
Background
PR #387 fixed the dominant per-request CPU bottleneck in
matchConfigPatternby adding a module-levelMapcache so the tokeniser,isSafeRegexscan, andnew RegExp()call run at most once per unique pattern string per isolate lifetime.That fix is sufficient for long-lived isolates (Cloudflare Workers reuses isolates aggressively for popular routes). The remaining opportunity is to move pattern compilation entirely to build time so even the first request after a cold start pays zero compilation cost.
What this would look like
Redirect/rewrite
sourcepatterns are already baked into the bundle as JSON-serialized arrays:With build-time compilation, the code generator would also emit a parallel compiled array as raw JS regex literals, completely bypassing
matchConfigPattern's regex branch at runtime:matchRedirectwould accept an optional pre-compiled array and skipmatchConfigPatternentirely when it's present.What needs to change
Extract a
compileConfigPattern(pattern)function frommatchConfigPatternthat returns{ re: RegExp; paramNames: string[] } | null— the same logic currently cached by_compiledPatternCache, made callable from the code generators.generateRscEntry(App Router) — callcompileConfigPatternfor each redirect/rewrite/header source at code-gen time; emit__compiledRedirects,__compiledRewrites,__compiledHeadersas JS regex literals alongside the existing__configRedirectsJSON arrays.generateServerEntry(Pages Router) — same: embed compiled forms into thevinextConfigexport or as separate module-level constants.matchRedirect/matchRewrite— add an overload (or a newmatchRedirectCompiled) that accepts the pre-compiled array and skips tore.execdirectly.Dev server (
index.tsconnect handler) — the dev server callsapplyRedirects/applyRewriteswhich callmatchRedirect/matchRewritewith the livenextConfigarrays. Pre-compilation here would mean compiling once at plugin init time (afterresolveNextConfig) and keeping a compiled copy alongsidenextConfig. Worth doing for consistency, though the module-level cache inconfig-matchers.tsalready covers this path.Emit regex source correctly —
re.source+re.flagscan be used to reconstruct a regex literal as a string:`/${re.source}/${re.flags}`. Flags are""for all current patterns (noi,g, etc.), so the output is simply/${re.source}/.Why the current cache is sufficient for most cases
The module-level
Mapcache (PR #387) means:Map.getper rule — identical to reading a module constantBuild-time emission only helps the first request after a cold start on low-traffic routes where isolates are recycled frequently. For high-traffic routes Workers keeps isolates alive long enough that the cache is always warm.
When to prioritise this
Worth revisiting if profiling shows measurable p99 cold-start latency from pattern compilation, or if the codebase moves toward a model where isolates are intentionally short-lived (e.g. per-request isolate mode).