From fba57c0d5e6456f4fdc6cfc6f156498e38551080 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 12 Oct 2024 23:24:48 +0100 Subject: [PATCH 1/3] refactor: move prerender manifest retrieval to own utility --- .../cloudflare/src/cli/build/build-worker.ts | 5 +++-- .../build/utils/copy-prerendered-routes.ts | 16 ++++++-------- .../cli/build/utils/get-prerender-manifest.ts | 21 +++++++++++++++++++ .../cloudflare/src/cli/build/utils/index.ts | 1 + 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 packages/cloudflare/src/cli/build/utils/get-prerender-manifest.ts diff --git a/packages/cloudflare/src/cli/build/build-worker.ts b/packages/cloudflare/src/cli/build/build-worker.ts index 0fe9dd9..11a91b0 100644 --- a/packages/cloudflare/src/cli/build/build-worker.ts +++ b/packages/cloudflare/src/cli/build/build-worker.ts @@ -1,9 +1,9 @@ import { Plugin, build } from "esbuild"; +import { copyPrerenderedRoutes, getPrerenderManifest } from "./utils"; import { cp, readFile, writeFile } from "node:fs/promises"; import { existsSync, readFileSync } from "node:fs"; import { Config } from "../config"; import { copyPackageCliFiles } from "./patches/investigated/copy-package-cli-files"; -import { copyPrerenderedRoutes } from "./utils"; import { fileURLToPath } from "node:url"; import { inlineEvalManifest } from "./patches/to-investigate/inline-eval-manifest"; import { inlineMiddlewareManifestRequire } from "./patches/to-investigate/inline-middleware-manifest-require"; @@ -46,8 +46,9 @@ export async function buildWorker(config: Config): Promise { }); } + const prerenderManifest = getPrerenderManifest(config); // Copy over prerendered assets (e.g. SSG routes) - copyPrerenderedRoutes(config); + copyPrerenderedRoutes(config, prerenderManifest); copyPackageCliFiles(packageDistDir, config); diff --git a/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts b/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts index 8395213..12f733f 100644 --- a/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts +++ b/packages/cloudflare/src/cli/build/utils/copy-prerendered-routes.ts @@ -1,8 +1,8 @@ import { NEXT_META_SUFFIX, SEED_DATA_DIR } from "../../constants/incremental-cache"; -import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; -import { Config } from "../../config"; -import type { PrerenderManifest } from "next/dist/build"; +import type { Config } from "../../config"; +import type { PrerenderManifest } from "./get-prerender-manifest"; import { readPathsRecursively } from "./read-paths-recursively"; /** @@ -13,19 +13,15 @@ import { readPathsRecursively } from "./read-paths-recursively"; * the incremental cache to determine whether an entry is _fresh_ or not. * * @param config Build config. + * @param manifest Prerender manifest. */ -export function copyPrerenderedRoutes(config: Config) { +export function copyPrerenderedRoutes(config: Config, manifest: PrerenderManifest | null) { console.log("# copyPrerenderedRoutes"); const serverAppDirPath = join(config.paths.standaloneAppServer, "app"); - const prerenderManifestPath = join(config.paths.standaloneAppDotNext, "prerender-manifest.json"); const outputPath = join(config.paths.outputDir, "assets", SEED_DATA_DIR); - const prerenderManifest: PrerenderManifest = existsSync(prerenderManifestPath) - ? JSON.parse(readFileSync(prerenderManifestPath, "utf8")) - : {}; - const prerenderedRoutes = Object.keys(prerenderManifest.routes); - + const prerenderedRoutes = Object.keys(manifest?.routes ?? {}); const prerenderedAssets = readPathsRecursively(serverAppDirPath) .map((fullPath) => ({ fullPath, relativePath: fullPath.replace(serverAppDirPath, "") })) .filter(({ relativePath }) => diff --git a/packages/cloudflare/src/cli/build/utils/get-prerender-manifest.ts b/packages/cloudflare/src/cli/build/utils/get-prerender-manifest.ts new file mode 100644 index 0000000..5f22a9c --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/get-prerender-manifest.ts @@ -0,0 +1,21 @@ +import { existsSync, readFileSync } from "node:fs"; +import { Config } from "../../config"; +import { PrerenderManifest } from "next/dist/build"; +import { join } from "node:path"; + +/** + * Reads the prerender manifest from the Next.js standalone output. + * + * @param config Build config + */ +export function getPrerenderManifest(config: Config): PrerenderManifest | null { + const prerenderManifestPath = join(config.paths.standaloneAppDotNext, "prerender-manifest.json"); + + if (!existsSync(prerenderManifestPath)) { + return null; + } + + return JSON.parse(readFileSync(prerenderManifestPath, "utf8")); +} + +export type { PrerenderManifest }; diff --git a/packages/cloudflare/src/cli/build/utils/index.ts b/packages/cloudflare/src/cli/build/utils/index.ts index dca46d6..41c19f9 100644 --- a/packages/cloudflare/src/cli/build/utils/index.ts +++ b/packages/cloudflare/src/cli/build/utils/index.ts @@ -1,2 +1,3 @@ export * from "./ts-parse-file"; export * from "./copy-prerendered-routes"; +export * from "./get-prerender-manifest"; From 63785d0cf85fb3a1845b64d456500ad24664c4ac Mon Sep 17 00:00:00 2001 From: James Date: Sat, 12 Oct 2024 23:48:44 +0100 Subject: [PATCH 2/3] fix: draft mode env vars not available --- packages/cloudflare/env.d.ts | 1 + packages/cloudflare/src/cli/build/build-worker.ts | 7 +++++++ packages/cloudflare/src/cli/templates/worker.ts | 13 ++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/env.d.ts b/packages/cloudflare/env.d.ts index b5e1c84..434fdfd 100644 --- a/packages/cloudflare/env.d.ts +++ b/packages/cloudflare/env.d.ts @@ -6,6 +6,7 @@ declare global { SKIP_NEXT_APP_BUILD?: string; NEXT_PRIVATE_DEBUG_CACHE?: string; __OPENNEXT_KV_BINDING_NAME: string; + __NEXT_PRERENDER_MANIFEST_PREVIEW_CONFIG?: string; [key: string]: string | Fetcher; } } diff --git a/packages/cloudflare/src/cli/build/build-worker.ts b/packages/cloudflare/src/cli/build/build-worker.ts index 11a91b0..9e7d75c 100644 --- a/packages/cloudflare/src/cli/build/build-worker.ts +++ b/packages/cloudflare/src/cli/build/build-worker.ts @@ -47,6 +47,7 @@ export async function buildWorker(config: Config): Promise { } const prerenderManifest = getPrerenderManifest(config); + // Copy over prerendered assets (e.g. SSG routes) copyPrerenderedRoutes(config, prerenderManifest); @@ -99,6 +100,12 @@ export async function buildWorker(config: Config): Promise { "process.env.NEXT_RUNTIME": '"nodejs"', "process.env.NODE_ENV": '"production"', "process.env.NEXT_MINIMAL": "true", + ...(prerenderManifest?.preview && { + // Used for Next.js Draft Mode internal variables - https://nextjs.org/docs/app/building-your-application/configuring/draft-mode + "process.env.__NEXT_PRERENDER_MANIFEST_PREVIEW_CONFIG": JSON.stringify( + JSON.stringify(prerenderManifest.preview) + ), + }), }, // We need to set platform to node so that esbuild doesn't complain about the node imports platform: "node", diff --git a/packages/cloudflare/src/cli/templates/worker.ts b/packages/cloudflare/src/cli/templates/worker.ts index e76547f..e1c1c0b 100644 --- a/packages/cloudflare/src/cli/templates/worker.ts +++ b/packages/cloudflare/src/cli/templates/worker.ts @@ -7,6 +7,7 @@ import { MockedResponse } from "next/dist/server/lib/mock-request"; import type { NextConfig } from "next"; import type { NodeRequestHandler } from "next/dist/server/next-server"; import Stream from "node:stream"; +import type { __ApiPreviewProps } from "next/dist/server/api-utils"; const NON_BODY_RESPONSES = new Set([101, 204, 205, 304]); @@ -27,6 +28,9 @@ const cloudflareContextALS = new AsyncLocalStorage(); // Injected at build time const nextConfig: NextConfig = JSON.parse(process.env.__NEXT_PRIVATE_STANDALONE_CONFIG ?? "{}"); +const nextPreviewConfig: __ApiPreviewProps = JSON.parse( + process.env.__NEXT_PRERENDER_MANIFEST_PREVIEW_CONFIG ?? "{}" +); let requestHandler: NodeRequestHandler | null = null; @@ -34,7 +38,14 @@ export default { async fetch(request, env, ctx) { return cloudflareContextALS.run({ env, ctx, cf: request.cf }, async () => { if (requestHandler == null) { - globalThis.process.env = { ...globalThis.process.env, ...env }; + globalThis.process.env = { + __NEXT_PREVIEW_MODE_ID: nextPreviewConfig.previewModeId, + __NEXT_PREVIEW_MODE_ENCRYPTION_KEY: nextPreviewConfig.previewModeEncryptionKey, + __NEXT_PREVIEW_MODE_SIGNING_KEY: nextPreviewConfig.previewModeSigningKey, + ...globalThis.process.env, + ...env, + }; + // Note: "next/dist/server/next-server" is a cjs module so we have to `require` it not to confuse esbuild // (since esbuild can run in projects with different module resolutions) // eslint-disable-next-line @typescript-eslint/no-require-imports From fc118eb6fad1213a4f6c759e0788edbc5b045818 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 12 Oct 2024 23:57:13 +0100 Subject: [PATCH 3/3] chore: changeset --- .changeset/young-eyes-hang.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/young-eyes-hang.md diff --git a/.changeset/young-eyes-hang.md b/.changeset/young-eyes-hang.md new file mode 100644 index 0000000..c9256d0 --- /dev/null +++ b/.changeset/young-eyes-hang.md @@ -0,0 +1,7 @@ +--- +"@opennextjs/cloudflare": patch +--- + +fix: draft mode env vars not available + +In certain scenarios, Next.js expects to be able to access environment variables for draft/preview mode. These environment variables were not being exposed before, but are now exposed on process.env.