Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/warm-pans-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@opennextjs/aws": patch
---

Some perf improvements :
- Eliminate unnecessary runtime imports.
- Refactor route preloading to be either on-demand or using waitUntil or at the start or during warmerEvent.
- Add a global function to preload routes when needed.
8 changes: 6 additions & 2 deletions packages/open-next/src/build/createServerBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import * as buildHelper from "./helper.js";
import { installDependencies } from "./installDeps.js";
import { type CodePatcher, applyCodePatches } from "./patch/codePatcher.js";
import {
patchEnvVars,
patchFetchCacheForISR,
patchFetchCacheSetMissingWaitUntil,
patchNextServer,
patchUnstableCacheForISR,
} from "./patch/patchFetchCacheISR.js";
import { patchFetchCacheSetMissingWaitUntil } from "./patch/patchFetchCacheWaitUntil.js";
} from "./patch/patches/index.js";

interface CodeCustomization {
// These patches are meant to apply on user and next generated code
Expand Down Expand Up @@ -187,6 +189,8 @@ async function generateBundle(
patchFetchCacheSetMissingWaitUntil,
patchFetchCacheForISR,
patchUnstableCacheForISR,
patchNextServer,
patchEnvVars,
...additionalCodePatches,
]);

Expand Down
4 changes: 4 additions & 0 deletions packages/open-next/src/build/patch/patches/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./patchEnvVar.js";
export * from "./patchNextServer.js";
export * from "./patchFetchCacheISR.js";
export * from "./patchFetchCacheWaitUntil.js";
46 changes: 46 additions & 0 deletions packages/open-next/src/build/patch/patches/patchEnvVar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createPatchCode } from "../astCodePatcher.js";
import type { CodePatcher } from "../codePatcher";

export const envVarRuleCreator = (envVar: string, value: string) => `
rule:
kind: member_expression
pattern: process.env.${envVar}
inside:
kind: if_statement
stopBy: end
fix:
'${value}'
`;

export const patchEnvVars: CodePatcher = {
name: "patch-env-vars",
patches: [
{
versions: ">=15.0.0",
field: {
pathFilter: /module\.compiled\.js$/,
contentFilter: /process\.env\.NEXT_RUNTIME/,
patchCode: createPatchCode(envVarRuleCreator("NEXT_RUNTIME", '"node"')),
},
},
{
versions: ">=15.0.0",
field: {
pathFilter:
/(module\.compiled|react\/index|react\/jsx-runtime|react-dom\/index)\.js$/,
contentFilter: /process\.env\.NODE_ENV/,
patchCode: createPatchCode(
envVarRuleCreator("NODE_ENV", '"production"'),
),
},
},
{
versions: ">=15.0.0",
field: {
pathFilter: /module\.compiled\.js$/,
contentFilter: /process\.env\.TURBOPACK/,
patchCode: createPatchCode(envVarRuleCreator("TURBOPACK", "false")),
},
},
],
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Lang } from "@ast-grep/napi";
import { getCrossPlatformPathRegex } from "utils/regex.js";
import { createPatchCode } from "./astCodePatcher.js";
import type { CodePatcher } from "./codePatcher";
import { createPatchCode } from "../astCodePatcher.js";
import type { CodePatcher } from "../codePatcher.js";

export const fetchRule = `
rule:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getCrossPlatformPathRegex } from "utils/regex.js";
import { createPatchCode } from "./astCodePatcher.js";
import type { CodePatcher } from "./codePatcher";
import { createPatchCode } from "../astCodePatcher.js";
import type { CodePatcher } from "../codePatcher.js";

export const rule = `
rule:
Expand Down
91 changes: 91 additions & 0 deletions packages/open-next/src/build/patch/patches/patchNextServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { createPatchCode } from "../astCodePatcher.js";
import type { CodePatcher } from "../codePatcher.js";

export const minimalRule = `
rule:
kind: member_expression
pattern: process.env.NEXT_MINIMAL
any:
- inside:
kind: parenthesized_expression
stopBy: end
inside:
kind: if_statement
any:
- inside:
kind: statement_block
inside:
kind: method_definition
any:
- has: {kind: property_identifier, field: name, regex: runEdgeFunction}
- has: {kind: property_identifier, field: name, regex: runMiddleware}
- has: {kind: property_identifier, field: name, regex: imageOptimizer}
- has:
kind: statement_block
has:
kind: expression_statement
pattern: res.statusCode = 400;
fix:
'true'
`;

export const disablePreloadingRule = `
rule:
kind: statement_block
inside:
kind: if_statement
any:
- has:
kind: member_expression
pattern: this.nextConfig.experimental.preloadEntriesOnStart
stopBy: end
- has:
kind: binary_expression
pattern: appDocumentPreloading === true
stopBy: end
fix:
'{}'
`;

// This rule is mostly for splitted edge functions so that we don't try to match them on the other non edge functions
export const removeMiddlewareManifestRule = `
rule:
kind: statement_block
inside:
kind: method_definition
has:
kind: property_identifier
regex: getMiddlewareManifest
fix:
'{return null;}'
`;

export const patchNextServer: CodePatcher = {
name: "patch-next-server",
patches: [
{
versions: ">=15.0.0",
field: {
pathFilter: /next-server\.(js)$/,
contentFilter: /process\.env\.NEXT_MINIMAL/,
patchCode: createPatchCode(minimalRule),
},
},
{
versions: ">=15.0.0",
field: {
pathFilter: /next-server\.(js)$/,
contentFilter: /this\.nextConfig\.experimental\.preloadEntriesOnStart/,
patchCode: createPatchCode(disablePreloadingRule),
},
},
{
versions: ">=15.0.0",
field: {
pathFilter: /next-server\.(js)$/,
contentFilter: /getMiddlewareManifest/,
patchCode: createPatchCode(removeMiddlewareManifestRule),
},
},
],
};
2 changes: 2 additions & 0 deletions packages/open-next/src/core/createMainHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export async function createMainHandler() {
globalThis.serverId = generateUniqueId();
globalThis.openNextConfig = config;

await globalThis.__next_route_preloader("start");

// Default queue
globalThis.queue = await resolveQueue(thisFunction.override?.queue);

Expand Down
1 change: 1 addition & 0 deletions packages/open-next/src/core/requestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function openNextHandler(
waitUntil: options?.waitUntil,
},
async () => {
await globalThis.__next_route_preloader("waitUntil");
if (initialHeaders["x-forwarded-host"]) {
initialHeaders.host = initialHeaders["x-forwarded-host"];
}
Expand Down
47 changes: 46 additions & 1 deletion packages/open-next/src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
// @ts-ignore
import NextServer from "next/dist/server/next-server.js";

import { debug } from "../adapters/logger.js";
import { debug, error } from "../adapters/logger.js";
import {
applyOverride as applyNextjsRequireHooksOverride,
overrideHooks as overrideNextjsRequireHooks,
Expand Down Expand Up @@ -59,6 +59,51 @@ const nextServer = new NextServer.default({
dir: __dirname,
});

let alreadyLoaded = false;

globalThis.__next_route_preloader = async (stage) => {
if (alreadyLoaded) {
return;
}
const thisFunction = globalThis.fnName
? globalThis.openNextConfig.functions![globalThis.fnName]
: globalThis.openNextConfig.default;
const routePreloadingBehavior =
thisFunction?.routePreloadingBehavior ?? "none";
if (routePreloadingBehavior === "none") {
alreadyLoaded = true;
return;
}
if (!("unstable_preloadEntries" in nextServer)) {
debug(
"The current version of Next.js does not support route preloading. Skipping route preloading.",
);
alreadyLoaded = true;
return;
}
if (stage === "waitUntil" && routePreloadingBehavior === "withWaitUntil") {
// We need to access the waitUntil
const waitUntil = globalThis.__openNextAls.getStore()?.waitUntil;
if (!waitUntil) {
error(
"You've tried to use the 'withWaitUntil' route preloading behavior, but the 'waitUntil' function is not available.",
);
}
debug("Preloading entries with waitUntil");
waitUntil?.(nextServer.unstable_preloadEntries());
alreadyLoaded = true;
} else if (
(stage === "start" && routePreloadingBehavior === "onStart") ||
(stage === "warmerEvent" && routePreloadingBehavior === "onWarmerEvent") ||
stage === "onDemand"
) {
const startTimestamp = Date.now();
debug("Preloading entries");
await nextServer.unstable_preloadEntries();
debug("Preloading entries took", Date.now() - startTimestamp, "ms");
alreadyLoaded = true;
}
};
// `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>) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const handler: WrapperHandler = async (handler, converter) =>
if ("type" in event) {
const result = await formatWarmerResponse(event);
responseStream.end(Buffer.from(JSON.stringify(result)), "utf-8");
await globalThis.__next_route_preloader("warmerEvent");
return;
}

Expand Down
9 changes: 9 additions & 0 deletions packages/open-next/src/types/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,13 @@ declare global {
* Defined in `createMainHandler`
*/
var cdnInvalidationHandler: CDNInvalidationHandler;

/**
* A function to preload the routes.
* This needs to be defined on globalThis because it can be used by custom overrides.
* Only available in main functions.
*/
var __next_route_preloader: (
stage: "waitUntil" | "start" | "warmerEvent" | "onDemand",
) => Promise<void>;
}
17 changes: 16 additions & 1 deletion packages/open-next/src/types/open-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ export interface ResolvedRoute {
type: RouteType;
}

export type RoutePreloadingBehavior =
| "none"
| "withWaitUntil"
| "onWarmerEvent"
| "onStart";

export interface RoutingResult {
internalEvent: InternalEvent;
// If the request is an external rewrite, if used with an external middleware will be false on every server function
Expand Down Expand Up @@ -170,7 +176,6 @@ export type IncludedWarmer = "aws-lambda" | "dummy";
export type IncludedProxyExternalRequest = "node" | "fetch" | "dummy";

export type IncludedCDNInvalidationHandler = "cloudfront" | "dummy";

export interface DefaultOverrideOptions<
E extends BaseEventOrResult = InternalEvent,
R extends BaseEventOrResult = InternalResult,
Expand Down Expand Up @@ -313,6 +318,16 @@ export interface FunctionOptions extends DefaultFunctionOptions {
* @deprecated This is not supported in 14.2+
*/
experimentalBundledNextServer?: boolean;

/**
* The route preloading behavior. Only supported in Next 15+.
* - "none" - No preloading of the route at all
* - "withWaitUntil" - Preload the route using the waitUntil provided by the wrapper - If not supported, it will fallback to "none"
* - "onWarmerEvent" - Preload the route on the warmer event - Needs to be implemented by the wrapper. Only supported in `aws-lambda-streaming` wrapper for now
* - "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
* @default "none"
*/
routePreloadingBehavior?: RoutePreloadingBehavior;
}

export type RouteTemplate =
Expand Down
Loading
Loading