[GH-ISSUE #389] perf: emit pre-compiled regex literals for config patterns at build time #85

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

Originally created by @james-elicx on GitHub (Mar 9, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/389

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

  1. 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.

  2. 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.

  3. generateServerEntry (Pages Router) — same: embed compiled forms into the vinextConfig export or as separate module-level constants.

  4. matchRedirect / matchRewrite — add an overload (or a new matchRedirectCompiled) that accepts the pre-compiled array and skips to re.exec directly.

  5. 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.

  6. Emit regex source correctlyre.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).

Originally created by @james-elicx on GitHub (Mar 9, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/389 ## 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: ```js // 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: ```js // 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 1. **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. 2. **`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. 3. **`generateServerEntry` (Pages Router)** — same: embed compiled forms into the `vinextConfig` export or as separate module-level constants. 4. **`matchRedirect` / `matchRewrite`** — add an overload (or a new `matchRedirectCompiled`) that accepts the pre-compiled array and skips to `re.exec` directly. 5. **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. 6. **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).
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#85
No description provided.