Skip to content

perf: emit pre-compiled regex literals for config patterns at build time #389

@james-elicx

Description

@james-elicx

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions