Skip to content

Commit

Permalink
feat: enable incremental adoption of ppr
Browse files Browse the repository at this point in the history
  • Loading branch information
wyattjoh committed Mar 28, 2024
1 parent ae61e7e commit b4de9cc
Show file tree
Hide file tree
Showing 30 changed files with 378 additions and 276 deletions.
2 changes: 1 addition & 1 deletion packages/next/src/build/analysis/get-page-static-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ async function tryToReadFile(filePath: string, shouldThrow: boolean) {

export function getMiddlewareMatchers(
matcherOrMatchers: unknown,
nextConfig: NextConfig
nextConfig: Pick<NextConfig, 'basePath' | 'i18n'>
): MiddlewareMatcher[] {
let matchers: unknown[] = []
if (Array.isArray(matcherOrMatchers)) {
Expand Down
23 changes: 8 additions & 15 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ import type { Entrypoints } from '../server/dev/turbopack/types'
import { buildCustomRoute } from '../lib/build-custom-route'
import { createProgress } from './progress'
import { generateEncryptionKeyBase64 } from '../server/app-render/encryption-utils'
import { parsePPRConfig } from '../server/lib/experimental/ppr'

interface ExperimentalBypassForInfo {
experimentalBypassFor?: RouteHas[]
Expand Down Expand Up @@ -715,6 +716,8 @@ export default async function build(
)
)

const ppr = parsePPRConfig(config.experimental?.ppr)

process.env.NEXT_DEPLOYMENT_ID = config.deploymentId || ''
NextBuildContext.config = config

Expand Down Expand Up @@ -1752,7 +1755,6 @@ export default async function build(
minimalMode: ciEnvironment.hasNextSupport,
allowedRevalidateHeaderKeys:
config.experimental.allowedRevalidateHeaderKeys,
experimental: { ppr: config.experimental.ppr === true },
})

incrementalCacheIpcPort = cacheInitialization.ipcPort
Expand Down Expand Up @@ -1830,7 +1832,7 @@ export default async function build(
locales: config.i18n?.locales,
defaultLocale: config.i18n?.defaultLocale,
nextConfigOutput: config.output,
ppr: config.experimental.ppr === true,
pprConfig: config.experimental.ppr,
})
)

Expand Down Expand Up @@ -2043,7 +2045,7 @@ export default async function build(
: config.experimental.isrFlushToDisk,
maxMemoryCacheSize: config.cacheMaxMemorySize,
nextConfigOutput: config.output,
ppr: config.experimental.ppr === true,
pprConfig: config.experimental.ppr,
})
}
)
Expand Down Expand Up @@ -2118,7 +2120,6 @@ export default async function build(
])
isStatic = true
} else if (
isDynamic &&
!hasGenerateStaticParams &&
(appConfig.dynamic === 'error' ||
appConfig.dynamic === 'force-static')
Expand Down Expand Up @@ -2573,13 +2574,6 @@ export default async function build(
})
})

// Ensure we don't generate explicit app prefetches while in PPR.
if (config.experimental.ppr && appPrefetchPaths.size > 0) {
throw new Error(
"Invariant: explicit app prefetches shouldn't generated with PPR"
)
}

for (const [originalAppPath, page] of appPrefetchPaths) {
defaultMap[page] = {
page: originalAppPath,
Expand Down Expand Up @@ -2691,9 +2685,7 @@ export default async function build(
// When this is an app page and PPR is enabled, the route supports
// partial pre-rendering.
const experimentalPPR =
!isRouteHandler && config.experimental.ppr === true
? true
: undefined
!isRouteHandler && ppr.isSupported(page) ? true : undefined

// this flag is used to selectively bypass the static cache and invoke the lambda directly
// to enable server actions on static routes
Expand Down Expand Up @@ -2780,7 +2772,8 @@ export default async function build(

finalPrerenderRoutes[route] = {
...routeMeta,
experimentalPPR,
experimentalPPR:
!isRouteHandler && ppr.isSupported(page) ? true : undefined,
experimentalBypassFor: bypassFor,
initialRevalidateSeconds: revalidate,
srcRoute: page,
Expand Down
32 changes: 16 additions & 16 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { NextConfig, NextConfigComplete } from '../server/config-shared'
import type {
ExperimentalPPRConfig,
NextConfig,
NextConfigComplete,
} from '../server/config-shared'
import type { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
import type { AssetBinding } from './webpack/loaders/get-module-build-info'
import type {
Expand Down Expand Up @@ -87,6 +91,7 @@ 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'
import { parsePPRConfig } from '../server/lib/experimental/ppr'

export type ROUTER_TYPE = 'pages' | 'app'

Expand Down Expand Up @@ -1306,7 +1311,6 @@ export async function buildAppStaticPaths({
requestHeaders,
maxMemoryCacheSize,
fetchCacheKeyPrefix,
ppr,
ComponentMod,
}: {
dir: string
Expand All @@ -1319,7 +1323,6 @@ export async function buildAppStaticPaths({
cacheHandler?: string
maxMemoryCacheSize?: number
requestHeaders: IncrementalCache['requestHeaders']
ppr: boolean
ComponentMod: AppPageModule
}) {
ComponentMod.patchFetch()
Expand Down Expand Up @@ -1354,7 +1357,6 @@ export async function buildAppStaticPaths({
CurCacheHandler: CacheHandler,
requestHeaders,
minimalMode: ciEnvironment.hasNextSupport,
experimental: { ppr },
})

return StaticGenerationAsyncStorageWrapper.wrap(
Expand All @@ -1366,8 +1368,6 @@ export async function buildAppStaticPaths({
incrementalCache,
supportsDynamicHTML: true,
isRevalidate: false,
// building static paths should never postpone
experimental: { ppr: false },
},
},
async () => {
Expand Down Expand Up @@ -1481,7 +1481,7 @@ export async function isPageStatic({
isrFlushToDisk,
maxMemoryCacheSize,
cacheHandler,
ppr,
pprConfig,
}: {
dir: string
page: string
Expand All @@ -1500,7 +1500,7 @@ export async function isPageStatic({
maxMemoryCacheSize?: number
cacheHandler?: string
nextConfigOutput: 'standalone' | 'export'
ppr: boolean
pprConfig: ExperimentalPPRConfig | undefined
}): Promise<{
isPPR?: boolean
isStatic?: boolean
Expand Down Expand Up @@ -1577,13 +1577,15 @@ export async function isPageStatic({
const routeModule: RouteModule =
componentsResult.ComponentMod?.routeModule

let supportsPPR = false
const ppr = parsePPRConfig(pprConfig)

if (pageType === 'app') {
if (ppr && routeModule.definition.kind === RouteKind.APP_PAGE) {
supportsPPR = true
}
// If this is an app page, it supports PPR if the configuration allows
// this page.
const supportsPPR =
routeModule.definition.kind === RouteKind.APP_PAGE &&
ppr.isSupported(page)

if (pageType === 'app') {
const ComponentMod: AppPageModule = componentsResult.ComponentMod

isClientComponent = isClientReference(componentsResult.ComponentMod)
Expand Down Expand Up @@ -1672,7 +1674,6 @@ export async function isPageStatic({
isrFlushToDisk,
maxMemoryCacheSize,
cacheHandler,
ppr,
ComponentMod,
}))
}
Expand Down Expand Up @@ -1750,9 +1751,8 @@ export async function isPageStatic({

// When PPR is enabled, any route may be completely static, so
// mark this route as static.
let isPPR = false
let isPPR = supportsPPR
if (supportsPPR) {
isPPR = true
isStatic = true
}

Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/build/webpack/plugins/define-env-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
import type { MiddlewareMatcher } from '../../analysis/get-page-static-info'
import { webpack } from 'next/dist/compiled/webpack/webpack'
import { needsExperimentalReact } from '../../../lib/needs-experimental-react'
import { parsePPRConfig } from '../../../server/lib/experimental/ppr'

function errorIfEnvConflicted(config: NextConfigComplete, key: string) {
const isPrivateKey = /^(?:NODE_.+)|^(?:__.+)$/i.test(key)
Expand Down Expand Up @@ -165,7 +166,7 @@ export function getDefineEnv({
? 'nodejs'
: '',
'process.env.NEXT_MINIMAL': '',
'process.env.__NEXT_PPR': config.experimental.ppr === true,
'process.env.__NEXT_PPR': parsePPRConfig(config.experimental.ppr).enabled,
'process.env.NEXT_DEPLOYMENT_ID': config.deploymentId || false,
'process.env.__NEXT_FETCH_CACHE_KEY_PREFIX': fetchCacheKeyPrefix ?? '',
'process.env.__NEXT_MIDDLEWARE_MATCHERS': middlewareMatchers ?? [],
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/export/helpers/create-incremental-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function createIncrementalCache({
distDir,
dir,
enabledDirectories,
experimental,
pprEnabled,
flushToDisk,
}: {
cacheHandler?: string
Expand All @@ -23,7 +23,7 @@ export async function createIncrementalCache({
distDir: string
dir: string
enabledDirectories: NextEnabledDirectories
experimental: { ppr: boolean }
pprEnabled?: boolean
flushToDisk?: boolean
}) {
// Custom cache handler overrides.
Expand Down Expand Up @@ -60,7 +60,7 @@ export async function createIncrementalCache({
serverDistDir: path.join(distDir, 'server'),
CurCacheHandler: CacheHandler,
minimalMode: hasNextSupport,
experimental,
pprEnabled,
})

;(globalThis as any).__incrementalCache = incrementalCache
Expand Down
4 changes: 3 additions & 1 deletion packages/next/src/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { formatManifest } from '../build/manifests/formatter/format-manifest'
import { validateRevalidate } from '../server/lib/patch-fetch'
import { TurborepoAccessTraceResult } from '../build/turborepo-access-trace'
import { createProgress } from '../build/progress'
import { parsePPRConfig } from '../server/lib/experimental/ppr'

export class ExportError extends Error {
code = 'NEXT_EXPORT_ERROR'
Expand Down Expand Up @@ -421,7 +422,7 @@ export async function exportAppImpl(
strictNextHead: !!nextConfig.experimental.strictNextHead,
deploymentId: nextConfig.deploymentId,
experimental: {
ppr: nextConfig.experimental.ppr === true,
pprEnabled: parsePPRConfig(nextConfig.experimental.ppr).enabled,
missingSuspenseWithCSRBailout:
nextConfig.experimental.missingSuspenseWithCSRBailout === true,
swrDelta: nextConfig.experimental.swrDelta,
Expand Down Expand Up @@ -619,6 +620,7 @@ export async function exportAppImpl(
cacheHandler: nextConfig.cacheHandler,
enableExperimentalReact: needsExperimentalReact(nextConfig),
enabledDirectories,
pprConfig: nextConfig.experimental.ppr,
})
})

Expand Down
16 changes: 11 additions & 5 deletions packages/next/src/export/routes/app-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ export async function exportAppPage(
const { flightData, revalidate = false, postponed, fetchTags } = metadata

// Ensure we don't postpone without having PPR enabled.
if (postponed && !renderOpts.experimental.ppr) {
if (
postponed &&
(!renderOpts.experimental.supportsPPR ||
!renderOpts.experimental.pprEnabled)
) {
throw new Error('Invariant: page postponed without PPR being enabled')
}

Expand Down Expand Up @@ -90,8 +94,9 @@ export async function exportAppPage(
}
// If PPR is enabled, we want to emit a prefetch rsc file for the page
// instead of the standard rsc. This is because the standard rsc will
// contain the dynamic data.
else if (renderOpts.experimental.ppr) {
// contain the dynamic data. We do this if any routes have PPR enabled so
// that the cache read/write is the same.
else if (renderOpts.experimental.pprEnabled) {
// If PPR is enabled, we should emit the flight data as the prefetch
// payload.
await fileWriter(
Expand All @@ -112,7 +117,7 @@ export async function exportAppPage(

// When PPR is enabled, we should grab the headers from the mocked response
// and add it to the headers.
if (renderOpts.experimental.ppr) {
if (renderOpts.experimental.supportsPPR) {
Object.assign(headers, res.getHeaders())
}

Expand All @@ -130,10 +135,11 @@ export async function exportAppPage(

const isParallelRoute = /\/@\w+/.test(page)
const isNonSuccessfulStatusCode = res.statusCode > 300

// When PPR is enabled, we don't always send 200 for routes that have been
// pregenerated, so we should grab the status code from the mocked
// response.
let status: number | undefined = renderOpts.experimental.ppr
let status: number | undefined = renderOpts.experimental.pprEnabled
? res.statusCode
: undefined

Expand Down
1 change: 0 additions & 1 deletion packages/next/src/export/routes/app-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export async function exportAppRoute(
notFoundRoutes: [],
},
renderOpts: {
experimental: { ppr: false },
originalPathname: page,
nextExport: true,
supportsDynamicHTML: false,
Expand Down
7 changes: 6 additions & 1 deletion packages/next/src/export/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import type { LoadComponentsReturnType } from '../server/load-components'
import type { OutgoingHttpHeaders } from 'http'
import type AmpHtmlValidator from 'next/dist/compiled/amphtml-validator'
import type { FontConfig } from '../server/font-utils'
import type { ExportPathMap, NextConfigComplete } from '../server/config-shared'
import type {
ExperimentalPPRConfig,
ExportPathMap,
NextConfigComplete,
} from '../server/config-shared'
import type { Span } from '../trace'
import type { Revalidate } from '../server/lib/revalidate'
import type { NextEnabledDirectories } from '../server/base-server'
Expand Down Expand Up @@ -64,6 +68,7 @@ export interface ExportPageInput {
nextConfigOutput?: NextConfigComplete['output']
enableExperimentalReact?: boolean
enabledDirectories: NextEnabledDirectories
pprConfig: ExperimentalPPRConfig | undefined
}

export type ExportedPageFile = {
Expand Down
11 changes: 9 additions & 2 deletions packages/next/src/export/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
turborepoTraceAccess,
TurborepoAccessTraceResult,
} from '../build/turborepo-access-trace'
import { parsePPRConfig } from '../server/lib/experimental/ppr'

const envConfig = require('../shared/lib/runtime-config.external')

Expand Down Expand Up @@ -71,6 +72,7 @@ async function exportPageImpl(
ampValidatorPath,
trailingSlash,
enabledDirectories,
pprConfig,
} = input

if (enableExperimentalReact) {
Expand Down Expand Up @@ -220,6 +222,8 @@ async function exportPageImpl(

await fs.mkdir(baseDir, { recursive: true })

const ppr = parsePPRConfig(pprConfig)

// If the fetch cache was enabled, we need to create an incremental
// cache instance for this page.
const incrementalCache =
Expand All @@ -231,8 +235,7 @@ async function exportPageImpl(
distDir,
dir,
enabledDirectories,
// PPR is not available for Pages.
experimental: { ppr: false },
pprEnabled: ppr.enabled,
// skip writing to disk in minimal mode for now, pending some
// changes to better support it
flushToDisk: !hasNextSupport,
Expand Down Expand Up @@ -271,6 +274,10 @@ async function exportPageImpl(
locale,
supportsDynamicHTML: false,
originalPathname: page,
experimental: {
...input.renderOpts.experimental,
supportsPPR: isAppDir && ppr.isSupported(page),
},
}

if (hasNextSupport) {
Expand Down
Loading

0 comments on commit b4de9cc

Please sign in to comment.