Skip to content

Commit

Permalink
Introduce dynamic IO experimental configuration
Browse files Browse the repository at this point in the history
In this mode prerenders must complete in a single render Task. When PPR is off if a prerender is incomplete after a single Render Task that page is considered dynamic. When PPR is on if a prerender is incomplete after a single Render Task then all the branches that are incomplete will postpone triggering the nearest parent suspense boundary.

The Dynamic APIs that bail out of prerendering will no longer trigger a postpone via a throw but will instead abort the render synchronously. This is very aggressive and to make this mode useful we intend to alter these dynamic APIs in a way that will allow their use to only exclude their local sub-tree.

If you experiment with this mode expect that many of your previously static pages will become dynamic and your mostly static PPR prerenders will become empty.
  • Loading branch information
gnoff committed Aug 13, 2024
1 parent 8f9b736 commit 728b140
Show file tree
Hide file tree
Showing 70 changed files with 3,004 additions and 465 deletions.
9 changes: 7 additions & 2 deletions packages/next/src/build/templates/app-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,18 @@ const routeModule = new AppRouteRouteModule({
// Pull out the exports that we need to expose from the module. This should
// be eliminated when we've moved the other routes to the new format. These
// are used to hook into the route.
const { requestAsyncStorage, staticGenerationAsyncStorage, serverHooks } =
routeModule
const {
requestAsyncStorage,
staticGenerationAsyncStorage,
prerenderAsyncStorage,
serverHooks,
} = routeModule

function patchFetch() {
return _patchFetch({
staticGenerationAsyncStorage,
requestAsyncStorage,
prerenderAsyncStorage,
})
}

Expand Down
1 change: 1 addition & 0 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,7 @@ export async function buildAppStaticPaths({
isRevalidate: false,
experimental: {
after: false,
dynamicIO: false,
},
},
},
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ async function exportAppImpl(
clientTraceMetadata: nextConfig.experimental.clientTraceMetadata,
swrDelta: nextConfig.swrDelta,
after: nextConfig.experimental.after ?? false,
dynamicIO: nextConfig.experimental.dynamicIO ?? false,
},
reactMaxHeadersLength: nextConfig.reactMaxHeadersLength,
}
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/export/routes/app-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function exportAppRoute(
distDir: string,
htmlFilepath: string,
fileWriter: FileWriter,
experimental: Required<Pick<ExperimentalConfig, 'after'>>
experimental: Required<Pick<ExperimentalConfig, 'after' | 'dynamicIO'>>
): Promise<ExportRouteResult> {
// Ensure that the URL is absolute.
req.url = `http://localhost:3000${req.url}`
Expand Down
5 changes: 3 additions & 2 deletions packages/next/src/lib/metadata/metadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ export function createMetadataComponents({
errorType
)

// We construct this instrumented promise to allow React.use to synchronously unwrap
// it if it has already settled.
// We instrument the promise compatible with React. This isn't necessary but we can
// perform a similar trick in synchronously unwrapping in the outlet component to avoid
// ticking a new microtask unecessarily
const metadataReady: Promise<void> & { status: string; value: unknown } =
pendingMetadata.then(
([error]) => {
Expand Down
15 changes: 15 additions & 0 deletions packages/next/src/server/app-render/app-render-prerender-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* This utility function is extracted to make it easier to find places where we are doing
* specific timing tricks to try to schedule work after React has rendered. This is especially
* import at the moment because Next.js uses the edge builds of React which use setTimeout to
* schedule work when you might expect that something like setImmediate would do the trick.
*
* Long term we should switch to the node versions of React rendering when possible and then
* update this to use setImmediate rather than setTimeout
*
* A shorter term work around would be to patch React to use setImmediate instead of setTimeout
* in the edge builds since this might also avoid setTimeout throttling.
*/
export async function waitAtLeastOneReactRenderTask() {
return new Promise((r) => setTimeout(r, 0))
}
Loading

0 comments on commit 728b140

Please sign in to comment.