Skip to content

Commit

Permalink
Fix for page router data json in next 15.2 (#756)
Browse files Browse the repository at this point in the history
* fix page router

* add additional request metadata

* changeset

* fix unit test

* Update packages/open-next/src/adapters/middleware.ts

Co-authored-by: Victor Berchet <victor@suumit.com>

* review fix

---------

Co-authored-by: Victor Berchet <victor@suumit.com>
  • Loading branch information
conico974 and vicb authored Feb 27, 2025
1 parent fe0e45a commit bfef363
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 72 deletions.
9 changes: 9 additions & 0 deletions .changeset/light-parrots-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@opennextjs/aws": minor
---

fix page router json data for next 15.2

This PR also use `getRequestHandlerWithMetadata` instead of `getRequestHandler` to allow assign metadata to the request.

BREAKING CHANGE: `MiddlewareResult` now contains `initialURL` instead of `initialPath`
13 changes: 7 additions & 6 deletions packages/open-next/src/adapters/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "../core/resolve";
import { constructNextUrl } from "../core/routing/util";
import routingHandler, {
INTERNAL_HEADER_INITIAL_PATH,
INTERNAL_HEADER_INITIAL_URL,
INTERNAL_HEADER_RESOLVED_ROUTES,
} from "../core/routingHandler";

Expand Down Expand Up @@ -70,15 +70,16 @@ const defaultHandler = async (
...result.internalEvent,
headers: {
...result.internalEvent.headers,
[INTERNAL_HEADER_INITIAL_PATH]: internalEvent.rawPath,
[INTERNAL_HEADER_RESOLVED_ROUTES]:
JSON.stringify(result.resolvedRoutes) ?? "[]",
[INTERNAL_HEADER_INITIAL_URL]: internalEvent.url,
[INTERNAL_HEADER_RESOLVED_ROUTES]: JSON.stringify(
result.resolvedRoutes,
),
},
},
isExternalRewrite: result.isExternalRewrite,
origin,
isISR: result.isISR,
initialPath: result.initialPath,
initialURL: result.initialURL,
resolvedRoutes: result.resolvedRoutes,
};
}
Expand All @@ -98,7 +99,7 @@ const defaultHandler = async (
isExternalRewrite: false,
origin: false,
isISR: result.isISR,
initialPath: result.internalEvent.rawPath,
initialURL: result.internalEvent.url,
resolvedRoutes: [{ route: "/500", type: "page" }],
};
}
Expand Down
56 changes: 45 additions & 11 deletions packages/open-next/src/core/requestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import type {
} from "types/open-next";
import { runWithOpenNextRequestContext } from "utils/promise";

import { NextConfig } from "config/index";
import type { OpenNextHandlerOptions } from "types/overrides";
import { debug, error, warn } from "../adapters/logger";
import { patchAsyncStorage } from "./patchAsyncStorage";
import {
constructNextUrl,
convertRes,
convertToQuery,
createServerResponse,
} from "./routing/util";
import routingHandler, {
INTERNAL_HEADER_INITIAL_PATH,
INTERNAL_HEADER_INITIAL_URL,
INTERNAL_HEADER_RESOLVED_ROUTES,
MIDDLEWARE_HEADER_PREFIX,
MIDDLEWARE_HEADER_PREFIX_LEN,
Expand Down Expand Up @@ -51,7 +53,7 @@ export async function openNextHandler(
// These 2 will get overwritten by the routing handler if not using an external middleware
const internalHeaders = {
initialPath:
initialHeaders[INTERNAL_HEADER_INITIAL_PATH] ?? internalEvent.rawPath,
initialHeaders[INTERNAL_HEADER_INITIAL_URL] ?? internalEvent.rawPath,
resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
: ([] as ResolvedRoute[]),
Expand All @@ -62,6 +64,7 @@ export async function openNextHandler(
isExternalRewrite: false,
origin: false,
isISR: false,
initialURL: internalEvent.url,
...internalHeaders,
};

Expand Down Expand Up @@ -115,7 +118,7 @@ export async function openNextHandler(
isExternalRewrite: false,
isISR: false,
origin: false,
initialPath: internalEvent.rawPath,
initialURL: internalEvent.url,
resolvedRoutes: [{ route: "/500", type: "page" }],
};
}
Expand All @@ -131,7 +134,7 @@ export async function openNextHandler(
isISR: false,
resolvedRoutes: [],
origin: false,
initialPath: internalEvent.rawPath,
initialURL: internalEvent.url,
},
headers,
options.streamCreator,
Expand Down Expand Up @@ -182,7 +185,7 @@ export async function openNextHandler(
options?.streamCreator,
);

await processRequest(req, res, preprocessedEvent);
await processRequest(req, res, routingResult);

const {
statusCode,
Expand All @@ -207,7 +210,7 @@ export async function openNextHandler(
async function processRequest(
req: IncomingMessage,
res: OpenNextNodeResponse,
internalEvent: InternalEvent,
routingResult: RoutingResult,
) {
// @ts-ignore
// Next.js doesn't parse body if the property exists
Expand All @@ -216,20 +219,44 @@ async function processRequest(

try {
//#override applyNextjsPrebundledReact
setNextjsPrebundledReact(internalEvent.rawPath);
setNextjsPrebundledReact(routingResult.internalEvent.rawPath);
//#endOverride

// Here we try to apply as much request metadata as possible
// We apply every metadata from `resolve-routes` https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-utils/resolve-routes.ts
// and `router-server` https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-server.ts
const initialURL = new URL(routingResult.initialURL);
let invokeStatus: number | undefined;
if (routingResult.internalEvent.rawPath === "/500") {
invokeStatus = 500;
} else if (routingResult.internalEvent.rawPath === "/404") {
invokeStatus = 404;
}
const requestMetadata = {
isNextDataReq: routingResult.internalEvent.query.__nextDataReq === "1",
initURL: routingResult.initialURL,
initQuery: convertToQuery(initialURL.search),
initProtocol: initialURL.protocol,
defaultLocale: NextConfig.i18n?.defaultLocale,
locale: routingResult.locale,
middlewareInvoke: false,
// By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
invokePath: routingResult.internalEvent.rawPath,
invokeQuery: routingResult.internalEvent.query,
// invokeStatus is only used for error pages
invokeStatus,
};
// Next Server
await requestHandler(req, res);
await requestHandler(requestMetadata)(req, res);
} catch (e: any) {
// This might fail when using bundled next, importing won't do the trick either
if (e.constructor.name === "NoFallbackError") {
// Do we need to handle _not-found
// Ideally this should never get triggered and be intercepted by the routing handler
await tryRenderError("404", res, internalEvent);
await tryRenderError("404", res, routingResult.internalEvent);
} else {
error("NextJS request failed.", e);
await tryRenderError("500", res, internalEvent);
await tryRenderError("500", res, routingResult.internalEvent);
}
}
}
Expand All @@ -247,7 +274,14 @@ async function tryRenderError(
body: internalEvent.body,
remoteAddress: internalEvent.remoteAddress,
});
await requestHandler(_req, res);
// By setting this it will allow us to bypass and directly render the 404 or 500 page
const requestMetadata = {
// By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
invokePath: type === "404" ? "/404" : "/500",
invokeStatus: type === "404" ? 404 : 500,
middlewareInvoke: false,
};
await requestHandler(requestMetadata)(_req, res);
} catch (e) {
error("NextJS request failed.", e);
res.setHeader("Content-Type", "application/json");
Expand Down
5 changes: 4 additions & 1 deletion packages/open-next/src/core/routing/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ function getLocaleFromCookie(cookies: Record<string, string>) {
: undefined;
}

function detectLocale(internalEvent: InternalEvent, i18n: i18nConfig): string {
export function detectLocale(
internalEvent: InternalEvent,
i18n: i18nConfig,
): string {
if (i18n.localeDetection === false) {
return i18n.defaultLocale;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/open-next/src/core/routing/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ export async function invalidateCDNOnRequest(
params: RoutingResult,
headers: OutgoingHttpHeaders,
) {
const { internalEvent, initialPath, resolvedRoutes } = params;
const { internalEvent, resolvedRoutes, initialURL } = params;
const initialPath = new URL(initialURL).pathname;
const isIsrRevalidation = internalEvent.headers["x-isr"] === "1";
if (
!isIsrRevalidation &&
Expand Down
10 changes: 8 additions & 2 deletions packages/open-next/src/core/routingHandler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
BuildId,
ConfigHeaders,
NextConfig,
PrerenderManifest,
RoutesManifest,
} from "config/index";
Expand All @@ -13,6 +14,7 @@ import type {

import { debug } from "../adapters/logger";
import { cacheInterceptor } from "./routing/cacheInterceptor";
import { detectLocale } from "./routing/i18n";
import {
fixDataPage,
getNextConfigHeaders,
Expand All @@ -31,7 +33,8 @@ import { constructNextUrl } from "./routing/util";
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
export const INTERNAL_HEADER_INITIAL_PATH = `${INTERNAL_HEADER_PREFIX}initial-path`;
export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;

// Geolocation headers starting from Nextjs 15
Expand Down Expand Up @@ -221,7 +224,10 @@ export default async function routingHandler(
isExternalRewrite,
origin: false,
isISR,
initialPath: event.rawPath,
resolvedRoutes,
initialURL: event.url,
locale: NextConfig.i18n
? detectLocale(internalEvent, NextConfig.i18n)
: undefined,
};
}
11 changes: 9 additions & 2 deletions packages/open-next/src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ applyNextjsRequireHooksOverride();
//#endOverride
const cacheHandlerPath = require.resolve("./cache.cjs");
// @ts-ignore
export const requestHandler = new NextServer.default({
const nextServer = new NextServer.default({
//#override requestHandlerHost
hostname: "localhost",
port: 3000,
Expand Down Expand Up @@ -57,7 +57,14 @@ export const requestHandler = new NextServer.default({
customServer: false,
dev: false,
dir: __dirname,
}).getRequestHandler();
});

// `getRequestHandlerWithMetadata` is not available in older versions of Next.js
// It is required to for next 15.2 to pass metadata for page router data route
export const requestHandler = (metadata: Record<string, any>) =>
"getRequestHandlerWithMetadata" in nextServer
? nextServer.getRequestHandlerWithMetadata(metadata)
: nextServer.getRequestHandler();

//#override setNextjsPrebundledReact
export function setNextjsPrebundledReact(rawPath: string) {
Expand Down
7 changes: 5 additions & 2 deletions packages/open-next/src/types/open-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,11 @@ export interface RoutingResult {
origin: Origin | false;
// If the request is for an ISR route, will be false on every server function. Only used in external middleware
isISR: boolean;
// The initial rawPath of the request before applying rewrites, if used with an external middleware will be defined in x-opennext-initial-path header
initialPath: string;
// The initial URL of the request before applying rewrites, if used with an external middleware will be defined in x-opennext-initial-url header
initialURL: string;

// The locale of the request, if used with an external middleware will be defined in x-opennext-locale header
locale?: string;

// The resolved route after applying rewrites, if used with an external middleware will be defined in x-opennext-resolved-routes header as a json encoded array
resolvedRoutes: ResolvedRoute[];
Expand Down
4 changes: 3 additions & 1 deletion packages/tests-unit/tests/core/routing/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ describe("invalidateCDNOnRequest", () => {
internalEvent: {
headers: {},
},
initialURL: "http://localhost/path",
},
headers,
);
Expand All @@ -767,6 +768,7 @@ describe("invalidateCDNOnRequest", () => {
"x-isr": "1",
},
},
initialURL: "http://localhost/path",
},
headers,
);
Expand All @@ -782,7 +784,6 @@ describe("invalidateCDNOnRequest", () => {
};
await invalidateCDNOnRequest(
{
initialPath: "/path",
internalEvent: {
rawPath: "/path",
headers: {},
Expand All @@ -793,6 +794,7 @@ describe("invalidateCDNOnRequest", () => {
route: "/path",
},
],
initialURL: "http://localhost/path",
},
headers,
);
Expand Down
Loading

0 comments on commit bfef363

Please sign in to comment.