From 0d747805229483fcb64d17a2aacabf00312bfa31 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Wed, 12 Feb 2025 20:14:14 +0000 Subject: [PATCH 01/20] add "async mode" to `getCloudflareContext` add a new option to `getCloudflareContext` that makes it run in "async mode", the difference being that the returned value is a promise of the Cloudflare context instead of the context itself The main of this is that it allows the function to also run during SSG (since the missing context can be created on demand). --- .changeset/tough-tables-talk.md | 10 + examples/common/apps.ts | 1 + examples/ssg-app/.dev.vars | 1 + examples/ssg-app/.gitignore | 47 ++++ examples/ssg-app/app/favicon.ico | Bin 0 -> 25931 bytes examples/ssg-app/app/globals.css | 14 ++ examples/ssg-app/app/layout.tsx | 28 +++ examples/ssg-app/app/page.module.css | 17 ++ examples/ssg-app/app/page.tsx | 17 ++ examples/ssg-app/e2e/base.spec.ts | 17 ++ examples/ssg-app/e2e/playwright.config.ts | 3 + examples/ssg-app/next.config.ts | 10 + examples/ssg-app/open-next.config.ts | 26 ++ examples/ssg-app/package.json | 28 +++ examples/ssg-app/tsconfig.json | 27 ++ examples/ssg-app/worker-configuration.d.ts | 5 + examples/ssg-app/wrangler.json | 22 ++ .../cloudflare/src/api/cloudflare-context.ts | 64 ++++- pnpm-lock.yaml | 232 +++++++++++++++--- 19 files changed, 528 insertions(+), 41 deletions(-) create mode 100644 .changeset/tough-tables-talk.md create mode 100644 examples/ssg-app/.dev.vars create mode 100644 examples/ssg-app/.gitignore create mode 100644 examples/ssg-app/app/favicon.ico create mode 100644 examples/ssg-app/app/globals.css create mode 100644 examples/ssg-app/app/layout.tsx create mode 100644 examples/ssg-app/app/page.module.css create mode 100644 examples/ssg-app/app/page.tsx create mode 100644 examples/ssg-app/e2e/base.spec.ts create mode 100644 examples/ssg-app/e2e/playwright.config.ts create mode 100644 examples/ssg-app/next.config.ts create mode 100644 examples/ssg-app/open-next.config.ts create mode 100644 examples/ssg-app/package.json create mode 100644 examples/ssg-app/tsconfig.json create mode 100644 examples/ssg-app/worker-configuration.d.ts create mode 100644 examples/ssg-app/wrangler.json diff --git a/.changeset/tough-tables-talk.md b/.changeset/tough-tables-talk.md new file mode 100644 index 00000000..646c1faf --- /dev/null +++ b/.changeset/tough-tables-talk.md @@ -0,0 +1,10 @@ +--- +"@opennextjs/cloudflare": patch +--- + +add "async mode" to `getCloudflareContext` + +add a new option to `getCloudflareContext` that makes it run in "async mode", the difference being that the returned value is a +promise of the Cloudflare context instead of the context itself + +The main of this is that it allows the function to also run during SSG (since the missing context can be created on demand). diff --git a/examples/common/apps.ts b/examples/common/apps.ts index a9ed13d3..c855fa68 100644 --- a/examples/common/apps.ts +++ b/examples/common/apps.ts @@ -6,6 +6,7 @@ const apps = [ "middleware", "vercel-blog-starter", "vercel-commerce", + "ssg-app", // e2e "app-pages-router", "app-router", diff --git a/examples/ssg-app/.dev.vars b/examples/ssg-app/.dev.vars new file mode 100644 index 00000000..e2ed741b --- /dev/null +++ b/examples/ssg-app/.dev.vars @@ -0,0 +1 @@ +MY_SECRET = "psst... this is a secret!" diff --git a/examples/ssg-app/.gitignore b/examples/ssg-app/.gitignore new file mode 100644 index 00000000..3f753f29 --- /dev/null +++ b/examples/ssg-app/.gitignore @@ -0,0 +1,47 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/examples/ssg-app/app/favicon.ico b/examples/ssg-app/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/examples/ssg-app/app/globals.css b/examples/ssg-app/app/globals.css new file mode 100644 index 00000000..6e6f12f3 --- /dev/null +++ b/examples/ssg-app/app/globals.css @@ -0,0 +1,14 @@ +html, +body { + max-width: 100vw; + overflow-x: hidden; + height: 100vh; + display: flex; + flex-direction: column; +} + +footer { + padding: 1rem; + display: flex; + justify-content: end; +} diff --git a/examples/ssg-app/app/layout.tsx b/examples/ssg-app/app/layout.tsx new file mode 100644 index 00000000..d3d960b2 --- /dev/null +++ b/examples/ssg-app/app/layout.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from "next"; +import "./globals.css"; + +import { getCloudflareContext } from "@opennextjs/cloudflare"; + +export const metadata: Metadata = { + title: "SSG App", + description: "An app in which all the routes are SSG'd", +}; + +export default async function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const cloudflareContext = await getCloudflareContext({ + async: true, + }); + + return ( + + + {children} +
{cloudflareContext.env.APP_VERSION}
+ + + ); +} diff --git a/examples/ssg-app/app/page.module.css b/examples/ssg-app/app/page.module.css new file mode 100644 index 00000000..1217984e --- /dev/null +++ b/examples/ssg-app/app/page.module.css @@ -0,0 +1,17 @@ +.page { + display: grid; + grid-template-rows: 20px 1fr 20px; + align-items: center; + justify-items: center; + flex: 1; + border: 3px solid gray; + margin: 1rem; + margin-block-end: 0; +} + +.main { + display: flex; + flex-direction: column; + gap: 32px; + grid-row-start: 2; +} diff --git a/examples/ssg-app/app/page.tsx b/examples/ssg-app/app/page.tsx new file mode 100644 index 00000000..cfec97ef --- /dev/null +++ b/examples/ssg-app/app/page.tsx @@ -0,0 +1,17 @@ +import styles from "./page.module.css"; +import { getCloudflareContext } from "@opennextjs/cloudflare"; + +export default async function Home() { + const cloudflareContext = await getCloudflareContext({ + async: true, + }); + + return ( +
+
+

Hello from a Statically generated app

+

{cloudflareContext.env.MY_SECRET}

+
+
+ ); +} diff --git a/examples/ssg-app/e2e/base.spec.ts b/examples/ssg-app/e2e/base.spec.ts new file mode 100644 index 00000000..f092a3ce --- /dev/null +++ b/examples/ssg-app/e2e/base.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from "@playwright/test"; + +test("the index page should work", async ({ page }) => { + await page.goto("/"); + await expect(page.getByText("Hello from a Statically generated app")).toBeVisible(); +}); + +test("the APP_VERSION var from the cloudflare context should be displayed", async ({ page }) => { + await page.goto("/"); + await expect(page.getByTestId("app-version")).toHaveText("1.2.345"); +}); + +// Note: secrets from .dev.vars are also part of the SSG output, this is expected and nothing we can avoid +test("the MY_SECRET secret from the cloudflare context should be displayed", async ({ page }) => { + await page.goto("/"); + await expect(page.getByTestId("my-secret")).toHaveText("psst... this is a secret!"); +}); diff --git a/examples/ssg-app/e2e/playwright.config.ts b/examples/ssg-app/e2e/playwright.config.ts new file mode 100644 index 00000000..3be22cfb --- /dev/null +++ b/examples/ssg-app/e2e/playwright.config.ts @@ -0,0 +1,3 @@ +import { configurePlaywright } from "../../common/config-e2e"; + +export default configurePlaywright("ssg-app", { isCI: !!process.env.CI }); diff --git a/examples/ssg-app/next.config.ts b/examples/ssg-app/next.config.ts new file mode 100644 index 00000000..dce7856f --- /dev/null +++ b/examples/ssg-app/next.config.ts @@ -0,0 +1,10 @@ +import type { NextConfig } from "next"; +import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; + +initOpenNextCloudflareForDev(); + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/examples/ssg-app/open-next.config.ts b/examples/ssg-app/open-next.config.ts new file mode 100644 index 00000000..e860ec20 --- /dev/null +++ b/examples/ssg-app/open-next.config.ts @@ -0,0 +1,26 @@ +// default open-next.config.ts file created by @opennextjs/cloudflare + +import cache from "@opennextjs/cloudflare/kv-cache"; + +const config = { + default: { + override: { + wrapper: "cloudflare-node", + converter: "edge", + incrementalCache: async () => cache, + tagCache: "dummy", + queue: "dummy", + }, + }, + + middleware: { + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + }, + }, +}; + +export default config; diff --git a/examples/ssg-app/package.json b/examples/ssg-app/package.json new file mode 100644 index 00000000..da716640 --- /dev/null +++ b/examples/ssg-app/package.json @@ -0,0 +1,28 @@ +{ + "name": "ssg-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "build:worker": "opennextjs-cloudflare", + "preview": "pnpm build:worker && pnpm wrangler dev", + "e2e": "playwright test -c e2e/playwright.config.ts" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "next": "15.1.7" + }, + "devDependencies": { + "@opennextjs/cloudflare": "workspace:*", + "@playwright/test": "catalog:", + "@types/node": "catalog:", + "@types/react": "^19", + "@types/react-dom": "^19", + "typescript": "catalog:", + "wrangler": "catalog:" + } +} diff --git a/examples/ssg-app/tsconfig.json b/examples/ssg-app/tsconfig.json new file mode 100644 index 00000000..d8b93235 --- /dev/null +++ b/examples/ssg-app/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/examples/ssg-app/worker-configuration.d.ts b/examples/ssg-app/worker-configuration.d.ts new file mode 100644 index 00000000..e17a91c4 --- /dev/null +++ b/examples/ssg-app/worker-configuration.d.ts @@ -0,0 +1,5 @@ +interface CloudflareEnv { + APP_VERSION: "1.2.345"; + MY_SECRET: string; + ASSETS: Fetcher; +} diff --git a/examples/ssg-app/wrangler.json b/examples/ssg-app/wrangler.json new file mode 100644 index 00000000..c9b6a029 --- /dev/null +++ b/examples/ssg-app/wrangler.json @@ -0,0 +1,22 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "main": ".open-next/worker.js", + "name": "ssg-app", + "compatibility_date": "2025-02-04", + "compatibility_flags": ["nodejs_compat"], + "assets": { + "directory": ".open-next/assets", + "binding": "ASSETS" + }, + "vars": { + "APP_VERSION": "1.2.345" + }, + "kv_namespaces": [ + // Create a KV binding with the binding name "NEXT_CACHE_WORKERS_KV" + // to enable the KV based caching: + // { + // "binding": "NEXT_CACHE_WORKERS_KV", + // "id": "" + // } + ] +} diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 55a19191..209e2259 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -45,6 +45,14 @@ type InternalGlobalThis< __NEXT_DATA__: Record; }; +type GetCloudflareContextOptions = Partial<{ + /** + * Make `getCloudflareContext` return a promise of the cloudflare context instead of the object itself. This allows the + * function to be called in statically generated routes. + */ + async: boolean; +}>; + /** * Utility to get the current Cloudflare context * @@ -53,26 +61,46 @@ type InternalGlobalThis< export function getCloudflareContext< CfProperties extends Record = IncomingRequestCfProperties, Context = ExecutionContext, ->(): CloudflareContext { +>(options: { async: true }): Promise>; +export function getCloudflareContext< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(options?: { async?: false }): CloudflareContext; +export function getCloudflareContext< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>( + options: GetCloudflareContextOptions = {} +): CloudflareContext | Promise> { const global = globalThis as InternalGlobalThis; const cloudflareContext = global[cloudflareContextSymbol]; + // whether `getCloudflareContext` is run in "async mode" + const asyncMode = options.async; + if (!cloudflareContext) { - // For SSG Next.js creates (jest) workers that run in parallel, those don't get the current global - // state so they can't get access to the cloudflare context, unfortunately there isn't anything we - // can do about this, so the only solution is to error asking the developer to opt-out of SSG - // Next.js sets globalThis.__NEXT_DATA__.nextExport to true for the worker, so we can use that to detect - // that the route is being SSG'd (source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) - if (global.__NEXT_DATA__?.nextExport === true) { + // The non-async mode of `getCloudflareContext`, relies on the context set on the global state + // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither + // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the + // normal global state. There isn't much we can do about this so we can only throw with a helpful + // error message for the user. + // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for the worker, so we can use that to detect + // wether the route is being SSG'd (source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) + if (!asyncMode && global.__NEXT_DATA__?.nextExport === true) { throw new Error( `\n\nERROR: \`getCloudflareContext\` has been called in a static route` + - ` that is not allowed, please either avoid calling \`getCloudflareContext\`` + - ` in the route or make the route non static (for example by exporting the` + - ` \`dynamic\` route segment config set to \`'force-dynamic'\`.\n` + ` that is not allowed, this can be solved in different ways:\n\n` + + ` - call \`getCloudflareContext\` in \`async\` mode\n` + + ` - avoid calling \`getCloudflareContext\` in the route\n` + + ` - make the route non static\n` ); } + if (options.async) { + return getAndSetCloudflareContextInNextDev(); + } + // the cloudflare context is initialized by the worker and is always present in production/preview // during local development (`next dev`) it might be missing only if the developers hasn't called // the `initOpenNextCloudflareForDev` function in their Next.js config file @@ -192,3 +220,19 @@ async function getCloudflareContextFromWrangler< ctx: ctx as Context, }; } + +/** + * Gets a local proxy version of the cloudflare context (created using `getPlatformProxy`) when + * running in the standard next dev server (via `next dev`), is also sets this value on the + * globalThis so that it can be accessed later. + * + * @returns the local proxy version of the cloudflare context + */ +async function getAndSetCloudflareContextInNextDev< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(): Promise> { + const context = await getCloudflareContextFromWrangler(); + addCloudflareContextToNodejsGlobal(context); + return context as unknown as CloudflareContext; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbecf1ad..b41bbdcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -705,6 +705,40 @@ importers: specifier: 'catalog:' version: 3.107.3(@cloudflare/workers-types@4.20250109.0) + examples/ssg-app: + dependencies: + next: + specifier: 15.1.7 + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.47.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + devDependencies: + '@opennextjs/cloudflare': + specifier: workspace:* + version: link:../../packages/cloudflare + '@playwright/test': + specifier: 'catalog:' + version: 1.47.0 + '@types/node': + specifier: 'catalog:' + version: 22.2.0 + '@types/react': + specifier: ^19 + version: 19.0.8 + '@types/react-dom': + specifier: ^19 + version: 19.0.3(@types/react@19.0.8) + typescript: + specifier: 'catalog:' + version: 5.7.3 + wrangler: + specifier: 'catalog:' + version: 3.107.3(@cloudflare/workers-types@4.20250109.0) + examples/vercel-blog-starter: dependencies: classnames: @@ -3205,6 +3239,9 @@ packages: '@next/env@15.1.3': resolution: {integrity: sha512-Q1tXwQCGWyA3ehMph3VO+E6xFPHDKdHFYosadt0F78EObYxPio0S09H9UGYznDe6Wc8eLKLG89GqcFJJDiK5xw==} + '@next/env@15.1.7': + resolution: {integrity: sha512-d9jnRrkuOH7Mhi+LHav2XW91HOgTAWHxjMPkXMGBc9B2b7614P7kjt8tAplRvJpbSt4nbO1lugcT/kAaWzjlLQ==} + '@next/eslint-plugin-next@14.2.14': resolution: {integrity: sha512-kV+OsZ56xhj0rnTn6HegyTGkoa16Mxjrpk7pjWumyB2P8JVQb8S9qtkjy/ye0GnTr4JWtWG4x/2qN40lKZ3iVQ==} @@ -3253,6 +3290,12 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@15.1.7': + resolution: {integrity: sha512-hPFwzPJDpA8FGj7IKV3Yf1web3oz2YsR8du4amKw8d+jAOHfYHYFpMkoF6vgSY4W6vB29RtZEklK9ayinGiCmQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-x64@14.2.11': resolution: {integrity: sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==} engines: {node: '>= 10'} @@ -3289,6 +3332,12 @@ packages: cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@15.1.7': + resolution: {integrity: sha512-2qoas+fO3OQKkU0PBUfwTiw/EYpN+kdAx62cePRyY1LqKtP09Vp5UcUntfZYajop5fDFTjSxCHfZVRxzi+9FYQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-linux-arm64-gnu@14.2.11': resolution: {integrity: sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==} engines: {node: '>= 10'} @@ -3325,6 +3374,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-gnu@15.1.7': + resolution: {integrity: sha512-sKLLwDX709mPdzxMnRIXLIT9zaX2w0GUlkLYQnKGoXeWUhcvpCrK+yevcwCJPdTdxZEUA0mOXGLdPsGkudGdnA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@14.2.11': resolution: {integrity: sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==} engines: {node: '>= 10'} @@ -3361,6 +3416,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@15.1.7': + resolution: {integrity: sha512-zblK1OQbQWdC8fxdX4fpsHDw+VSpBPGEUX4PhSE9hkaWPrWoeIJn+baX53vbsbDRaDKd7bBNcXRovY1hEhFd7w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-x64-gnu@14.2.11': resolution: {integrity: sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==} engines: {node: '>= 10'} @@ -3397,6 +3458,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-gnu@15.1.7': + resolution: {integrity: sha512-GOzXutxuLvLHFDAPsMP2zDBMl1vfUHHpdNpFGhxu90jEzH6nNIgmtw/s1MDwpTOiM+MT5V8+I1hmVFeAUhkbgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@14.2.11': resolution: {integrity: sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==} engines: {node: '>= 10'} @@ -3433,6 +3500,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@15.1.7': + resolution: {integrity: sha512-WrZ7jBhR7ATW1z5iEQ0ZJfE2twCNSXbpCSaAunF3BKcVeHFADSI/AW1y5Xt3DzTqPF1FzQlwQTewqetAABhZRQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-win32-arm64-msvc@14.2.11': resolution: {integrity: sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==} engines: {node: '>= 10'} @@ -3469,6 +3542,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@15.1.7': + resolution: {integrity: sha512-LDnj1f3OVbou1BqvvXVqouJZKcwq++mV2F+oFHptToZtScIEnhNRJAhJzqAtTE2dB31qDYL45xJwrc+bLeKM2Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-ia32-msvc@14.2.11': resolution: {integrity: sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==} engines: {node: '>= 10'} @@ -3523,6 +3602,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@15.1.7': + resolution: {integrity: sha512-dC01f1quuf97viOfW05/K8XYv2iuBgAxJZl7mbCKEjMgdQl5JjAKJ0D2qMKZCgPWDeFbFT0Q0nYWwytEW0DWTQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@noble/ciphers@1.1.3': resolution: {integrity: sha512-Ygv6WnWJHLLiW4fnNDC1z+i13bud+enXOFRBlpxI+NJliPWx5wdR+oWlTjLuBPTqjUjtHXtjkU6w3kuuH6upZA==} engines: {node: ^14.21.3 || >=16} @@ -7548,6 +7633,27 @@ packages: sass: optional: true + next@15.1.7: + resolution: {integrity: sha512-GNeINPGS9c6OZKCvKypbL8GTsT5GhWPp4DM0fzkXJuXMilOO2EeFxuAY6JZbtk6XIl6Ws10ag3xRINDjSO5+wg==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -12436,6 +12542,8 @@ snapshots: '@next/env@15.1.3': {} + '@next/env@15.1.7': {} + '@next/eslint-plugin-next@14.2.14': dependencies: glob: 10.3.10 @@ -12470,6 +12578,9 @@ snapshots: '@next/swc-darwin-arm64@15.1.3': optional: true + '@next/swc-darwin-arm64@15.1.7': + optional: true + '@next/swc-darwin-x64@14.2.11': optional: true @@ -12488,6 +12599,9 @@ snapshots: '@next/swc-darwin-x64@15.1.3': optional: true + '@next/swc-darwin-x64@15.1.7': + optional: true + '@next/swc-linux-arm64-gnu@14.2.11': optional: true @@ -12506,6 +12620,9 @@ snapshots: '@next/swc-linux-arm64-gnu@15.1.3': optional: true + '@next/swc-linux-arm64-gnu@15.1.7': + optional: true + '@next/swc-linux-arm64-musl@14.2.11': optional: true @@ -12524,6 +12641,9 @@ snapshots: '@next/swc-linux-arm64-musl@15.1.3': optional: true + '@next/swc-linux-arm64-musl@15.1.7': + optional: true + '@next/swc-linux-x64-gnu@14.2.11': optional: true @@ -12542,6 +12662,9 @@ snapshots: '@next/swc-linux-x64-gnu@15.1.3': optional: true + '@next/swc-linux-x64-gnu@15.1.7': + optional: true + '@next/swc-linux-x64-musl@14.2.11': optional: true @@ -12560,6 +12683,9 @@ snapshots: '@next/swc-linux-x64-musl@15.1.3': optional: true + '@next/swc-linux-x64-musl@15.1.7': + optional: true + '@next/swc-win32-arm64-msvc@14.2.11': optional: true @@ -12578,6 +12704,9 @@ snapshots: '@next/swc-win32-arm64-msvc@15.1.3': optional: true + '@next/swc-win32-arm64-msvc@15.1.7': + optional: true + '@next/swc-win32-ia32-msvc@14.2.11': optional: true @@ -12605,6 +12734,9 @@ snapshots: '@next/swc-win32-x64-msvc@15.1.3': optional: true + '@next/swc-win32-x64-msvc@15.1.7': + optional: true + '@noble/ciphers@1.1.3': {} '@noble/curves@1.7.0': @@ -15754,8 +15886,8 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.36.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -15774,7 +15906,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.36.1(eslint@8.57.1) @@ -15794,7 +15926,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-jsx-a11y: 6.10.0(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-react: 7.37.4(eslint@9.11.1(jiti@1.21.6)) @@ -15814,7 +15946,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-jsx-a11y: 6.10.0(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-react: 7.37.4(eslint@9.19.0(jiti@1.21.6)) @@ -15834,32 +15966,32 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -15872,13 +16004,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 9.11.1(jiti@1.21.6) - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -15891,13 +16023,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 9.19.0(jiti@1.21.6) - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -15910,73 +16042,84 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -15987,7 +16130,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -16015,7 +16158,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -16044,7 +16187,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -16073,7 +16216,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -18220,6 +18363,33 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.47.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@next/env': 15.1.7 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001664 + postcss: 8.4.31 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + styled-jsx: 5.1.6(react@19.0.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.1.7 + '@next/swc-darwin-x64': 15.1.7 + '@next/swc-linux-arm64-gnu': 15.1.7 + '@next/swc-linux-arm64-musl': 15.1.7 + '@next/swc-linux-x64-gnu': 15.1.7 + '@next/swc-linux-x64-musl': 15.1.7 + '@next/swc-win32-arm64-msvc': 15.1.7 + '@next/swc-win32-x64-msvc': 15.1.7 + '@opentelemetry/api': 1.9.0 + '@playwright/test': 1.47.0 + sharp: 0.33.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + no-case@3.0.4: dependencies: lower-case: 2.0.2 From aae8cabba8b7ae6e2c25734d351b427d79811b3c Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Wed, 12 Feb 2025 20:49:09 +0000 Subject: [PATCH 02/20] change comment --- packages/cloudflare/src/api/cloudflare-context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 209e2259..8aa160fc 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -85,7 +85,7 @@ export function getCloudflareContext< // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the // normal global state. There isn't much we can do about this so we can only throw with a helpful // error message for the user. - // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for the worker, so we can use that to detect + // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes, so we can use that to detect // wether the route is being SSG'd (source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) if (!asyncMode && global.__NEXT_DATA__?.nextExport === true) { throw new Error( From 0d2dd9ba77b2cfe82d58ebcbc68c283b6a028fd2 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Wed, 12 Feb 2025 20:50:22 +0000 Subject: [PATCH 03/20] Update packages/cloudflare/src/api/cloudflare-context.ts Co-authored-by: Victor Berchet --- packages/cloudflare/src/api/cloudflare-context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 8aa160fc..3117741e 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -91,7 +91,7 @@ export function getCloudflareContext< throw new Error( `\n\nERROR: \`getCloudflareContext\` has been called in a static route` + ` that is not allowed, this can be solved in different ways:\n\n` + - ` - call \`getCloudflareContext\` in \`async\` mode\n` + + ` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` + ` - avoid calling \`getCloudflareContext\` in the route\n` + ` - make the route non static\n` ); From c5af4399026d535dcec2a50925b149af1ff3cb4d Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Wed, 12 Feb 2025 20:57:50 +0000 Subject: [PATCH 04/20] Update .changeset/tough-tables-talk.md Co-authored-by: Victor Berchet --- .changeset/tough-tables-talk.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tough-tables-talk.md b/.changeset/tough-tables-talk.md index 646c1faf..2918135a 100644 --- a/.changeset/tough-tables-talk.md +++ b/.changeset/tough-tables-talk.md @@ -4,7 +4,7 @@ add "async mode" to `getCloudflareContext` -add a new option to `getCloudflareContext` that makes it run in "async mode", the difference being that the returned value is a +Add an `async` option to `getCloudflareContext({async})` to run it in "async mode", the difference being that the returned value is a promise of the Cloudflare context instead of the context itself The main of this is that it allows the function to also run during SSG (since the missing context can be created on demand). From 51d661919a0db38accb7aab2c31235ec25d8b40d Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Wed, 12 Feb 2025 20:56:03 +0000 Subject: [PATCH 05/20] remove unnecessary partial --- packages/cloudflare/src/api/cloudflare-context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 3117741e..48bd0fd5 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -45,13 +45,13 @@ type InternalGlobalThis< __NEXT_DATA__: Record; }; -type GetCloudflareContextOptions = Partial<{ +type GetCloudflareContextOptions = { /** * Make `getCloudflareContext` return a promise of the cloudflare context instead of the object itself. This allows the * function to be called in statically generated routes. */ async: boolean; -}>; +}; /** * Utility to get the current Cloudflare context From d4a5d4b81276c0129675eb3441191a5111072622 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Wed, 12 Feb 2025 21:01:07 +0000 Subject: [PATCH 06/20] fix async still being treated as optional --- packages/cloudflare/src/api/cloudflare-context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 48bd0fd5..0283ec26 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -65,12 +65,12 @@ export function getCloudflareContext< export function getCloudflareContext< CfProperties extends Record = IncomingRequestCfProperties, Context = ExecutionContext, ->(options?: { async?: false }): CloudflareContext; +>(options?: { async: false }): CloudflareContext; export function getCloudflareContext< CfProperties extends Record = IncomingRequestCfProperties, Context = ExecutionContext, >( - options: GetCloudflareContextOptions = {} + options: GetCloudflareContextOptions = { async: false } ): CloudflareContext | Promise> { const global = globalThis as InternalGlobalThis; From 049d01c04b1efe515c5f2a58f76618ac076da68f Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 15:51:05 +0000 Subject: [PATCH 07/20] Apply suggestions from code review Co-authored-by: Victor Berchet --- examples/ssg-app/app/page.tsx | 2 +- packages/cloudflare/src/api/cloudflare-context.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/ssg-app/app/page.tsx b/examples/ssg-app/app/page.tsx index cfec97ef..e611c3dc 100644 --- a/examples/ssg-app/app/page.tsx +++ b/examples/ssg-app/app/page.tsx @@ -9,7 +9,7 @@ export default async function Home() { return (
-

Hello from a Statically generated app

+

Hello from a Statically generated page

{cloudflareContext.env.MY_SECRET}

diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 0283ec26..9b9cf583 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -80,13 +80,12 @@ export function getCloudflareContext< const asyncMode = options.async; if (!cloudflareContext) { - // The non-async mode of `getCloudflareContext`, relies on the context set on the global state + // The sync mode of `getCloudflareContext`, relies on the context being set on the global state // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the - // normal global state. There isn't much we can do about this so we can only throw with a helpful - // error message for the user. - // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes, so we can use that to detect - // wether the route is being SSG'd (source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) + // normal global state so we throw with a helpful error message. + // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes + // source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) if (!asyncMode && global.__NEXT_DATA__?.nextExport === true) { throw new Error( `\n\nERROR: \`getCloudflareContext\` has been called in a static route` + From 3c2df2723825985addff4111fdf0511180993b74 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 15:58:59 +0000 Subject: [PATCH 08/20] update incorrect function names and comments --- packages/cloudflare/src/api/cloudflare-context.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 9b9cf583..bdbf9f19 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -97,7 +97,7 @@ export function getCloudflareContext< } if (options.async) { - return getAndSetCloudflareContextInNextDev(); + return getAndSetCloudflareContextInNodejs(); } // the cloudflare context is initialized by the worker and is always present in production/preview @@ -154,7 +154,7 @@ function shouldContextInitializationRun(): boolean { } /** - * Adds the cloudflare context to the global scope in which the Next.js dev node.js process runs in, enabling + * Adds the cloudflare context to the global scope of the current node.js process, enabling * future calls to `getCloudflareContext` to retrieve and return such context * * @param cloudflareContext the cloudflare context to add to the node.sj global scope @@ -221,13 +221,13 @@ async function getCloudflareContextFromWrangler< } /** - * Gets a local proxy version of the cloudflare context (created using `getPlatformProxy`) when - * running in the standard next dev server (via `next dev`), is also sets this value on the + * Gets a local proxy version of the cloudflare context (created using `getPlatformProxy`) when running + * in a Node.js process (so under `next dev` or for ssg under `next build`), is also sets this value on the * globalThis so that it can be accessed later. * * @returns the local proxy version of the cloudflare context */ -async function getAndSetCloudflareContextInNextDev< +async function getAndSetCloudflareContextInNodejs< CfProperties extends Record = IncomingRequestCfProperties, Context = ExecutionContext, >(): Promise> { From 9a3db1caf8b5fa1f26a1e51d8e4666fb1df87b2c Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 15:59:34 +0000 Subject: [PATCH 09/20] update e2e text --- examples/ssg-app/e2e/base.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ssg-app/e2e/base.spec.ts b/examples/ssg-app/e2e/base.spec.ts index f092a3ce..14b8cdbf 100644 --- a/examples/ssg-app/e2e/base.spec.ts +++ b/examples/ssg-app/e2e/base.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from "@playwright/test"; test("the index page should work", async ({ page }) => { await page.goto("/"); - await expect(page.getByText("Hello from a Statically generated app")).toBeVisible(); + await expect(page.getByText("Hello from a Statically generated page")).toBeVisible(); }); test("the APP_VERSION var from the cloudflare context should be displayed", async ({ page }) => { From 477e2a1f6728043bdf8b47c0c311ca0ebbdfb476 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 16:54:58 +0000 Subject: [PATCH 10/20] Apply suggestions from code review Co-authored-by: Victor Berchet --- packages/cloudflare/src/api/cloudflare-context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index bdbf9f19..2f795f23 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -47,8 +47,8 @@ type InternalGlobalThis< type GetCloudflareContextOptions = { /** - * Make `getCloudflareContext` return a promise of the cloudflare context instead of the object itself. This allows the - * function to be called in statically generated routes. + * When `true`, `getCloudflareContext` returns a promise of the cloudflare context instead of the context, + * this is needed to access the context from statically generated routes. */ async: boolean; }; From 75fc34018bdf1203e5bc74662f4dd1d46e2dc002 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 17:01:43 +0000 Subject: [PATCH 11/20] inline setting of context and fix wrong types --- .../cloudflare/src/api/cloudflare-context.ts | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 2f795f23..6174543b 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -97,7 +97,10 @@ export function getCloudflareContext< } if (options.async) { - return getAndSetCloudflareContextInNodejs(); + return getCloudflareContextFromWrangler().then((context) => { + addCloudflareContextToNodejsGlobal(context); + return context; + }); } // the cloudflare context is initialized by the worker and is always present in production/preview @@ -159,7 +162,10 @@ function shouldContextInitializationRun(): boolean { * * @param cloudflareContext the cloudflare context to add to the node.sj global scope */ -function addCloudflareContextToNodejsGlobal(cloudflareContext: CloudflareContext) { +function addCloudflareContextToNodejsGlobal< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(cloudflareContext: CloudflareContext) { const global = globalThis as InternalGlobalThis; global[cloudflareContextSymbol] = cloudflareContext; } @@ -219,19 +225,3 @@ async function getCloudflareContextFromWrangler< ctx: ctx as Context, }; } - -/** - * Gets a local proxy version of the cloudflare context (created using `getPlatformProxy`) when running - * in a Node.js process (so under `next dev` or for ssg under `next build`), is also sets this value on the - * globalThis so that it can be accessed later. - * - * @returns the local proxy version of the cloudflare context - */ -async function getAndSetCloudflareContextInNodejs< - CfProperties extends Record = IncomingRequestCfProperties, - Context = ExecutionContext, ->(): Promise> { - const context = await getCloudflareContextFromWrangler(); - addCloudflareContextToNodejsGlobal(context); - return context as unknown as CloudflareContext; -} From b572bafa29e94b96e26921555e637233e3fc600b Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 17:46:14 +0000 Subject: [PATCH 12/20] get rid of `options.async` --- packages/cloudflare/src/api/cloudflare-context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 6174543b..ea3ef325 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -96,7 +96,7 @@ export function getCloudflareContext< ); } - if (options.async) { + if (asyncMode) { return getCloudflareContextFromWrangler().then((context) => { addCloudflareContextToNodejsGlobal(context); return context; From 3cc360b7795ec4069c2b6081f0f0efe8f8ba609d Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 18:48:20 +0000 Subject: [PATCH 13/20] fix incorrect logic in `getCloudflareContext` --- .../cloudflare/src/api/cloudflare-context.ts | 81 ++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index ea3ef325..0f426f94 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -79,48 +79,53 @@ export function getCloudflareContext< // whether `getCloudflareContext` is run in "async mode" const asyncMode = options.async; - if (!cloudflareContext) { - // The sync mode of `getCloudflareContext`, relies on the context being set on the global state - // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither - // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the - // normal global state so we throw with a helpful error message. - // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes - // source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) - if (!asyncMode && global.__NEXT_DATA__?.nextExport === true) { - throw new Error( - `\n\nERROR: \`getCloudflareContext\` has been called in a static route` + - ` that is not allowed, this can be solved in different ways:\n\n` + - ` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` + - ` - avoid calling \`getCloudflareContext\` in the route\n` + - ` - make the route non static\n` - ); - } - - if (asyncMode) { - return getCloudflareContextFromWrangler().then((context) => { - addCloudflareContextToNodejsGlobal(context); - return context; - }); - } - - // the cloudflare context is initialized by the worker and is always present in production/preview - // during local development (`next dev`) it might be missing only if the developers hasn't called - // the `initOpenNextCloudflareForDev` function in their Next.js config file + if (cloudflareContext) { + return asyncMode ? Promise.resolve(cloudflareContext) : cloudflareContext; + } + + const inNodejsRuntime = process.env.NEXT_RUNTIME === "nodejs"; + + // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes + // source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) + const inSSG = global.__NEXT_DATA__?.nextExport === true; + + if ((inNodejsRuntime || inSSG) && asyncMode) { + // we're in a node.js process and also in "async mode" so we can use wrangler to asynchronously get the context + return getCloudflareContextFromWrangler().then((context) => { + addCloudflareContextToNodejsGlobal(context); + return context; + }); + } + + // The sync mode of `getCloudflareContext`, relies on the context being set on the global state + // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither + // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the + // normal global state so we throw with a helpful error message. + if (inSSG) { throw new Error( - `\n\nERROR: \`getCloudflareContext\` has been called without having called` + - ` \`initOpenNextCloudflareForDev\` from the Next.js config file.\n` + - `You should update your Next.js config file as shown below:\n\n` + - " ```\n // next.config.mjs\n\n" + - ` import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";\n\n` + - ` initOpenNextCloudflareForDev();\n\n` + - " const nextConfig = { ... };\n" + - " export default nextConfig;\n" + - " ```\n" + - "\n" + `\n\nERROR: \`getCloudflareContext\` has been called in a static route,` + + ` that is not allowed, this can be solved in different ways:\n\n` + + ` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` + + ` - avoid calling \`getCloudflareContext\` in the route\n` + + ` - make the route non static\n` ); } - return cloudflareContext; + // the cloudflare context is initialized by the worker and is always present in production/preview + // during local development (`next dev`) it might be missing only if the developers hasn't called + // the `initOpenNextCloudflareForDev` function in their Next.js config file + throw new Error( + `\n\nERROR: \`getCloudflareContext\` has been called without having called` + + ` \`initOpenNextCloudflareForDev\` from the Next.js config file.\n` + + `You should update your Next.js config file as shown below:\n\n` + + " ```\n // next.config.mjs\n\n" + + ` import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";\n\n` + + ` initOpenNextCloudflareForDev();\n\n` + + " const nextConfig = { ... };\n" + + " export default nextConfig;\n" + + " ```\n" + + "\n" + ); } /** From 2e994100a6301c8d7424851229dd5a2ed82e5efb Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 22:14:40 +0000 Subject: [PATCH 14/20] Apply suggestions from code review Co-authored-by: Victor Berchet --- packages/cloudflare/src/api/cloudflare-context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 0f426f94..c04e753b 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -111,8 +111,8 @@ export function getCloudflareContext< ); } - // the cloudflare context is initialized by the worker and is always present in production/preview - // during local development (`next dev`) it might be missing only if the developers hasn't called + // The cloudflare context is initialized by the worker so it is always available. + // During local development (`next dev`) it might be missing only if the developers hasn't called // the `initOpenNextCloudflareForDev` function in their Next.js config file throw new Error( `\n\nERROR: \`getCloudflareContext\` has been called without having called` + From 213fdba96d74ada6b5c56e7cca5e782c82b51401 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 22:35:41 +0000 Subject: [PATCH 15/20] add comment to `isNodejsRuntime` --- packages/cloudflare/src/api/cloudflare-context.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index c04e753b..9793e4ec 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -83,6 +83,8 @@ export function getCloudflareContext< return asyncMode ? Promise.resolve(cloudflareContext) : cloudflareContext; } + // Note: Next.js sets process.env.NEXT_RUNTIME to 'nodejs' when the runtime in use is the node.js one + // source: https://github.com/vercel/next.js/blob/1e8603113/packages/next/src/build/webpack/plugins/define-env-plugin.ts#L179-L183 const inNodejsRuntime = process.env.NEXT_RUNTIME === "nodejs"; // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes From abd7f3c96caa853ee156a9f4399f0146129b8f84 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Thu, 13 Feb 2025 22:50:25 +0000 Subject: [PATCH 16/20] add if-else statement around `asyncMode` --- .../cloudflare/src/api/cloudflare-context.ts | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 9793e4ec..1e2b2730 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -91,26 +91,28 @@ export function getCloudflareContext< // source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) const inSSG = global.__NEXT_DATA__?.nextExport === true; - if ((inNodejsRuntime || inSSG) && asyncMode) { - // we're in a node.js process and also in "async mode" so we can use wrangler to asynchronously get the context - return getCloudflareContextFromWrangler().then((context) => { - addCloudflareContextToNodejsGlobal(context); - return context; - }); - } - - // The sync mode of `getCloudflareContext`, relies on the context being set on the global state - // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither - // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the - // normal global state so we throw with a helpful error message. - if (inSSG) { - throw new Error( - `\n\nERROR: \`getCloudflareContext\` has been called in a static route,` + - ` that is not allowed, this can be solved in different ways:\n\n` + - ` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` + - ` - avoid calling \`getCloudflareContext\` in the route\n` + - ` - make the route non static\n` - ); + if (asyncMode) { + if (inNodejsRuntime || inSSG) { + // we're in a node.js process and also in "async mode" so we can use wrangler to asynchronously get the context + return getCloudflareContextFromWrangler().then((context) => { + addCloudflareContextToNodejsGlobal(context); + return context; + }); + } + } else { + // The sync mode of `getCloudflareContext`, relies on the context being set on the global state + // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither + // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the + // normal global state so we throw with a helpful error message. + if (inSSG) { + throw new Error( + `\n\nERROR: \`getCloudflareContext\` has been called in a static route,` + + ` that is not allowed, this can be solved in different ways:\n\n` + + ` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` + + ` - avoid calling \`getCloudflareContext\` in the route\n` + + ` - make the route non static\n` + ); + } } // The cloudflare context is initialized by the worker so it is always available. From 30683e7c8462f3cfd17330cace512da582048fb6 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Fri, 14 Feb 2025 06:49:13 +0100 Subject: [PATCH 17/20] fixup! Update wrangler.json, playwright.config.ts --- examples/ssg-app/e2e/playwright.config.ts | 2 +- examples/ssg-app/wrangler.json | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/examples/ssg-app/e2e/playwright.config.ts b/examples/ssg-app/e2e/playwright.config.ts index 3be22cfb..7a4d4258 100644 --- a/examples/ssg-app/e2e/playwright.config.ts +++ b/examples/ssg-app/e2e/playwright.config.ts @@ -1,3 +1,3 @@ import { configurePlaywright } from "../../common/config-e2e"; -export default configurePlaywright("ssg-app", { isCI: !!process.env.CI }); +export default configurePlaywright("ssg-app", { isCI: !!process.env.CI, multipleBrowsers: false }); diff --git a/examples/ssg-app/wrangler.json b/examples/ssg-app/wrangler.json index c9b6a029..bb008505 100644 --- a/examples/ssg-app/wrangler.json +++ b/examples/ssg-app/wrangler.json @@ -10,13 +10,5 @@ }, "vars": { "APP_VERSION": "1.2.345" - }, - "kv_namespaces": [ - // Create a KV binding with the binding name "NEXT_CACHE_WORKERS_KV" - // to enable the KV based caching: - // { - // "binding": "NEXT_CACHE_WORKERS_KV", - // "id": "" - // } - ] + } } From 18e755d68362579c3c8cd1690fb122d1ac602300 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Fri, 14 Feb 2025 13:51:21 +0000 Subject: [PATCH 18/20] apply suggested change --- packages/cloudflare/src/api/cloudflare-context.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 1e2b2730..800ae50e 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -84,7 +84,8 @@ export function getCloudflareContext< } // Note: Next.js sets process.env.NEXT_RUNTIME to 'nodejs' when the runtime in use is the node.js one - // source: https://github.com/vercel/next.js/blob/1e8603113/packages/next/src/build/webpack/plugins/define-env-plugin.ts#L179-L183 + // We want to detect when the runtime is the node.js one so that during development (`next dev`) we know wether + // we are or not in a node.js process and that access to wrangler's node.js apis const inNodejsRuntime = process.env.NEXT_RUNTIME === "nodejs"; // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes From 0fef012e2e960014ee934aee7d7919cdffb7f74c Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Fri, 14 Feb 2025 14:16:51 +0000 Subject: [PATCH 19/20] split `getCloudflareContext` implementation in `getCloudflareContextSync` and `getCloudflareContextAsync` --- .../cloudflare/src/api/cloudflare-context.ts | 104 ++++++++++++------ 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index 800ae50e..ed268490 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -72,15 +72,74 @@ export function getCloudflareContext< >( options: GetCloudflareContextOptions = { async: false } ): CloudflareContext | Promise> { + return options.async ? getCloudflareContextAsync() : getCloudflareContextSync(); +} + +/** + * Get the cloudflare context from the current global scope + */ +function getCloudflareContextFromGlobalScope< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(): CloudflareContext | undefined { + const global = globalThis as InternalGlobalThis; + return global[cloudflareContextSymbol]; +} + +/** + * Detects whether the current code is being evaluated in a statically generated route + */ +function inSSG< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(): boolean { const global = globalThis as InternalGlobalThis; + // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes + // source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) + return global.__NEXT_DATA__?.nextExport === true; +} + +/** + * Utility to get the current Cloudflare context in sync mode + */ +function getCloudflareContextSync< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(): CloudflareContext { + const cloudflareContext = getCloudflareContextFromGlobalScope(); - const cloudflareContext = global[cloudflareContextSymbol]; + if (cloudflareContext) { + return cloudflareContext; + } + + // The sync mode of `getCloudflareContext`, relies on the context being set on the global state + // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither + // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the + // normal global state so we throw with a helpful error message. + if (inSSG()) { + throw new Error( + `\n\nERROR: \`getCloudflareContext\` has been called in a static route,` + + ` that is not allowed, this can be solved in different ways:\n\n` + + ` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` + + ` - avoid calling \`getCloudflareContext\` in the route\n` + + ` - make the route non static\n` + ); + } + + throwMissingInitOpenNextCloudflareForDevError(); +} - // whether `getCloudflareContext` is run in "async mode" - const asyncMode = options.async; +/** + * Utility to get the current Cloudflare context in async mode + */ +async function getCloudflareContextAsync< + CfProperties extends Record = IncomingRequestCfProperties, + Context = ExecutionContext, +>(): Promise> { + const cloudflareContext = getCloudflareContextFromGlobalScope(); if (cloudflareContext) { - return asyncMode ? Promise.resolve(cloudflareContext) : cloudflareContext; + return cloudflareContext; } // Note: Next.js sets process.env.NEXT_RUNTIME to 'nodejs' when the runtime in use is the node.js one @@ -88,35 +147,18 @@ export function getCloudflareContext< // we are or not in a node.js process and that access to wrangler's node.js apis const inNodejsRuntime = process.env.NEXT_RUNTIME === "nodejs"; - // Note: Next.js sets globalThis.__NEXT_DATA__.nextExport to true for SSG routes - // source: https://github.com/vercel/next.js/blob/4e394608423/packages/next/src/export/worker.ts#L55-L57) - const inSSG = global.__NEXT_DATA__?.nextExport === true; - - if (asyncMode) { - if (inNodejsRuntime || inSSG) { - // we're in a node.js process and also in "async mode" so we can use wrangler to asynchronously get the context - return getCloudflareContextFromWrangler().then((context) => { - addCloudflareContextToNodejsGlobal(context); - return context; - }); - } - } else { - // The sync mode of `getCloudflareContext`, relies on the context being set on the global state - // by either the worker entrypoint (in prod) or by `initOpenNextCloudflareForDev` (in dev), neither - // can work during SSG since for SSG Next.js creates (jest) workers that don't get access to the - // normal global state so we throw with a helpful error message. - if (inSSG) { - throw new Error( - `\n\nERROR: \`getCloudflareContext\` has been called in a static route,` + - ` that is not allowed, this can be solved in different ways:\n\n` + - ` - call \`getCloudflareContext({async: true})\` to use the \`async\` mode\n` + - ` - avoid calling \`getCloudflareContext\` in the route\n` + - ` - make the route non static\n` - ); - } + if (inNodejsRuntime || inSSG()) { + // we're in a node.js process and also in "async mode" so we can use wrangler to asynchronously get the context + const cloudflareContext = await getCloudflareContextFromWrangler(); + addCloudflareContextToNodejsGlobal(cloudflareContext); + return cloudflareContext; } - // The cloudflare context is initialized by the worker so it is always available. + throwMissingInitOpenNextCloudflareForDevError(); +} + +function throwMissingInitOpenNextCloudflareForDevError(): never { + // In production the cloudflare context is initialized by the worker so it is always available. // During local development (`next dev`) it might be missing only if the developers hasn't called // the `initOpenNextCloudflareForDev` function in their Next.js config file throw new Error( From 621718e4ac8ed68fbe13237a30dd2c9bc7418fda Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Fri, 14 Feb 2025 17:21:32 +0000 Subject: [PATCH 20/20] remove `throwMissingInitOpenNextCloudflareForDevError` function --- .../cloudflare/src/api/cloudflare-context.ts | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index ed268490..200afbc7 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -126,7 +126,7 @@ function getCloudflareContextSync< ); } - throwMissingInitOpenNextCloudflareForDevError(); + throw new Error(initOpenNextCloudflareForDevErrorMsg); } /** @@ -154,25 +154,7 @@ async function getCloudflareContextAsync< return cloudflareContext; } - throwMissingInitOpenNextCloudflareForDevError(); -} - -function throwMissingInitOpenNextCloudflareForDevError(): never { - // In production the cloudflare context is initialized by the worker so it is always available. - // During local development (`next dev`) it might be missing only if the developers hasn't called - // the `initOpenNextCloudflareForDev` function in their Next.js config file - throw new Error( - `\n\nERROR: \`getCloudflareContext\` has been called without having called` + - ` \`initOpenNextCloudflareForDev\` from the Next.js config file.\n` + - `You should update your Next.js config file as shown below:\n\n` + - " ```\n // next.config.mjs\n\n" + - ` import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";\n\n` + - ` initOpenNextCloudflareForDev();\n\n` + - " const nextConfig = { ... };\n" + - " export default nextConfig;\n" + - " ```\n" + - "\n" - ); + throw new Error(initOpenNextCloudflareForDevErrorMsg); } /** @@ -277,3 +259,18 @@ async function getCloudflareContextFromWrangler< ctx: ctx as Context, }; } + +// In production the cloudflare context is initialized by the worker so it is always available. +// During local development (`next dev`) it might be missing only if the developers hasn't called +// the `initOpenNextCloudflareForDev` function in their Next.js config file +const initOpenNextCloudflareForDevErrorMsg = + `\n\nERROR: \`getCloudflareContext\` has been called without having called` + + ` \`initOpenNextCloudflareForDev\` from the Next.js config file.\n` + + `You should update your Next.js config file as shown below:\n\n` + + " ```\n // next.config.mjs\n\n" + + ` import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";\n\n` + + ` initOpenNextCloudflareForDev();\n\n` + + " const nextConfig = { ... };\n" + + " export default nextConfig;\n" + + " ```\n" + + "\n";