Repository: vercel/next.js
Pull request: Don't insert build id comment in HTML

The build id comment in HTML documents was added in  https://github.com/vercel/next.js/pull/81247
But it was removed again to detect redirects using a HEAD request instead in https://github.com/vercel/next.js/pull/85910
Meaning the build id comment is completely unused now, so no need to insert it

see https://vercel.slack.com/archives/C04KC8A53T7/p1770196901369359

Closes PACK-6846

Changed files:
filename: packages/next/src/server/app-render/app-render.tsx
patch: @@ -2998,8 +2998,6 @@ async function renderToStream(
           formState
         ),
         isStaticGeneration: generateStaticHTML,
-        isBuildTimePrerendering: ctx.workStore.isBuildTimePrerendering === true,
-        buildId: ctx.workStore.buildId,
         deploymentId: ctx.sharedContext.deploymentId,
         getServerInsertedHTML,
         getServerInsertedMetadata,
@@ -3167,9 +3165,6 @@ async function renderToStream(
             formState
           ),
           isStaticGeneration: generateStaticHTML,
-          isBuildTimePrerendering:
-            ctx.workStore.isBuildTimePrerendering === true,
-          buildId: ctx.workStore.buildId,
           deploymentId: ctx.sharedContext.deploymentId,
           getServerInsertedHTML: makeGetServerInsertedHTML({
             polyfills,
@@ -5000,9 +4995,6 @@ async function prerenderToStream(
             ),
             getServerInsertedHTML,
             getServerInsertedMetadata,
-            isBuildTimePrerendering:
-              ctx.workStore.isBuildTimePrerendering === true,
-            buildId: ctx.workStore.buildId,
             deploymentId: ctx.sharedContext.deploymentId,
           })
         } else {
@@ -5015,9 +5007,6 @@ async function prerenderToStream(
             ),
             getServerInsertedHTML,
             getServerInsertedMetadata,
-            isBuildTimePrerendering:
-              ctx.workStore.isBuildTimePrerendering === true,
-            buildId: ctx.workStore.buildId,
             deploymentId: ctx.sharedContext.deploymentId,
           })
         }
@@ -5278,9 +5267,6 @@ async function prerenderToStream(
             ),
             getServerInsertedHTML,
             getServerInsertedMetadata,
-            isBuildTimePrerendering:
-              ctx.workStore.isBuildTimePrerendering === true,
-            buildId: ctx.workStore.buildId,
             deploymentId: ctx.sharedContext.deploymentId,
           }),
           dynamicAccess: dynamicTracking.dynamicAccesses,
@@ -5377,9 +5363,6 @@ async function prerenderToStream(
             formState
           ),
           isStaticGeneration: true,
-          isBuildTimePrerendering:
-            ctx.workStore.isBuildTimePrerendering === true,
-          buildId: ctx.workStore.buildId,
           getServerInsertedHTML,
           getServerInsertedMetadata,
           deploymentId: ctx.sharedContext.deploymentId,
@@ -5556,9 +5539,6 @@ async function prerenderToStream(
             formState
           ),
           isStaticGeneration: true,
-          isBuildTimePrerendering:
-            ctx.workStore.isBuildTimePrerendering === true,
-          buildId: ctx.workStore.buildId,
           getServerInsertedHTML: makeGetServerInsertedHTML({
             polyfills,
             renderServerInsertedHTML,

filename: packages/next/src/server/stream-utils/node-web-streams-helper.ts
patch: @@ -14,7 +14,6 @@ import {
   removeFromUint8Array,
 } from './uint8array-helpers'
 import { MISSING_ROOT_TAGS_ERROR } from '../../shared/lib/errors/constants'
-import { insertBuildIdComment } from '../../shared/lib/segment-cache/output-export-prefetch-encoding'
 import {
   RSC_HEADER,
   NEXT_ROUTER_PREFETCH_HEADER,
@@ -235,32 +234,35 @@ export function createBufferedTransformStream(
   })
 }
 
-function createPrefetchCommentStream(
-  isBuildTimePrerendering: boolean,
-  buildId: string
-): TransformStream<Uint8Array, Uint8Array> {
-  // Insert an extra comment at the beginning of the HTML document. This must
-  // come after the DOCTYPE, which is inserted by React.
-  //
-  // The first chunk sent by React will contain the doctype. After that, we can
-  // pass through the rest of the chunks as-is.
-  let didTransformFirstChunk = false
-  return new TransformStream({
-    transform(chunk, controller) {
-      if (isBuildTimePrerendering && !didTransformFirstChunk) {
-        didTransformFirstChunk = true
-        const decoder = new TextDecoder('utf-8', { fatal: true })
-        const chunkStr = decoder.decode(chunk, {
-          stream: true,
-        })
-        const updatedChunkStr = insertBuildIdComment(chunkStr, buildId)
-        controller.enqueue(encoder.encode(updatedChunkStr))
-        return
-      }
-      controller.enqueue(chunk)
-    },
-  })
-}
+// TODO this is currently unused but once we add proper output:export support, it needs to be
+// revisited. See https://github.com/vercel/next.js/pull/89478 for more details
+//
+// function createPrefetchCommentStream(
+//   isBuildTimePrerendering: boolean,
+//   buildId: string
+// ): TransformStream<Uint8Array, Uint8Array> {
+//   // Insert an extra comment at the beginning of the HTML document. This must
+//   // come after the DOCTYPE, which is inserted by React.
+//   //
+//   // The first chunk sent by React will contain the doctype. After that, we can
+//   // pass through the rest of the chunks as-is.
+//   let didTransformFirstChunk = false
+//   return new TransformStream({
+//     transform(chunk, controller) {
+//       if (isBuildTimePrerendering && !didTransformFirstChunk) {
+//         didTransformFirstChunk = true
+//         const decoder = new TextDecoder('utf-8', { fatal: true })
+//         const chunkStr = decoder.decode(chunk, {
+//           stream: true,
+//         })
+//         const updatedChunkStr = insertBuildIdComment(chunkStr, buildId)
+//         controller.enqueue(encoder.encode(updatedChunkStr))
+//         return
+//       }
+//       controller.enqueue(chunk)
+//     },
+//   })
+// }
 
 export function renderToInitialFizzStream({
   ReactDOMServer,
@@ -839,8 +841,6 @@ function chainTransformers<T>(
 export type ContinueStreamOptions = {
   inlinedDataStream: ReadableStream<Uint8Array> | undefined
   isStaticGeneration: boolean
-  isBuildTimePrerendering: boolean
-  buildId: string
   deploymentId: string | undefined
   getServerInsertedHTML: () => Promise<string>
   getServerInsertedMetadata: () => Promise<string>
@@ -857,8 +857,6 @@ export async function continueFizzStream(
     suffix,
     inlinedDataStream,
     isStaticGeneration,
-    isBuildTimePrerendering,
-    buildId,
     deploymentId,
     getServerInsertedHTML,
     getServerInsertedMetadata,
@@ -881,9 +879,6 @@ export async function continueFizzStream(
     // Buffer everything to avoid flushing too frequently
     createBufferedTransformStream(),
 
-    // Add build id comment to start of the HTML document (in export mode)
-    createPrefetchCommentStream(isBuildTimePrerendering, buildId),
-
     // Insert data-dpl-id attribute on the html tag
     deploymentId ? createHtmlDataDplIdTransformStream(deploymentId) : null,
 
@@ -944,8 +939,6 @@ type ContinueStaticPrerenderOptions = {
   inlinedDataStream: ReadableStream<Uint8Array>
   getServerInsertedHTML: () => Promise<string>
   getServerInsertedMetadata: () => Promise<string>
-  isBuildTimePrerendering: boolean
-  buildId: string
   deploymentId: string | undefined
 }
 
@@ -955,16 +948,13 @@ export async function continueStaticPrerender(
     inlinedDataStream,
     getServerInsertedHTML,
     getServerInsertedMetadata,
-    isBuildTimePrerendering,
-    buildId,
     deploymentId,
   }: ContinueStaticPrerenderOptions
 ) {
   return chainTransformers(prerenderStream, [
     // Buffer everything to avoid flushing too frequently
     createBufferedTransformStream(),
     // Add build id comment to start of the HTML document (in export mode)
-    createPrefetchCommentStream(isBuildTimePrerendering, buildId),
     // Insert data-dpl-id attribute on the html tag
     deploymentId ? createHtmlDataDplIdTransformStream(deploymentId) : null,
     // Insert generated tags to head
@@ -984,8 +974,6 @@ export async function continueStaticFallbackPrerender(
     inlinedDataStream,
     getServerInsertedHTML,
     getServerInsertedMetadata,
-    isBuildTimePrerendering,
-    buildId,
     deploymentId,
   }: ContinueStaticPrerenderOptions
 ) {
@@ -995,8 +983,6 @@ export async function continueStaticFallbackPrerender(
   return chainTransformers(prerenderStream, [
     // Buffer everything to avoid flushing too frequently
     createBufferedTransformStream(),
-    // Add build id comment to start of the HTML document (in export mode)
-    createPrefetchCommentStream(isBuildTimePrerendering, buildId),
     // Insert data-dpl-id attribute on the html tag
     deploymentId ? createHtmlDataDplIdTransformStream(deploymentId) : null,
     // Insert generated tags to head

filename: packages/next/src/shared/lib/segment-cache/output-export-prefetch-encoding.ts
patch: @@ -1,48 +0,0 @@
-// In output: export mode, the build id is added to the start of the HTML
-// document, directly after the doctype declaration. During a prefetch, the
-// client performs a range request to get the build id, so it can check whether
-// the target page belongs to the same build.
-//
-// The first 64 bytes of the document are requested. The exact number isn't
-// too important; it must be larger than the build id + doctype + closing and
-// ending comment markers, but it doesn't need to match the end of the
-// comment exactly.
-//
-// Build ids are 21 bytes long in the default implementation, though this
-// can be overridden in the Next.js config. For the purposes of this check,
-// it's OK to only match the start of the id, so we'll truncate it if exceeds
-// a certain length.
-
-const DOCTYPE_PREFIX = '<!DOCTYPE html>' // 15 bytes
-const MAX_BUILD_ID_LENGTH = 24
-
-function escapeBuildId(buildId: string) {
-  // If the build id is longer than the given limit, it's OK for our purposes
-  // to only match the beginning.
-  const truncated = buildId.slice(0, MAX_BUILD_ID_LENGTH)
-  // Replace hyphens with underscores so it doesn't break the HTML comment.
-  // (Unlikely, but if this did happen it would break the whole document.)
-  return truncated.replace(/-/g, '_')
-}
-
-export function insertBuildIdComment(originalHtml: string, buildId: string) {
-  if (
-    // Skip if the build id contains a closing comment marker.
-    buildId.includes('-->') ||
-    // React always inserts a doctype at the start of the document. Skip if it
-    // isn't present. Shouldn't happen; suggests an issue elsewhere.
-    !originalHtml.startsWith(DOCTYPE_PREFIX)
-  ) {
-    // Return the original HTML unchanged. This means the document will not
-    // be prefetched.
-    // TODO: The build id comment is currently only used during prefetches, but
-    // if we eventually use this mechanism for regular navigations, we may need
-    // to error during build if we fail to insert it for some reason.
-    return originalHtml
-  }
-  // The comment must be inserted after the doctype.
-  return originalHtml.replace(
-    DOCTYPE_PREFIX,
-    DOCTYPE_PREFIX + '<!--' + escapeBuildId(buildId) + '-->'
-  )
-}