Skip to content

Commit d4de73a

Browse files
benfavreclaude
andcommitted
perf: avoid new URL() construction for pathname/query extraction in hot paths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 196ed2b commit d4de73a

File tree

3 files changed

+29
-5
lines changed

3 files changed

+29
-5
lines changed

packages/next/src/server/app-render/app-render.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,7 +2117,8 @@ async function renderToHTMLOrFlightImpl(
21172117

21182118
if (process.env.__NEXT_DEV_SERVER && setIsrStatus && !cacheComponents) {
21192119
// Reset the ISR status at start of request.
2120-
const { pathname } = new URL(req.url || '/', 'http://n')
2120+
const reqUrl = req.url || '/'
2121+
const pathname = reqUrl.split('?', 1)[0]
21212122
setIsrStatus(
21222123
pathname,
21232124
// Only pages using the Node runtime can use ISR, Edge is always dynamic.
@@ -2396,7 +2397,8 @@ async function renderToHTMLOrFlightImpl(
23962397
isNodeNextRequest(req)
23972398
) {
23982399
req.originalRequest.on('end', () => {
2399-
const { pathname } = new URL(req.url || '/', 'http://n')
2400+
const reqUrl = req.url || '/'
2401+
const pathname = reqUrl.split('?', 1)[0]
24002402
const isStatic = !requestStore.usedDynamic && !workStore.forceDynamic
24012403
setIsrStatus(pathname, isStatic)
24022404
})

packages/next/src/server/base-server.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-
6565
import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'
6666
import * as Log from '../build/output/log'
6767
import { getServerUtils } from './server-utils'
68+
import { getQueryParam } from './internal-utils'
6869
import isError, { getProperError } from '../lib/is-error'
6970
import {
7071
addRequestMeta,
@@ -2090,9 +2091,7 @@ export default abstract class Server<
20902091
)
20912092
const actualHash =
20922093
getRequestMeta(req, 'cacheBustingSearchParam') ??
2093-
new URL(req.url || '', 'http://localhost').searchParams.get(
2094-
NEXT_RSC_UNION_QUERY
2095-
)
2094+
getQueryParam(req.url || '', NEXT_RSC_UNION_QUERY)
20962095

20972096
if (expectedHash !== actualHash) {
20982097
// The hash sent by the client does not match the expected value.

packages/next/src/server/internal-utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,29 @@ export function stripInternalQueries(query: NextParsedUrlQuery) {
1010
}
1111
}
1212

13+
/**
14+
* Extract a single query parameter value from a URL string without
15+
* constructing a URL object. Returns null if the parameter is not found.
16+
*/
17+
export function getQueryParam(url: string, name: string): string | null {
18+
const qIdx = url.indexOf('?')
19+
if (qIdx === -1) return null
20+
const search = url.substring(qIdx + 1)
21+
const prefix = name + '='
22+
let start = 0
23+
while (start <= search.length) {
24+
const ampIdx = search.indexOf('&', start)
25+
const end = ampIdx === -1 ? search.length : ampIdx
26+
const segment = search.substring(start, end)
27+
if (segment.startsWith(prefix)) {
28+
return decodeURIComponent(segment.substring(prefix.length))
29+
}
30+
if (segment === name) return ''
31+
start = end + 1
32+
}
33+
return null
34+
}
35+
1336
export function stripInternalSearchParams<T extends string | URL>(url: T): T {
1437
const isStringUrl = typeof url === 'string'
1538
const instance = isStringUrl ? new URL(url) : (url as URL)

0 commit comments

Comments
 (0)