diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 04e21ac68a4..68bd6bd9ab0 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -160,6 +160,7 @@ import { hasCustomExportOutput } from '../export/utils' import { interopDefault } from '../lib/interop-default' import { formatDynamicImportPath } from '../lib/format-dynamic-import-path' import { isDefaultRoute } from '../lib/is-default-route' +import { isInterceptionRouteAppPath } from '../server/future/helpers/interception-routes' interface ExperimentalBypassForInfo { experimentalBypassFor?: RouteHas[] @@ -1857,7 +1858,7 @@ export default async function build( ) } else { // If this route can be partially pre-rendered, then - // mark it as such and mark it that it can be + // mark it as such and mark that it can be // generated server-side. if (workerResult.isPPR) { isPPR = workerResult.isPPR @@ -1885,6 +1886,8 @@ export default async function build( } const appConfig = workerResult.appConfig || {} + const isInterceptionRoute = + isInterceptionRouteAppPath(page) if (appConfig.revalidate !== 0) { const isDynamic = isDynamicRoute(page) const hasGenerateStaticParams = @@ -1900,26 +1903,29 @@ export default async function build( ) } - if ( - // Mark the app as static if: - // - It has no dynamic param - // - It doesn't have generateStaticParams but `dynamic` is set to - // `error` or `force-static` - !isDynamic - ) { - appStaticPaths.set(originalAppPath, [page]) - appStaticPathsEncoded.set(originalAppPath, [page]) - isStatic = true - } else if ( - isDynamic && - !hasGenerateStaticParams && - (appConfig.dynamic === 'error' || - appConfig.dynamic === 'force-static') - ) { - appStaticPaths.set(originalAppPath, []) - appStaticPathsEncoded.set(originalAppPath, []) - isStatic = true - isPPR = false + // Mark the app as static if: + // - It's not an interception route (these currently depend on request headers and cannot be computed at build) + // - It has no dynamic param + // - It doesn't have generateStaticParams but `dynamic` is set to + // `error` or `force-static` + if (!isInterceptionRoute) { + if (!isDynamic) { + appStaticPaths.set(originalAppPath, [page]) + appStaticPathsEncoded.set(originalAppPath, [ + page, + ]) + isStatic = true + } else if ( + isDynamic && + !hasGenerateStaticParams && + (appConfig.dynamic === 'error' || + appConfig.dynamic === 'force-static') + ) { + appStaticPaths.set(originalAppPath, []) + appStaticPathsEncoded.set(originalAppPath, []) + isStatic = true + isPPR = false + } } } @@ -1936,7 +1942,8 @@ export default async function build( !isStatic && !isAppRouteRoute(originalAppPath) && !isDynamicRoute(originalAppPath) && - !isPPR + !isPPR && + !isInterceptionRoute ) { appPrefetchPaths.set(originalAppPath, page) } diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index 91818fd3d71..63815f59d35 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -83,6 +83,7 @@ import { isAppRouteRouteModule } from '../server/future/route-modules/checks' import { interopDefault } from '../lib/interop-default' import type { PageExtensions } from './page-extensions-type' import { formatDynamicImportPath } from '../lib/format-dynamic-import-path' +import { isInterceptionRouteAppPath } from '../server/future/helpers/interception-routes' export type ROUTER_TYPE = 'pages' | 'app' @@ -1744,7 +1745,7 @@ export async function isPageStatic({ isStatic = true } - // When PPR is enabled, any route may contain or be completely static, so + // When PPR is enabled, any route may be completely static, so // mark this route as static. let isPPR = false if (ppr && routeModule.definition.kind === RouteKind.APP_PAGE) { @@ -1752,6 +1753,12 @@ export async function isPageStatic({ isStatic = true } + // interception routes depend on `Next-URL` and `Next-Router-State-Tree` request headers and thus cannot be prerendered + if (isInterceptionRouteAppPath(page)) { + isStatic = false + isPPR = false + } + return { isStatic, isPPR, diff --git a/test/e2e/app-dir/interception-route-groups/interception-route-groups.test.ts b/test/e2e/app-dir/interception-route-groups/interception-route-groups.test.ts index 562d8b5bb13..548af8faa8f 100644 --- a/test/e2e/app-dir/interception-route-groups/interception-route-groups.test.ts +++ b/test/e2e/app-dir/interception-route-groups/interception-route-groups.test.ts @@ -127,27 +127,12 @@ createNextDescribe( app: new FileRef(path.join(__dirname, 'app')), }, }, - ({ next, isNextStart }) => { - if (process.env.__NEXT_EXPERIMENTAL_PPR === 'true' && isNextStart) { - // The PPR prefetch will 404 since it'll request the full page (which won't exist, since the intercepted route - // has no default). The default router behavior if a prefetch fails is to trigger an MPA navigation - it('should render the non-intercepted page on navigation', async () => { - const browser = await next.browser('/') - - await browser.elementByCss('[href="/photos/1"]').click() - await check(() => browser.elementById('slot').text(), /@slot default/) - await check( - () => browser.elementById('children').text(), - /Photo Page \(non-intercepted\) 1/ - ) - }) - } else { - it('should use the default fallback (a 404) if there is no custom default page', async () => { - const browser = await next.browser('/') - - await browser.elementByCss('[href="/photos/1"]').click() - await check(() => browser.elementByCss('body').text(), /404/) - }) - } + ({ next }) => { + it('should use the default fallback (a 404) if there is no custom default page', async () => { + const browser = await next.browser('/') + + await browser.elementByCss('[href="/photos/1"]').click() + await check(() => browser.elementByCss('body').text(), /404/) + }) } ) diff --git a/test/e2e/app-dir/interception-route-prefetch-cache/app/baz/@modal/(.)modal/page.tsx b/test/e2e/app-dir/interception-route-prefetch-cache/app/baz/@modal/(.)modal/page.tsx new file mode 100644 index 00000000000..c7dca9d0375 --- /dev/null +++ b/test/e2e/app-dir/interception-route-prefetch-cache/app/baz/@modal/(.)modal/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return