Skip to content

Commit 5b41907

Browse files
committed
handle preloading
1 parent 1217fa3 commit 5b41907

File tree

7 files changed

+79
-3
lines changed

7 files changed

+79
-3
lines changed

.changeset/warm-pans-argue.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22
"@opennextjs/aws": patch
33
---
44

5-
patch code to avoid useless import at runtime
5+
Some perf improvements :
6+
- Eliminate unnecessary runtime imports.
7+
- Refactor route preloading to be either on-demand or using waitUntil or at the start or during warmerEvent.
8+
- Add a global function to preload routes when needed.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export async function createMainHandler() {
2626
globalThis.serverId = generateUniqueId();
2727
globalThis.openNextConfig = config;
2828

29+
await globalThis.__next_route_preloader("start");
30+
2931
// Default queue
3032
globalThis.queue = await resolveQueue(thisFunction.override?.queue);
3133

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export async function openNextHandler(
4747
waitUntil: options?.waitUntil,
4848
},
4949
async () => {
50+
await globalThis.__next_route_preloader("waitUntil");
5051
if (initialHeaders["x-forwarded-host"]) {
5152
initialHeaders.host = initialHeaders["x-forwarded-host"];
5253
}

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
// @ts-ignore
77
import NextServer from "next/dist/server/next-server.js";
88

9-
import { debug } from "../adapters/logger.js";
9+
import { debug, error } from "../adapters/logger.js";
1010
import {
1111
applyOverride as applyNextjsRequireHooksOverride,
1212
overrideHooks as overrideNextjsRequireHooks,
@@ -59,6 +59,51 @@ const nextServer = new NextServer.default({
5959
dir: __dirname,
6060
});
6161

62+
let alreadyLoaded = false;
63+
64+
globalThis.__next_route_preloader = async (stage) => {
65+
if (alreadyLoaded) {
66+
return;
67+
}
68+
const thisFunction = globalThis.fnName
69+
? globalThis.openNextConfig.functions![globalThis.fnName]
70+
: globalThis.openNextConfig.default;
71+
const routePreloadingBehavior =
72+
thisFunction?.routePreloadingBehavior ?? "none";
73+
if (routePreloadingBehavior === "none") {
74+
alreadyLoaded = true;
75+
return;
76+
}
77+
if (!("unstable_preloadEntries" in nextServer)) {
78+
debug(
79+
"The current version of Next.js does not support route preloading. Skipping route preloading.",
80+
);
81+
alreadyLoaded = true;
82+
return;
83+
}
84+
if (stage === "waitUntil" && routePreloadingBehavior === "withWaitUntil") {
85+
// We need to access the waitUntil
86+
const waitUntil = globalThis.__openNextAls.getStore()?.waitUntil;
87+
if (!waitUntil) {
88+
error(
89+
"You've tried to use the 'withWaitUntil' route preloading behavior, but the 'waitUntil' function is not available.",
90+
);
91+
}
92+
debug("Preloading entries with waitUntil");
93+
waitUntil?.(nextServer.unstable_preloadEntries());
94+
alreadyLoaded = true;
95+
} else if (
96+
(stage === "start" && routePreloadingBehavior === "onStart") ||
97+
(stage === "warmerEvent" && routePreloadingBehavior === "onWarmerEvent") ||
98+
stage === "onDemand"
99+
) {
100+
const startTimestamp = Date.now();
101+
debug("Preloading entries");
102+
await nextServer.unstable_preloadEntries();
103+
debug("Preloading entries took", Date.now() - startTimestamp, "ms");
104+
alreadyLoaded = true;
105+
}
106+
};
62107
// `getRequestHandlerWithMetadata` is not available in older versions of Next.js
63108
// It is required to for next 15.2 to pass metadata for page router data route
64109
export const requestHandler = (metadata: Record<string, any>) =>

packages/open-next/src/overrides/wrappers/aws-lambda-streaming.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const handler: WrapperHandler = async (handler, converter) =>
3535
if ("type" in event) {
3636
const result = await formatWarmerResponse(event);
3737
responseStream.end(Buffer.from(JSON.stringify(result)), "utf-8");
38+
await globalThis.__next_route_preloader("warmerEvent");
3839
return;
3940
}
4041

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,13 @@ declare global {
211211
* Defined in `createMainHandler`
212212
*/
213213
var cdnInvalidationHandler: CDNInvalidationHandler;
214+
215+
/**
216+
* A function to preload the routes.
217+
* This needs to be defined on globalThis because it can be used by custom overrides.
218+
* Only available in main functions.
219+
*/
220+
var __next_route_preloader: (
221+
stage: "waitUntil" | "start" | "warmerEvent" | "onDemand",
222+
) => Promise<void>;
214223
}

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ export interface ResolvedRoute {
118118
type: RouteType;
119119
}
120120

121+
export type RoutePreloadingBehavior =
122+
| "none"
123+
| "withWaitUntil"
124+
| "onWarmerEvent"
125+
| "onStart";
126+
121127
export interface RoutingResult {
122128
internalEvent: InternalEvent;
123129
// If the request is an external rewrite, if used with an external middleware will be false on every server function
@@ -170,7 +176,6 @@ export type IncludedWarmer = "aws-lambda" | "dummy";
170176
export type IncludedProxyExternalRequest = "node" | "fetch" | "dummy";
171177

172178
export type IncludedCDNInvalidationHandler = "cloudfront" | "dummy";
173-
174179
export interface DefaultOverrideOptions<
175180
E extends BaseEventOrResult = InternalEvent,
176181
R extends BaseEventOrResult = InternalResult,
@@ -313,6 +318,16 @@ export interface FunctionOptions extends DefaultFunctionOptions {
313318
* @deprecated This is not supported in 14.2+
314319
*/
315320
experimentalBundledNextServer?: boolean;
321+
322+
/**
323+
* The route preloading behavior. Only supported in Next 15+.
324+
* - "none" - No preloading of the route at all
325+
* - "withWaitUntil" - Preload the route using the waitUntil provided by the wrapper - If not supported, it will fallback to "none"
326+
* - "onWarmerEvent" - Preload the route on the warmer event - Needs to be implemented by the wrapper. Only supported in `aws-lambda-streaming` wrapper for now
327+
* - "onStart" - Preload the route before even invoking the wrapper - This is a blocking operation and if not used properly, may increase the cold start time by a lot
328+
* @default "none"
329+
*/
330+
routePreloadingBehavior?: RoutePreloadingBehavior;
316331
}
317332

318333
export type RouteTemplate =

0 commit comments

Comments
 (0)