Skip to content

Commit bfef363

Browse files
conico974vicb
andauthored
Fix for page router data json in next 15.2 (#756)
* fix page router * add additional request metadata * changeset * fix unit test * Update packages/open-next/src/adapters/middleware.ts Co-authored-by: Victor Berchet <[email protected]> * review fix --------- Co-authored-by: Victor Berchet <[email protected]>
1 parent fe0e45a commit bfef363

File tree

11 files changed

+160
-72
lines changed

11 files changed

+160
-72
lines changed

.changeset/light-parrots-walk.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@opennextjs/aws": minor
3+
---
4+
5+
fix page router json data for next 15.2
6+
7+
This PR also use `getRequestHandlerWithMetadata` instead of `getRequestHandler` to allow assign metadata to the request.
8+
9+
BREAKING CHANGE: `MiddlewareResult` now contains `initialURL` instead of `initialPath`

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
} from "../core/resolve";
1818
import { constructNextUrl } from "../core/routing/util";
1919
import routingHandler, {
20-
INTERNAL_HEADER_INITIAL_PATH,
20+
INTERNAL_HEADER_INITIAL_URL,
2121
INTERNAL_HEADER_RESOLVED_ROUTES,
2222
} from "../core/routingHandler";
2323

@@ -70,15 +70,16 @@ const defaultHandler = async (
7070
...result.internalEvent,
7171
headers: {
7272
...result.internalEvent.headers,
73-
[INTERNAL_HEADER_INITIAL_PATH]: internalEvent.rawPath,
74-
[INTERNAL_HEADER_RESOLVED_ROUTES]:
75-
JSON.stringify(result.resolvedRoutes) ?? "[]",
73+
[INTERNAL_HEADER_INITIAL_URL]: internalEvent.url,
74+
[INTERNAL_HEADER_RESOLVED_ROUTES]: JSON.stringify(
75+
result.resolvedRoutes,
76+
),
7677
},
7778
},
7879
isExternalRewrite: result.isExternalRewrite,
7980
origin,
8081
isISR: result.isISR,
81-
initialPath: result.initialPath,
82+
initialURL: result.initialURL,
8283
resolvedRoutes: result.resolvedRoutes,
8384
};
8485
}
@@ -98,7 +99,7 @@ const defaultHandler = async (
9899
isExternalRewrite: false,
99100
origin: false,
100101
isISR: result.isISR,
101-
initialPath: result.internalEvent.rawPath,
102+
initialURL: result.internalEvent.url,
102103
resolvedRoutes: [{ route: "/500", type: "page" }],
103104
};
104105
}

packages/open-next/src/core/requestHandler.ts

+45-11
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ import type {
1010
} from "types/open-next";
1111
import { runWithOpenNextRequestContext } from "utils/promise";
1212

13+
import { NextConfig } from "config/index";
1314
import type { OpenNextHandlerOptions } from "types/overrides";
1415
import { debug, error, warn } from "../adapters/logger";
1516
import { patchAsyncStorage } from "./patchAsyncStorage";
1617
import {
1718
constructNextUrl,
1819
convertRes,
20+
convertToQuery,
1921
createServerResponse,
2022
} from "./routing/util";
2123
import routingHandler, {
22-
INTERNAL_HEADER_INITIAL_PATH,
24+
INTERNAL_HEADER_INITIAL_URL,
2325
INTERNAL_HEADER_RESOLVED_ROUTES,
2426
MIDDLEWARE_HEADER_PREFIX,
2527
MIDDLEWARE_HEADER_PREFIX_LEN,
@@ -51,7 +53,7 @@ export async function openNextHandler(
5153
// These 2 will get overwritten by the routing handler if not using an external middleware
5254
const internalHeaders = {
5355
initialPath:
54-
initialHeaders[INTERNAL_HEADER_INITIAL_PATH] ?? internalEvent.rawPath,
56+
initialHeaders[INTERNAL_HEADER_INITIAL_URL] ?? internalEvent.rawPath,
5557
resolvedRoutes: initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES]
5658
? JSON.parse(initialHeaders[INTERNAL_HEADER_RESOLVED_ROUTES])
5759
: ([] as ResolvedRoute[]),
@@ -62,6 +64,7 @@ export async function openNextHandler(
6264
isExternalRewrite: false,
6365
origin: false,
6466
isISR: false,
67+
initialURL: internalEvent.url,
6568
...internalHeaders,
6669
};
6770

@@ -115,7 +118,7 @@ export async function openNextHandler(
115118
isExternalRewrite: false,
116119
isISR: false,
117120
origin: false,
118-
initialPath: internalEvent.rawPath,
121+
initialURL: internalEvent.url,
119122
resolvedRoutes: [{ route: "/500", type: "page" }],
120123
};
121124
}
@@ -131,7 +134,7 @@ export async function openNextHandler(
131134
isISR: false,
132135
resolvedRoutes: [],
133136
origin: false,
134-
initialPath: internalEvent.rawPath,
137+
initialURL: internalEvent.url,
135138
},
136139
headers,
137140
options.streamCreator,
@@ -182,7 +185,7 @@ export async function openNextHandler(
182185
options?.streamCreator,
183186
);
184187

185-
await processRequest(req, res, preprocessedEvent);
188+
await processRequest(req, res, routingResult);
186189

187190
const {
188191
statusCode,
@@ -207,7 +210,7 @@ export async function openNextHandler(
207210
async function processRequest(
208211
req: IncomingMessage,
209212
res: OpenNextNodeResponse,
210-
internalEvent: InternalEvent,
213+
routingResult: RoutingResult,
211214
) {
212215
// @ts-ignore
213216
// Next.js doesn't parse body if the property exists
@@ -216,20 +219,44 @@ async function processRequest(
216219

217220
try {
218221
//#override applyNextjsPrebundledReact
219-
setNextjsPrebundledReact(internalEvent.rawPath);
222+
setNextjsPrebundledReact(routingResult.internalEvent.rawPath);
220223
//#endOverride
221224

225+
// Here we try to apply as much request metadata as possible
226+
// 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
227+
// and `router-server` https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-server.ts
228+
const initialURL = new URL(routingResult.initialURL);
229+
let invokeStatus: number | undefined;
230+
if (routingResult.internalEvent.rawPath === "/500") {
231+
invokeStatus = 500;
232+
} else if (routingResult.internalEvent.rawPath === "/404") {
233+
invokeStatus = 404;
234+
}
235+
const requestMetadata = {
236+
isNextDataReq: routingResult.internalEvent.query.__nextDataReq === "1",
237+
initURL: routingResult.initialURL,
238+
initQuery: convertToQuery(initialURL.search),
239+
initProtocol: initialURL.protocol,
240+
defaultLocale: NextConfig.i18n?.defaultLocale,
241+
locale: routingResult.locale,
242+
middlewareInvoke: false,
243+
// By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
244+
invokePath: routingResult.internalEvent.rawPath,
245+
invokeQuery: routingResult.internalEvent.query,
246+
// invokeStatus is only used for error pages
247+
invokeStatus,
248+
};
222249
// Next Server
223-
await requestHandler(req, res);
250+
await requestHandler(requestMetadata)(req, res);
224251
} catch (e: any) {
225252
// This might fail when using bundled next, importing won't do the trick either
226253
if (e.constructor.name === "NoFallbackError") {
227254
// Do we need to handle _not-found
228255
// Ideally this should never get triggered and be intercepted by the routing handler
229-
await tryRenderError("404", res, internalEvent);
256+
await tryRenderError("404", res, routingResult.internalEvent);
230257
} else {
231258
error("NextJS request failed.", e);
232-
await tryRenderError("500", res, internalEvent);
259+
await tryRenderError("500", res, routingResult.internalEvent);
233260
}
234261
}
235262
}
@@ -247,7 +274,14 @@ async function tryRenderError(
247274
body: internalEvent.body,
248275
remoteAddress: internalEvent.remoteAddress,
249276
});
250-
await requestHandler(_req, res);
277+
// By setting this it will allow us to bypass and directly render the 404 or 500 page
278+
const requestMetadata = {
279+
// By setting invokePath and invokeQuery we can bypass some of the routing logic in Next.js
280+
invokePath: type === "404" ? "/404" : "/500",
281+
invokeStatus: type === "404" ? 404 : 500,
282+
middlewareInvoke: false,
283+
};
284+
await requestHandler(requestMetadata)(_req, res);
251285
} catch (e) {
252286
error("NextJS request failed.", e);
253287
res.setHeader("Content-Type", "application/json");

packages/open-next/src/core/routing/i18n/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ function getLocaleFromCookie(cookies: Record<string, string>) {
2020
: undefined;
2121
}
2222

23-
function detectLocale(internalEvent: InternalEvent, i18n: i18nConfig): string {
23+
export function detectLocale(
24+
internalEvent: InternalEvent,
25+
i18n: i18nConfig,
26+
): string {
2427
if (i18n.localeDetection === false) {
2528
return i18n.defaultLocale;
2629
}

packages/open-next/src/core/routing/util.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ export async function invalidateCDNOnRequest(
414414
params: RoutingResult,
415415
headers: OutgoingHttpHeaders,
416416
) {
417-
const { internalEvent, initialPath, resolvedRoutes } = params;
417+
const { internalEvent, resolvedRoutes, initialURL } = params;
418+
const initialPath = new URL(initialURL).pathname;
418419
const isIsrRevalidation = internalEvent.headers["x-isr"] === "1";
419420
if (
420421
!isIsrRevalidation &&

packages/open-next/src/core/routingHandler.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
BuildId,
33
ConfigHeaders,
4+
NextConfig,
45
PrerenderManifest,
56
RoutesManifest,
67
} from "config/index";
@@ -13,6 +14,7 @@ import type {
1314

1415
import { debug } from "../adapters/logger";
1516
import { cacheInterceptor } from "./routing/cacheInterceptor";
17+
import { detectLocale } from "./routing/i18n";
1618
import {
1719
fixDataPage,
1820
getNextConfigHeaders,
@@ -31,7 +33,8 @@ import { constructNextUrl } from "./routing/util";
3133
export const MIDDLEWARE_HEADER_PREFIX = "x-middleware-response-";
3234
export const MIDDLEWARE_HEADER_PREFIX_LEN = MIDDLEWARE_HEADER_PREFIX.length;
3335
export const INTERNAL_HEADER_PREFIX = "x-opennext-";
34-
export const INTERNAL_HEADER_INITIAL_PATH = `${INTERNAL_HEADER_PREFIX}initial-path`;
36+
export const INTERNAL_HEADER_INITIAL_URL = `${INTERNAL_HEADER_PREFIX}initial-url`;
37+
export const INTERNAL_HEADER_LOCALE = `${INTERNAL_HEADER_PREFIX}locale`;
3538
export const INTERNAL_HEADER_RESOLVED_ROUTES = `${INTERNAL_HEADER_PREFIX}resolved-routes`;
3639

3740
// Geolocation headers starting from Nextjs 15
@@ -221,7 +224,10 @@ export default async function routingHandler(
221224
isExternalRewrite,
222225
origin: false,
223226
isISR,
224-
initialPath: event.rawPath,
225227
resolvedRoutes,
228+
initialURL: event.url,
229+
locale: NextConfig.i18n
230+
? detectLocale(internalEvent, NextConfig.i18n)
231+
: undefined,
226232
};
227233
}

packages/open-next/src/core/util.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ applyNextjsRequireHooksOverride();
2626
//#endOverride
2727
const cacheHandlerPath = require.resolve("./cache.cjs");
2828
// @ts-ignore
29-
export const requestHandler = new NextServer.default({
29+
const nextServer = new NextServer.default({
3030
//#override requestHandlerHost
3131
hostname: "localhost",
3232
port: 3000,
@@ -57,7 +57,14 @@ export const requestHandler = new NextServer.default({
5757
customServer: false,
5858
dev: false,
5959
dir: __dirname,
60-
}).getRequestHandler();
60+
});
61+
62+
// `getRequestHandlerWithMetadata` is not available in older versions of Next.js
63+
// It is required to for next 15.2 to pass metadata for page router data route
64+
export const requestHandler = (metadata: Record<string, any>) =>
65+
"getRequestHandlerWithMetadata" in nextServer
66+
? nextServer.getRequestHandlerWithMetadata(metadata)
67+
: nextServer.getRequestHandler();
6168

6269
//#override setNextjsPrebundledReact
6370
export function setNextjsPrebundledReact(rawPath: string) {

packages/open-next/src/types/open-next.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,11 @@ export interface RoutingResult {
126126
origin: Origin | false;
127127
// If the request is for an ISR route, will be false on every server function. Only used in external middleware
128128
isISR: boolean;
129-
// The initial rawPath of the request before applying rewrites, if used with an external middleware will be defined in x-opennext-initial-path header
130-
initialPath: string;
129+
// The initial URL of the request before applying rewrites, if used with an external middleware will be defined in x-opennext-initial-url header
130+
initialURL: string;
131+
132+
// The locale of the request, if used with an external middleware will be defined in x-opennext-locale header
133+
locale?: string;
131134

132135
// 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
133136
resolvedRoutes: ResolvedRoute[];

packages/tests-unit/tests/core/routing/util.test.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ describe("invalidateCDNOnRequest", () => {
746746
internalEvent: {
747747
headers: {},
748748
},
749+
initialURL: "http://localhost/path",
749750
},
750751
headers,
751752
);
@@ -767,6 +768,7 @@ describe("invalidateCDNOnRequest", () => {
767768
"x-isr": "1",
768769
},
769770
},
771+
initialURL: "http://localhost/path",
770772
},
771773
headers,
772774
);
@@ -782,7 +784,6 @@ describe("invalidateCDNOnRequest", () => {
782784
};
783785
await invalidateCDNOnRequest(
784786
{
785-
initialPath: "/path",
786787
internalEvent: {
787788
rawPath: "/path",
788789
headers: {},
@@ -793,6 +794,7 @@ describe("invalidateCDNOnRequest", () => {
793794
route: "/path",
794795
},
795796
],
797+
initialURL: "http://localhost/path",
796798
},
797799
headers,
798800
);

0 commit comments

Comments
 (0)