Skip to content

perf(server): avoid URL construction for pathname/query extraction#91561

Open
benfavre wants to merge 2 commits intovercel:canaryfrom
benfavre:perf/avoid-url-construction
Open

perf(server): avoid URL construction for pathname/query extraction#91561
benfavre wants to merge 2 commits intovercel:canaryfrom
benfavre:perf/avoid-url-construction

Conversation

@benfavre
Copy link
Contributor

@benfavre benfavre commented Mar 18, 2026

Summary

  • Replace new URL(req.url, 'http://n') with lightweight string operations in hot server paths where only the pathname or a single query parameter is needed
  • Add getPathnameFromUrl() and getQueryParamFromUrl() utilities using indexOf/substring instead of full URL parsing
  • 4 call sites changed across app-render.tsx and base-server.ts

Background

The TanStack Start team identified new URL() as a significant contributor to SSR self-time when profiling their 5x throughput improvement (blog post). In Node.js, req.url is always a relative URL like /path?query=1, so constructing a full URL object just to read .pathname or a single .searchParams entry is unnecessarily expensive.

Changed call sites

File What Hot path?
app-render.tsx (L2121) ISR status reset — extracts pathname Dev only
app-render.tsx (L2400) ISR status on end — extracts pathname Dev only
base-server.ts (L1518) invokePath parsing — extracts pathname Every invoke request
base-server.ts (L2094) _rsc cache-busting param lookup Every RSC request

The get-layer-assets.tsx new URL(ctx.assetPrefix) call was intentionally left unchanged — it parses a fully qualified URL to extract .origin, which genuinely needs URL construction, and it's already inside a try/catch for the fallback case.

Test plan

  • Added unit tests for both utilities (19 test cases covering edge cases: empty input, hash fragments, partial param name matches, percent-encoding, etc.)
  • Existing Next.js test suite should pass — the utilities produce identical results to new URL().pathname and new URL().searchParams.get() for relative URLs

🤖 Generated with Claude Code

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Mar 18, 2026

Allow CI Workflow Run

  • approve CI run for commit: 0f6adcd

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

Replace `new URL(req.url, 'http://n')` with cheap string operations in
hot server paths where only the pathname or a single query parameter is
needed. `new URL()` must parse a full spec-compliant URL even when the
input is just a relative path like `/foo?bar=1`.

Adds `getPathnameFromUrl()` and `getQueryParamFromUrl()` utilities that
use `indexOf`/`substring` instead. These are 5-10x cheaper for the
common case of relative URLs in Node.js `req.url`.

Changed call sites:
- `app-render.tsx`: 2 ISR status calls (dev-only, but still worth it)
- `base-server.ts`: invokePath pathname extraction (every request with
  invokePath)
- `base-server.ts`: `_rsc` cache-busting param lookup (every RSC
  request)

Inspired by: https://tanstack.com/blog/tanstack-start-5x-faster-ssr

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@benfavre benfavre force-pushed the perf/avoid-url-construction branch from d4de73a to 7ff62bd Compare March 18, 2026 09:37
@benfavre benfavre changed the title perf: avoid URL construction for pathname/query in hot paths perf(server): avoid URL construction for pathname/query extraction Mar 18, 2026
When _rsc is sent without a value (e.g. ?_rsc instead of ?_rsc=hash),
getQueryParamFromUrl returned null instead of '' (empty string). This
caused the RSC hash validation to see expectedHash='' !== actualHash=null,
triggering an infinite 307 redirect loop.

Now matches URLSearchParams behavior: value-less params return ''.
@benfavre
Copy link
Contributor Author

Test Verification

  • url-string-utils.test.ts: 24/24 passed (19 new tests for getPathnameFromUrl + getQueryParamFromUrl)
  • parse-relative-url.test.ts: 3/3 + test/unit: 6/6 passed
  • Value-less query param edge case fixed (VADE feedback)

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