Skip to content

Commit 159ff24

Browse files
benfavreclaude
andcommitted
perf(server): avoid delete on hot-path objects to preserve V8 hidden classes
Using `delete` on object properties changes the V8 hidden class (object "shape"), forcing the engine into dictionary/slow-property mode and disabling inline caching for all subsequent property accesses on that object. TanStack observed >50% CPU time reduction in affected methods after switching to `undefined` assignment. This commit makes three targeted changes in the SSR request hot path: 1. **`parsedUrl.query` clear-and-replace** (base-server.ts): Instead of loop-deleting every key then using `Object.assign`, reassign `parsedUrl.query` to a fresh object. This avoids N shape transitions and is semantically equivalent since `parsedUrl.query` is accessed via the property, not by captured reference. 2. **`req.headers[NEXT_URL]` removal** (base-server.ts): Use `= undefined` instead of `delete`. Downstream code accesses this header by value (`headers[NEXT_URL]`), never with the `in` operator, so `undefined` is semantically equivalent. 3. **`removeRequestMeta`** (request-meta.ts): Use `= undefined` instead of `delete`. All consumers use `getRequestMeta(req, key)` which returns `meta[key]` — `undefined` is indistinguishable from a missing key. The request meta object persists for the entire request lifetime, so preserving its shape benefits all downstream accesses. Several other `delete` sites were analyzed and intentionally left unchanged because they affect objects that are later serialized via `querystring.stringify` or `url.format`, where `undefined` values would leak as empty query parameters (e.g., `_rsc=`). Reference: https://tanstack.com/blog/tanstack-start-v1-5x-faster-ssr Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 196ed2b commit 159ff24

File tree

2 files changed

+12
-9
lines changed

2 files changed

+12
-9
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,14 +1541,12 @@ export default abstract class Server<
15411541
}
15421542
parsedUrl.pathname = normalizeResult.pathname
15431543

1544-
for (const key of Object.keys(parsedUrl.query)) {
1545-
delete parsedUrl.query[key]
1546-
}
1544+
// Replace the query object instead of loop-deleting every key.
1545+
// Using `delete` on object properties mutates V8 hidden classes,
1546+
// which de-optimises inline caches on downstream property accesses.
1547+
// See: https://tanstack.com/blog/tanstack-start-v1-5x-faster-ssr
15471548
const invokeQuery = getRequestMeta(req, 'invokeQuery')
1548-
1549-
if (invokeQuery) {
1550-
Object.assign(parsedUrl.query, invokeQuery)
1551-
}
1549+
parsedUrl.query = invokeQuery ? { ...invokeQuery } : {}
15521550

15531551
finished = await this.normalizeAndAttachMetadata(req, res, parsedUrl)
15541552
if (finished) return
@@ -2018,7 +2016,9 @@ export default abstract class Server<
20182016
if (!addedNextUrlToVary) {
20192017
// Remove `Next-URL` from the request headers we determined it wasn't necessary to include in the Vary header.
20202018
// This is to avoid any dependency on the `Next-URL` header being present when preparing the response.
2021-
delete req.headers[NEXT_URL]
2019+
// Use undefined assignment instead of delete to preserve V8 hidden class.
2020+
// Downstream code checks this header by value, not with the `in` operator.
2021+
req.headers[NEXT_URL] = undefined
20222022
}
20232023
}
20242024

packages/next/src/server/request-meta.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,10 @@ export function removeRequestMeta<K extends keyof RequestMeta>(
372372
key: K
373373
) {
374374
const meta = getRequestMeta(request)
375-
delete meta[key]
375+
// Set to undefined instead of delete to preserve V8 hidden class.
376+
// All consumers use getRequestMeta(req, key) which returns meta[key],
377+
// so undefined is semantically equivalent to a missing key.
378+
meta[key] = undefined as RequestMeta[K]
376379
return setRequestMeta(request, meta)
377380
}
378381

0 commit comments

Comments
 (0)