Skip to content

perf: replace structuredClone with shallow copy in handleRewrites#91572

Open
benfavre wants to merge 1 commit intovercel:canaryfrom
benfavre:perf/shallow-copy-rewrite-url
Open

perf: replace structuredClone with shallow copy in handleRewrites#91572
benfavre wants to merge 1 commit intovercel:canaryfrom
benfavre:perf/shallow-copy-rewrite-url

Conversation

@benfavre
Copy link
Contributor

Summary

  • Replace structuredClone(parsedUrl) with object spread + query spread in handleRewrites (packages/next/src/server/server-utils.ts)
  • Pre-compute the basePath RegExp once in getServerUtils instead of allocating a new RegExp on every rewrite check iteration

Why

structuredClone(parsedUrl) was the #1 non-React CPU hotspot in production CPU profiles under load:

Metric Value
Self time 780ms
% of non-React CPU 8.6%
Calls per request 1 (every request through rewrite handling)

structuredClone is designed for deep object graphs with circular references, transferables, typed arrays, etc. None of that applies here — NextUrlWithParsedQuery is a flat object:

{
  auth?: string | null
  hash: string | null
  hostname: string | null
  href: string
  pathname: string | null
  protocol: string | null
  search: string | null
  slashes: boolean | null
  port: string | null
  query: { [key: string]: string | string[] }  // single-level
}

A shallow copy ({ ...parsedUrl, query: { ...parsedUrl.query } }) is semantically equivalent and reduces this hotspot to near-zero overhead.

The new RegExp() inside checkRewrite was also being re-created on every rewrite rule check, despite basePath being constant for the lifetime of the server utils. Hoisted to the outer scope.

Test plan

  • Verified NextUrlWithParsedQuery extends LegacyUrl — all properties are primitives or null, plus flat query record
  • The shallow copy preserves the same mutation semantics: rewrittenParsedUrl and its query are independent copies, so mutations inside checkRewrite don't affect the original parsedUrl
  • Existing rewrite-related integration tests should pass unchanged

🤖 Generated with Claude Code

`structuredClone(parsedUrl)` in `handleRewrites` was the vercel#1 non-React
CPU hotspot at 780ms (8.6% of CPU time) in production profiles under
load. It runs on every request that goes through rewrite handling.

`structuredClone` is expensive because it handles deep object graphs,
circular references, and transferables — none of which apply here.
`NextUrlWithParsedQuery` is a flat object: all properties are primitives
(string | null | boolean) plus a single-level `query` record.

Replace with object spread + query spread, which is semantically
equivalent and reduces this hotspot to near-zero overhead.

Also pre-compute the basePath RegExp outside `handleRewrites` so it
isn't re-allocated on every rewrite check iteration.

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

Allow CI Workflow Run

  • approve CI run for commit: ba9b154

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

@benfavre
Copy link
Contributor Author

Test Verification

  • server-utils.test.ts: 4/4 passed
  • Shallow copy verified — query is single-level flat object, basePath regex pre-compiled

All tests run on the perf/combined-all branch against canary. Total: 203 tests across 13 suites, all passing.

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