Skip to content

perf: skip rewrite processing when no rewrites configured#91597

Open
benfavre wants to merge 1 commit intovercel:canaryfrom
benfavre:perf/skip-empty-config-phases
Open

perf: skip rewrite processing when no rewrites configured#91597
benfavre wants to merge 1 commit intovercel:canaryfrom
benfavre:perf/skip-empty-config-phases

Conversation

@benfavre
Copy link
Copy Markdown
Contributor

Summary

  • Adds a fast path in handleRewrites (in server-utils.ts) that returns the original parsedUrl immediately when no rewrites are configured, avoiding a per-request structuredClone, closure creation, and empty array iteration
  • Precomputes a hasRewrites flag once in getServerUtils (at route setup time) rather than checking array lengths on every request
  • Exposes hasRewrites on the returned utils object for caller awareness

Why

For Next.js apps that don't use rewrites (the common case), every request through the minimal-mode code path in base-server.ts and the route-module.ts render path called handleRewrites, which:

  1. Called structuredClone(parsedUrl) — a deep clone of the URL + query object
  2. Created matchesPage and checkRewrite closures (allocating two function objects)
  3. Iterated three empty arrays (beforeFiles, afterFiles, fallback)
  4. Returned the clone unchanged

With this change, when all three rewrite arrays are empty (or undefined), the function returns immediately with the original parsedUrl reference and an empty rewriteParams object. This is safe because:

  • base-server.ts only reads from rewrittenParsedUrl (to create rewrittenQueryParams copy and check didRewrite)
  • route-module.ts line Object.assign(parsedUrl.query, rewrittenParsedUrl.query) becomes a self-assign no-op
  • normalizeLocalePath on rewrittenParsedUrl.pathname is idempotent when it equals parsedUrl.pathname

Test plan

  • TypeScript compilation passes (tsc --noEmit on packages/next — 0 new errors)
  • Existing server-utils.test.ts tests all use rewrites: {} (empty config), exercising the new fast path
  • Prettier + ESLint pass (lint-staged)
  • Existing e2e rewrite tests should continue to pass — the slow path (with actual rewrites) is unchanged

For Next.js apps that don't configure rewrites (the common case),
`handleRewrites` was performing a `structuredClone` of the parsed URL,
creating `matchesPage` and `checkRewrite` closures, and iterating three
empty arrays on every request — only to return the clone unchanged.

This adds a fast path that precomputes a `hasRewrites` flag once in
`getServerUtils` and returns the original `parsedUrl` immediately when
no rewrites exist, avoiding the deep clone, closure allocation, and
iteration overhead per request.

Also exposes the `hasRewrites` flag on the returned utils object so
callers can make rewrite-aware decisions if needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nextjs-bot
Copy link
Copy Markdown
Collaborator

Allow CI Workflow Run

  • approve CI run for commit: 45f6ec0

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@benfavre
Copy link
Copy Markdown
Contributor Author

Performance Impact

Profiling setup: Node.js v25.7.0, --cpu-prof, autocannon c=30 for 20s, 10-layout deep route (no rewrites configured).

Before (canary):

  • handleRewrites() runs on every request regardless of configuration
  • Always performs: shallow copy of parsedUrl + shallow copy of query (2 object allocations)
  • Creates 2 closures: matchesPage() and checkRewrite()
  • Iterates 3 arrays: rewrites.beforeFiles, rewrites.afterFiles, rewrites.fallback
  • For apps with zero rewrites (very common), all this work produces an identical copy of the input

After (this PR):

  • hasRewrites flag computed once at getServerUtils() creation time
  • When false: handleRewrites() returns immediately with the original parsedUrl reference + empty rewriteParams
  • Eliminates per-request: 2 object spreads, 2 closure allocations, 3 array iterations
  • Caller safety verified: all 3 call sites (base-server.ts, route-module.ts, edge-route-module-wrapper.ts) either read from the returned URL to create separate copies, or perform idempotent mutations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants