Fix: Do not force BLOCKING_STATIC_RENDER for DOM bots (#82427)

The issue was that DOM bots with PPR-enabled routes were being forced to
use BLOCKING_STATIC_RENDER mode, which prevented them from using the
fallback cache lookup mechanism. This caused them to generate new
content with isRevalidating: true, which blocks postponed data and
triggers the Math.random() static generation bailout error.

For HTML bots, they keep using BLOCKING_STATIC_RENDER because SSG was
turned off for them and won't see the SSG bailout error. For DOM bots,
they should behave like a normal user.

Fixes NAR-273
This commit is contained in:
Jude Gao
2025-08-08 13:03:27 -04:00
committed by GitHub
parent dcece12928
commit 6797a087e5
5 changed files with 96 additions and 3 deletions
@@ -653,11 +653,13 @@ export async function handler(
fallbackMode = parseFallbackField(prerenderInfo.fallback)
}
// When serving a bot request, we want to serve a blocking render and not
// the prerendered page. This ensures that the correct content is served
// When serving a HTML bot request, we want to serve a blocking render and
// not the prerendered page. This ensures that the correct content is served
// to the bot in the head.
if (fallbackMode === FallbackMode.PRERENDER && isBot(userAgent)) {
fallbackMode = FallbackMode.BLOCKING_STATIC_RENDER
if (!isRoutePPREnabled || isHtmlBot) {
fallbackMode = FallbackMode.BLOCKING_STATIC_RENDER
}
}
if (previousCacheEntry?.isStale === -1) {
@@ -0,0 +1,29 @@
import React from 'react'
import type { Metadata } from 'next'
export async function generateMetadata(): Promise<Metadata> {
await new Promise((resolve) => setTimeout(resolve, 1000)) // Simulate a delay
return {
title: 'Home',
description: 'Welcome to the home page',
}
}
export default async function Home({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
// Math.random() will cause SSG_BAILOUT if static generation runs
// Bots should bypass static generation in PPR to avoid this error
const randomValue = Math.random()
return (
<>
<h1>{slug}</h1>
<p>{randomValue}</p>
</>
)
}
@@ -0,0 +1,22 @@
import React, { Suspense } from 'react'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'PPR Bot SSG Bypass Test',
description:
'Test bot static generation bypass with PPR and cache components',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Suspense>{children}</Suspense>
</body>
</html>
)
}
@@ -0,0 +1,32 @@
import { nextTestSetup } from 'e2e-utils'
describe('cache-components PPR bot static generation bypass', () => {
const { next } = nextTestSetup({
files: __dirname,
})
it('should bypass static generation for DOM bot requests to avoid SSG_BAILOUT', async () => {
const res = await next.fetch('/foo', {
headers: {
'user-agent': 'Googlebot',
},
})
// With cache components + PPR enabled, DOM bots should behave like regular users
// and use the fallback cache mechanism. This allows them to handle dynamic content
// like Math.random() without triggering SSG_BAILOUT errors.
expect(res.status).toBe(200)
// Verify that the response contains the page content
const html = await res.text()
// Check that the page rendered successfully
// With PPR, content is streamed via script tags
expect(html).toContain('\\"children\\":\\"foo\\"')
// Verify Math.random() was executed (check for a decimal number in the streamed content)
expect(html).toMatch(/\\"children\\":0\.\d+/)
// With PPR, content is streamed, but the important thing is that
// the page rendered without a 500 error
})
})
@@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
cacheComponents: true,
},
}
module.exports = nextConfig