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

121 lines (103 loc) 4.06 kB
import { handleSuspenseCacheRequest } from './cache'; import { doImport } from './doImport'; /** * Patches the global fetch in ways necessary for Next.js (/next-on-pages) applications * to work */ export function patchFetch(): void { const alreadyPatched = (globalThis as GlobalWithPatchSymbol)[patchFlagSymbol]; if (alreadyPatched) return; applyPatch(); (globalThis as GlobalWithPatchSymbol)[patchFlagSymbol] = true; } function applyPatch() { const originalFetch = globalThis.fetch; globalThis.fetch = async (...args) => { const request = new Request(...args); let response = await handleInlineAssetRequest(request); if (response) return response; response = await handleSuspenseCacheRequest(request); if (response) return response; setRequestUserAgentIfNeeded(request); return originalFetch(request); }; } /** * This function checks if a given request is trying to fetch an inline if it is it returns a response containing a stream for the asset, * otherwise returns null (signaling that the request hasn't been handled). * * This is necessary so that users can fetch urls such as: `new URL("file", import.meta.url)` * (used for example with `@vercel/og`) * * Note: this function's aim is to mimic the following Next behavior: * https://github.com/vercel/next.js/blob/6705c803021d3bdea7fec20e5d98f6899e49836d/packages/next/src/server/web/sandbox/fetch-inline-assets.ts * * @param request the request to handle * @returns the response to return to the caller if the request was for an inline asset one (and the file exists), null otherwise */ async function handleInlineAssetRequest(request: Request) { if (request.url.startsWith('blob:')) { try { const url = new URL(request.url); const moduleName = `./__next-on-pages-dist__/assets/${url.pathname}.bin`; const binaryContent = (await doImport(moduleName)).default; // Note: we can't generate a real Response object here because this fetch might be called // at the top level of a dynamically imported module, and such cases produce the following // error: // Some functionality, such as asynchronous I/O, timeouts, and generating random values, // can only be performed while handling a request // this is a somewhat known workerd behavior (currently kept for security and performance reasons) // // if the above issue/constraint were to change we should replace the following with a real Response object const resp = { async arrayBuffer() { return binaryContent; }, get body(): ReadableStream<unknown> | null { return new ReadableStream({ start(controller) { const b = Buffer.from(binaryContent); controller.enqueue(b); controller.close(); }, }); }, async text() { const b = Buffer.from(binaryContent); return b.toString(); }, async json() { const b = Buffer.from(binaryContent); return JSON.stringify(b.toString()); }, async blob() { return new Blob(binaryContent); }, } as Response; // Note: clone is necessary so that body does work resp.clone = (): Response => { return { ...resp } as Response; }; return resp; } catch { /* empty */ } } return null; } /** * updates the provided request by adding a Next.js specific user-agent header if the request has no user-agent header * * Note: this is done by the Vercel network, but also in their next dev server as you can see here: * https://github.com/vercel/next.js/blob/6705c803021d3bdea7fec20e5d98f6899e49836d/packages/next/src/server/web/sandbox/context.ts#L318-L320) * @param request the request to update */ function setRequestUserAgentIfNeeded( request: Request<unknown, RequestInitCfProperties>, ): void { if (!request.headers.has('user-agent')) { request.headers.set(`user-agent`, `Next.js Middleware`); } } const patchFlagSymbol = Symbol.for('next-on-pages fetch patch'); type GlobalWithPatchSymbol = typeof globalThis & { [patchFlagSymbol]: boolean };