diff --git a/.changeset/mighty-elephants-design.md b/.changeset/mighty-elephants-design.md new file mode 100644 index 000000000..2ffd5a3a0 --- /dev/null +++ b/.changeset/mighty-elephants-design.md @@ -0,0 +1,5 @@ +--- +"open-next": patch +--- + +patch asyncStorage for ISR and fetch diff --git a/packages/open-next/src/build/copyTracedFiles.ts b/packages/open-next/src/build/copyTracedFiles.ts index c16942cbd..22246e5f9 100644 --- a/packages/open-next/src/build/copyTracedFiles.ts +++ b/packages/open-next/src/build/copyTracedFiles.ts @@ -1,3 +1,5 @@ +import url from "node:url"; + import { copyFileSync, existsSync, @@ -14,6 +16,14 @@ import { NextConfig, PrerenderManifest } from "types/next-types"; import logger from "../logger.js"; +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +function copyPatchFile(outputDir: string) { + const patchFile = path.join(__dirname, "patch", "patchedAsyncStorage.js"); + const outputPatchFile = path.join(outputDir, "patchedAsyncStorage.cjs"); + copyFileSync(patchFile, outputPatchFile); +} + export async function copyTracedFiles( buildOutputPath: string, packagePath: string, @@ -205,6 +215,9 @@ See the docs for more information on how to bundle edge runtime functions. } }); + // Copy patch file + copyPatchFile(path.join(outputDir, packagePath)); + // TODO: Recompute all the files. // vercel doesn't seem to do it, but it seems wasteful to have all those files // we replace the pages-manifest.json with an empty one if we don't have a pages dir so that diff --git a/packages/open-next/src/build/patch/patchedAsyncStorage.ts b/packages/open-next/src/build/patch/patchedAsyncStorage.ts new file mode 100644 index 000000000..64bbf6a51 --- /dev/null +++ b/packages/open-next/src/build/patch/patchedAsyncStorage.ts @@ -0,0 +1,19 @@ +//@ts-nocheck + +const asyncStorage = require("next/dist/client/components/static-generation-async-storage.external.original"); + +const staticGenerationAsyncStorage = { + run: (store, cb, ...args) => + asyncStorage.staticGenerationAsyncStorage.run(store, cb, ...args), + getStore: () => { + const store = asyncStorage.staticGenerationAsyncStorage.getStore(); + if (store) { + store.isOnDemandRevalidate = + store.isOnDemandRevalidate && + !globalThis.__als.getStore().isISRRevalidation; + } + return store; + }, +}; + +exports.staticGenerationAsyncStorage = staticGenerationAsyncStorage; diff --git a/packages/open-next/src/core/createMainHandler.ts b/packages/open-next/src/core/createMainHandler.ts index c7d5dbece..faebad189 100644 --- a/packages/open-next/src/core/createMainHandler.ts +++ b/packages/open-next/src/core/createMainHandler.ts @@ -24,6 +24,7 @@ declare global { var __als: AsyncLocalStorage<{ requestId: string; pendingPromiseRunner: DetachedPromiseRunner; + isISRRevalidation?: boolean; }>; } diff --git a/packages/open-next/src/core/patchAsyncStorage.ts b/packages/open-next/src/core/patchAsyncStorage.ts new file mode 100644 index 000000000..e53c7c69a --- /dev/null +++ b/packages/open-next/src/core/patchAsyncStorage.ts @@ -0,0 +1,39 @@ +const mod = require("module"); + +const resolveFilename = mod._resolveFilename; + +export function patchAsyncStorage() { + mod._resolveFilename = function ( + originalResolveFilename: typeof resolveFilename, + request: string, + parent: any, + isMain: boolean, + options: any, + ) { + if ( + request.endsWith("static-generation-async-storage.external") || + request.endsWith("static-generation-async-storage.external.js") + ) { + return require.resolve("./patchedAsyncStorage.cjs"); + } else if ( + request.endsWith("static-generation-async-storage.external.original") + ) { + return originalResolveFilename.call( + mod, + request.replace(".original", ".js"), + parent, + isMain, + options, + ); + } else + return originalResolveFilename.call( + mod, + request, + parent, + isMain, + options, + ); + + // We use `bind` here to avoid referencing outside variables to create potential memory leaks. + }.bind(null, resolveFilename); +} diff --git a/packages/open-next/src/core/requestHandler.ts b/packages/open-next/src/core/requestHandler.ts index 5a5342e67..c34b9e46d 100644 --- a/packages/open-next/src/core/requestHandler.ts +++ b/packages/open-next/src/core/requestHandler.ts @@ -9,6 +9,7 @@ import { InternalEvent, InternalResult } from "types/open-next"; import { DetachedPromiseRunner } from "utils/promise"; import { debug, error, warn } from "../adapters/logger"; +import { patchAsyncStorage } from "./patchAsyncStorage"; import { convertRes, createServerResponse, proxyRequest } from "./routing/util"; import routingHandler, { MiddlewareOutputEvent } from "./routingHandler"; import { requestHandler, setNextjsPrebundledReact } from "./util"; @@ -17,8 +18,11 @@ import { requestHandler, setNextjsPrebundledReact } from "./util"; globalThis.__als = new AsyncLocalStorage<{ requestId: string; pendingPromiseRunner: DetachedPromiseRunner; + isISRRevalidation?: boolean; }>(); +patchAsyncStorage(); + export async function openNextHandler( internalEvent: InternalEvent, responseStreaming?: StreamCreator, @@ -98,8 +102,9 @@ export async function openNextHandler( const requestId = Math.random().toString(36); const pendingPromiseRunner: DetachedPromiseRunner = new DetachedPromiseRunner(); + const isISRRevalidation = headers["x-isr"] === "1"; const internalResult = await globalThis.__als.run( - { requestId, pendingPromiseRunner }, + { requestId, pendingPromiseRunner, isISRRevalidation }, async () => { const preprocessedResult = preprocessResult as MiddlewareOutputEvent; const req = new IncomingMessage(reqProps);