UNPKG

@cloudflare/next-on-pages

Version:

`@cloudflare/next-on-pages` is a CLI tool that you can use to build and develop [Next.js](https://nextjs.org/) applications so that they can run on the [Cloudflare Pages](https://pages.cloudflare.com/) platform (and integrate with Cloudflare's various oth

141 lines (124 loc) 3.94 kB
import { SUSPENSE_CACHE_URL } from '../cache'; import { handleRequest } from './handleRequest'; import { setupRoutesIsolation } from './routesIsolation'; import { adjustRequestForVercel, handleImageResizingRequest, patchFetch, } from './utils'; import type { AsyncLocalStorage } from 'node:async_hooks'; declare const __NODE_ENV__: string; declare const __CONFIG__: ProcessedVercelConfig; declare const __BUILD_OUTPUT__: VercelBuildOutput; declare const __BUILD_METADATA__: NextOnPagesBuildMetadata; declare const __ALSes_PROMISE__: Promise<null | { envAsyncLocalStorage: AsyncLocalStorage<unknown>; requestContextAsyncLocalStorage: AsyncLocalStorage<unknown>; }>; const originalDefineProperty = Object.defineProperty; const patchedDefineProperty = ( ...args: Parameters<typeof Object.defineProperty<unknown>> ) => { const target = args[0]; const key = args[1]; // Next.js defined an __import_unsupported global property as non configurable // with next-on-pages this apps try to re-define this property multiple times, // so here we patch `defineProperty` to just ignore re-definition of such property const importUnsupportedKey = '__import_unsupported'; if (key === importUnsupportedKey) { if ( typeof target === 'object' && target !== null && importUnsupportedKey in target ) { return; } } return originalDefineProperty(...args); }; globalThis.Object.defineProperty = patchedDefineProperty as typeof globalThis.Object.defineProperty; globalThis.AbortController = class PatchedAbortController extends ( AbortController ) { constructor() { try { super(); } catch (e) { if ( e instanceof Error && e.message.includes('Disallowed operation called within global scope') ) { // Next.js attempted to create an AbortController in the global scope // let's return something that looks like an AbortController but with // noop functionalities return { signal: { aborted: false, reason: null, onabort: () => { /* empty */ }, throwIfAborted: () => { /* empty */ }, } as unknown as AbortSignal, abort() { /* empty */ }, }; } else { throw e; } } } }; export default { async fetch(request, env, ctx) { setupRoutesIsolation(); patchFetch(); const asyncLocalStorages = await __ALSes_PROMISE__; if (!asyncLocalStorages) { const reqUrl = new URL(request.url); const noNodeJsCompatStaticPageRequest = await env.ASSETS.fetch( `${reqUrl.protocol}//${reqUrl.host}/cdn-cgi/errors/no-nodejs_compat.html`, ); const responseBody = noNodeJsCompatStaticPageRequest.ok ? noNodeJsCompatStaticPageRequest.body : "Error: Could not access built-in Node.js modules. Please make sure that your Cloudflare Pages project has the 'nodejs_compat' compatibility flag set."; return new Response(responseBody, { status: 503 }); } const { envAsyncLocalStorage, requestContextAsyncLocalStorage } = asyncLocalStorages; return envAsyncLocalStorage.run( // NOTE: The `SUSPENSE_CACHE_URL` is used to tell the Next.js Fetch Cache where to send requests. { ...env, NODE_ENV: __NODE_ENV__, SUSPENSE_CACHE_URL }, async () => { return requestContextAsyncLocalStorage.run( { env, ctx, cf: request.cf }, async () => { const url = new URL(request.url); if (url.pathname.startsWith('/_next/image')) { return handleImageResizingRequest(request, { buildOutput: __BUILD_OUTPUT__, assetsFetcher: env.ASSETS, imagesConfig: __CONFIG__.images, }); } const adjustedRequest = adjustRequestForVercel(request); return handleRequest( { request: adjustedRequest, ctx, assetsFetcher: env.ASSETS, }, __CONFIG__, __BUILD_OUTPUT__, __BUILD_METADATA__, ); }, ); }, ); }, } as ExportedHandler<{ ASSETS: Fetcher }>;