在某些情况下,简单的示例是不够的,您希望为服务器添加更多自定义功能。这就是懒加载覆盖(lazy loaded overrides)发挥作用的地方。您可以通过提供一个返回承诺(promise)的函数来覆盖服务器的任何部分,该承诺解析为覆盖对象。当您想为服务器添加自定义逻辑时,这很有用,比如添加自定义队列,或添加自定义转换器。
如果您使用 edge runtime(无论是在函数中还是通过使用外部中间件),请小心,我们会对 open-next.config.ts 进行 2 次编译,一次用于 node,一次用于 edge runtime。如果您使用了一些自定义覆盖,您可能想要添加
edgeExternals: ['./customWrapper', './anyOtherOverrideUsed']到您的 open-next.config.ts 以避免 edge runtime 尝试编译与 edge runtime 不兼容的覆盖。
自定义转换器
有时您可能想要修改 OpenNext 接收到的对象。例如,来自 SST 的 Config.YOUR_SECRET_KEY 不能在中间件中使用,所以您可能想要将其添加到 headers 中。这就是自定义转换器的作用所在。您可以在对象传递给 OpenNext 之前添加自定义转换器来修改它。
在开发期间您仍然需要使用回退值,因为开发服务器不使用这个。
// customConverter.ts
import converter from "@opennextjs/aws/overrides/converters/aws-apigw-v2.js";
import type { Converter } from "@opennextjs/aws/types/overrides.js";
import { Config } from "sst/node/Config";
const mySecretKey = Config.YOUR_SECRET_KEY;
export default {
convertFrom: async (event) => {
const result = await converter.convertFrom(event);
return {
...result,
headers: {
...result.headers,
"inserted-in-converter": "1",
"my-super-secret-key": mySecretKey,
},
};
},
convertTo: async (intResult) => {
const result = await converter.convertTo(intResult);
return {
...result,
headers: {
...result.headers,
"x-converter-end": "1",
},
};
},
name: "custom-apigw-v2",
} as Converter;// open-next.config.ts
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {
override: {
converter: () => import("./customConverter").then((mod) => mod.default),
},
},
} as OpenNextConfig;自定义包装器
这里我们提供一些自定义包装器的示例。
定义一个全局变量以便在中间件中使用 node
// customWrapper.ts
import defaultWrapper from "@opennextjs/aws/overrides/wrappers/aws-lambda.js";
// 在这里你可以定义一些全局变量
declare global {
var myApi: () => Promise<number>;
}
globalThis.myApi = async () => {
const crypto = await import("crypto");
return {
nb: crypto.randomInt(0, 100),
};
};
export default defaultWrapper;// open-next.config.ts
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {
override: {
wrapper: () => import("./customWrapper").then((mod) => mod.default),
},
},
} as OpenNextConfig;
export default config;但由于 Next 开发服务器运行在假的 edge runtime 中,且全局变量仅在部署时定义,您必须在中间件中模拟该全局变量。
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// 如果不存在,这里你需要模拟全局变量
// 避免不同实现出现问题的一种方法是创建一个 api 端点
// 它使用与你之前定义的全局变量完全相同的逻辑,
// 并且仅在开发期间可用,例如 /api/dev/myApi
if (!globalThis.myApi) {
globalThis.myApi = async () => {
return await fetch("http://localhost:3000/api/dev/myApi").then((res) => res.json());
};
}
export function middleware(request: NextRequest) {
// 你也可以在 api 端点本身发送错误
// 或者你可以将所有开发端点添加到它们自己的 lambda 中
// 这样你就不会在生产环境中部署它们
if (request.nextUrl.pathname.startsWith("/api/dev") && process.env.NODE_ENV === "production") {
return NextResponse("This route is only available in development", {
status: 500,
});
}
// 现在你可以在中间件中使用 Node.js 了
const { nb } = await myApi();
// ... your code here
}在包装器中使用 middy.js
// customWrapper.ts
import streamingWrapper from "@opennextjs/aws/overrides/wrappers/aws-lambda.js";
import type { WrapperHandler } from "@opennextjs/aws/types/overrides.js";
import middy from "@middy/core";
import httpSecurityHeaders from "@middy/http-security-headers";
const handler: WrapperHandler = async (handler, converter) => {
const defaultHandler = await streamingWrapper.wrapper(handler, converter);
return middy().use(httpSecurityHeaders()).handler(defaultHandler);
};
export default {
wrapper: handler,
name: "custom-aws-lambda",
supportStreaming: false,
};// open-next.config.ts
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {
override: {
wrapper: () => import("./customWrapper").then((mod) => mod.default),
},
},
} as OpenNextConfig;
export default config;在 warmer 事件期间预加载一些路由
在此示例中,自定义包装器用于在第一个请求之前预加载一些重要的路由。如果您有一些在冷启动时较慢的路由(Next 仅在需要时懒加载路由),并且您想在第一个请求之前预加载它们,这很有用。如果您想为服务器添加一些自定义逻辑,比如向响应添加自定义头,这也很有用。
警告:这个示例没有经过适当测试。这只是您可以做什么的一个示例。在生产环境中使用之前,您应该对其进行适当测试。此外,预加载太多路由可能不是一个好主意。
// customWrapper.ts
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
import { Writable } from "node:stream";
import { WarmerEvent, WarmerResponse } from "@opennextjs/aws/adapters/warmer-function.js";
import type { StreamCreator } from "@opennextjs/aws/types/open-next.js";
import type { WrapperHandler } from "@opennextjs/aws/types/overrides.js";
type AwsLambdaEvent = APIGatewayProxyEventV2 | WarmerEvent;
type AwsLambdaReturn = APIGatewayProxyResultV2 | WarmerResponse;
const serverId = Math.random().toPrecision(5).toString();
let isPreloaded = false;
function formatWarmerResponse(event: WarmerEvent) {
return new Promise<WarmerResponse>((resolve) => {
setTimeout(() => {
resolve({ serverId, type: "warmer" } satisfies WarmerResponse);
}, event.delay);
});
}
const handler: WrapperHandler =
async (handler, converter) =>
async (event: AwsLambdaEvent): Promise<AwsLambdaReturn> => {
console.log("custom wrapper");
// 处理 warmer 事件
if ("type" in event) {
if (!isPreloaded) {
// 你可以在这里预加载你想要的每个路由
// 小心,当路由预加载时,lambda 无法处理其他请求
await handler({
type: "core",
url: "/myRoute",
method: "GET",
headers: {},
query: {},
rawPath: "/myRoute",
cookies: {},
remoteAddress: "",
});
isPreloaded = true;
}
return formatWarmerResponse(event);
}
const internalEvent = await converter.convertFrom(event);
internalEvent.headers["inserted-in-wrapper"] = "hello from wrapper";
// 这是一个变通方法,node 中有一个问题,如果 OpenNextNodeResponse 流未被消耗,会导致 node 静默崩溃
// 这不会每次都发生,可能是由 ssr 中的 suspended 组件引起的(通过 <Suspense> 或 loading.tsx)
// 每个希望创建自己的包装器而不使用 StreamCreator 的人都应该实现这个变通方法
// 如果底层处理程序不使用 OpenNextNodeResponse,则不需要此操作(目前,OpenNextNodeResponse 由 node runtime 服务器和图像服务器使用)
const fakeStream: StreamCreator = {
writeHeaders: () => {
return new Writable({
write: (_chunk, _encoding, callback) => {
callback();
},
});
},
};
const response = await handler(internalEvent, { streamCreator: fakeStream });
response.headers["x-wrapper"] = "hi";
return converter.convertTo(response, event);
};
export default {
wrapper: handler,
name: "custom-aws-lambda",
supportStreaming: false,
};// open-next.config.ts
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {
override: {
wrapper: () => import("./customWrapper").then((mod) => mod.default),
},
},
} as OpenNextConfig;自定义增量缓存
您可以从我们的 fs-dev (opens in a new tab) 覆盖中获得灵感,它使用文件系统来存储增量缓存。您需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.ts";
const config = {
default: {
override: {
// 可以是我们包含的任何选项,也可以是你自己的自定义覆盖
incrementalCache: () => import("./customIncrementalCache").then((mod) => mod.default),
},
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 's3' | 's3-lite' | 'multi-tier-ddb-s3' | 'fs-dev' | 'dummy'
自定义队列
默认情况下,它将使用 SQS 队列来重新验证过时的路由。您可以在这里阅读更多相关信息。要创建您自己的自定义覆盖,您可以通过查看我们的 包含的 (opens in a new tab) 实现来获得灵感。您需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.ts";
const config = {
default: {
override: {
// 可以是我们包含的任何选项,也可以是你自己的自定义覆盖
queue: () => import("./customQueue").then((mod) => mod.default),
},
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 'sqs' | 'sqs-lite' | 'direct' | 'dummy'
自定义 Tag cache
要覆盖 tag cache,你可以参考使用文件系统的 fs-dev (opens in a new tab) 覆盖获取灵感。你可以 这里 阅读更多关于此覆盖的信息。你需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.ts";
const config = {
default: {
override: {
// 可以是我们要包含的任何一项,也可以是你自己的自定义覆盖
tagCache: () => import("./customTagCache").then((mod) => mod.default),
},
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 'dynamodb' | 'dynamodb-lite' | 'fs-dev' | 'dummy'
自定义 Origin Resolver
如果你有一个 external 中间件,此覆盖仅由 OpenNext 内部用于解析请求的源。你可以参考我们要包含的 pattern-env (opens in a new tab) 覆盖获取灵感。你需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {},
middleware: {
// 必须为 true 才能使用 originResolver
external: true,
// 可以是我们要包含的任何一项,也可以是你自己的自定义覆盖
originResolver: () => import("./customOriginResolver").then((mod) => mod.default),
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 'pattern-env' | 'dummy'
自定义 Image Loader
此覆盖用于图像优化服务器,从自定义源加载图像。你可以 这里 (opens in a new tab) 查看我们使用文件系统的实现。你需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {},
imageOptimization: {
loader: () => import("./customImageLoader").then((mod) => mod.default),
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 's3' | 's3-lite' | 'host' | 'fs-dev' | 'dummy'
自定义 Warmer Invoke
要为 warmer invoke 设置自定义覆盖,你可以参考我们的 aws-lambda (opens in a new tab) 覆盖获取灵感。你需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {},
warmer: {
invokeFunction: () => import("./customWarmer").then((mod) => mod.default),
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 'aws-lambda' | 'dummy'
自定义 CDN Invalidation
要为 CDN Invalidation 设置自定义覆盖,你可以参考我们的 cloudfront (opens in a new tab) 覆盖获取灵感。你需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {
override: {
cdnInvalidation: () => import("./customCdnInvalidation").then((mod) => mod.default),
},
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 'cloudfront' | 'dummy'
自定义 External Request Proxy
OpenNext 使用此项将重写的请求代理到外部服务。你可以 这里 阅读更多关于它的信息。要为 External Request Proxy 设置自定义覆盖,你可以参考我们的 fetch (opens in a new tab) 覆盖获取灵感。你需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {
override: {
proxyExternalRequest: () => import("./customProxyExternalRequest").then((mod) => mod.default),
},
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项有 'fetch' | 'node' | 'dummy'
自定义 Asset Resolver
OpenNext 使用此项来解析静态资源。你可以 这里 阅读更多关于它的信息。要为 Asset Resolver 设置自定义覆盖,你需要一个包含以下内容的 open-next.config.ts:
import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js";
const config = {
default: {},
middleware: {
assetResolver: import("./customAssetResolver").then((mod) => mod.default),
},
} satisfies OpenNextConfig;
export default config;包含的 (opens in a new tab) 选项是 'dummy'