import type { Context } from '@netlify/edge-functions' import type { NextURL } from 'next/dist/server/web/next-url' import { NextResponse } from 'next/server' import type { NextRequest as InternalNextRequest } from 'next/server' import { MiddlewareResponse } from './response' export type NextRequest = InternalNextRequest & { get geo(): { timezone?: string } } export interface NextOptions { /** * Include conditional request headers in the request to the origin. * If you do this, you must ensure you check the response for a 304 Not Modified response * and handle it and the missing bode accordingly. */ sendConditionalRequest?: boolean } /** * Supercharge your Next middleware with Netlify Edge Functions */ export class MiddlewareRequest extends Request { context: Context originalRequest: Request constructor(private nextRequest: NextRequest) { super(nextRequest) if (!('Deno' in globalThis)) { throw new Error('MiddlewareRequest only works in a Netlify Edge Function environment') } const requestId = nextRequest.headers.get('x-nf-request-id') if (!requestId) { throw new Error('Missing x-nf-request-id header') } const requestContext = globalThis.NFRequestContextMap.get(requestId) if (!requestContext) { throw new Error(`Could not find request context for request id ${requestId}`) } this.context = requestContext.context this.originalRequest = requestContext.request } // Add the headers to the original request, which will be passed to the origin private applyHeaders() { this.headers.forEach((value, name) => { this.originalRequest.headers.set(name, value) }) } /** * Passes the request to the origin, allowing you to access the response */ async next(options?: NextOptions): Promise { this.applyHeaders() let response = await this.context.next(options) // Because our cdn lowercases urls, this gets problematic when trying to add redirects // This intercepts that redirect loop and rewrites the lowercase version so that the i18n url serves the right content. const locationHeader = response.headers.get('location') if (response.status === 301 && locationHeader?.startsWith('/')) { response = await this.context.rewrite(locationHeader) } return new MiddlewareResponse(response) } rewrite(destination: string | URL | NextURL, init?: ResponseInit): NextResponse { if (typeof destination === 'string' && destination.startsWith('/')) { destination = new URL(destination, this.url) } this.applyHeaders() return NextResponse.rewrite(destination, init) } get headers() { return this.nextRequest.headers } get cookies() { return this.nextRequest.cookies } get geo() { return this.nextRequest.geo } get ip() { return this.nextRequest.ip } get nextUrl() { return this.nextRequest.nextUrl } get url() { return this.nextRequest.url.toString() } }