在 Nx Monorepo 中配置 OpenNext
以下是详细的示例,展示如何在一个已有的 Nx 工作区中添加 OpenNext + SST,其中已有一个位于 apps/next-site
的 NextJS 应用。
-
安装
open-next
:pnpm add —save-dev @opennextjs/aws
-
更新
apps/next-site/next.config.js
,添加output: 'standalone'
,同时需要添加experimental.outputFileTracingRoot
,配置应类似如下:
//@ts-check
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { composePlugins, withNx } = require('@nx/next');
const { join } = require('node:path');
/**
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
**/
const nextConfig = {
nx: {
// 如果需要使用 SVGR 请设置为 true
// 参见: https://github.com/gregberge/svgr
svgr: false,
},
+ output: 'standalone',
+ experimental: {
+ // 这里应该是你仓库根目录的路径,本例中只需向上两级。OpenNext 需要这个配置来识别 monorepo
+ outputFileTracingRoot: join(__dirname, '../../'),
+ },
};
const plugins = [
// 如有需要可在此添加更多 Next.js 插件
withNx,
];
module.exports = composePlugins(...plugins)(nextConfig);
- 在应用根目录创建
open-next.config.ts
,内容类似如下:
import type { OpenNextConfig } from '@opennextjs/aws/types/open-next';
const config = {
default: {},
buildCommand: 'exit 0', // 在本例中我们通过 Nx 任务分发来处理构建顺序
buildOutputPath: '.',
appPath: '.',
packageJsonPath: '../../', // 再次指向仓库根目录(package.json 所在位置)
} satisfies OpenNextConfig;
export default config;
- 设置 Nx 的 targets/tasks
现在 OpenNext 配置已完成,你可以尝试运行 open-next build
,根据是否已构建过 Next 应用,它可能会直接工作。
不过我们不希望每次部署变更时都需要手动运行构建,所以可以设置一个 target。在项目的 project.json
文件中(本例位于 apps/next-site/project
),找到 targets 对象并更新:
{
"name": "next-site",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/next-site",
"projectType": "application",
"tags": [],
"targets": {
+ "open-next-build": { // target 名称,这是你将要调用的名称
+ "executor": "nx:run-commands",
+ "dependsOn": ["build"], // 确保 Nx 在运行此命令前会先构建我们的 Next 应用
+ "cache": true, // 缓存输出,适用于使用 DTE/Nx cloud
+ "outputs": ["{projectRoot}/.open-next"], // 告诉 nx 输出目录位置
+ "options": {
+ "cwd": "apps/next-site", // 命令运行目录
+ "command": "open-next build" // 要运行的命令
+ }
+ }
}
}
接下来需要将 open-next 目录添加到 eslint 的 ignorePatterns
数组中:
{
"extends": [
"plugin:@nx/react-typescript",
"next",
"next/core-web-vitals",
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*",
+ ".next/**/*",
+ ".open-next/**/*"
],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"],
"env": {
"jest": true
}
}
]
}
现在,当你运行 nx open-next-build next-site
时,nx 会自动构建 next 应用及其所有依赖项,非常方便!
- 使用 SST 部署
现在我们已经有了一个构建好的应用,准备部署,那么如何将其部署到 SST/AWS 上呢?好问题!
本示例中我们使用 sst ion
。假设你已经安装了 CLI 如果没有,请参考这里 (opens in a new tab),但我们不会使用 SST cli 初始化项目,因为它会尝试在你的 next 应用中添加 package.json,看起来能工作但实际上会导致严重的服务器错误(因为 package.json 会覆盖 nx 认为应该存在的依赖关系,导致缺少大量依赖)。我们将手动设置:
- 首先用
pnpm add sst@ion
添加 sst 包,以及 SST 与 AWS 协同工作所需的包pnpm add --save-dev aws-cdk-lib constructs @types/aws-lambda
- 然后在
apps/next-site
中手动创建sst.config.ts
文件,内容如下:
/// <reference path="./.sst/platform/config.d.ts" />
export default $config({
app(input) {
return {
name: "next-site", // 使用你的项目名称
removal: input?.stage === "production" ? "retain" : "remove",
home: "aws",
};
},
async run() {
new sst.aws.Nextjs("Site", {
buildCommand: "exit 0;", // 再次强调,我们希望 Nx 处理构建
});
},
});
- 现在你可能会看到一些类型错误,因为 SST 尚未初始化,可以通过运行以下命令解决:
$ cd apps/next-site && sst install
这将解决类型问题并初始化 SST。
-
接下来需要将
sst.config.ts
添加到tsconfig.json
的 excludes 数组中 -
然后将
sst.config.ts
和.sst
文件夹添加到 eslint 的 ignorePatterns:
{
"extends": [
"plugin:@nx/react-typescript",
"next",
"next/core-web-vitals",
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*",
".next/**/*",
+ ".open-next/**/*",
+ ".sst/**/*",
+ "sst.config.ts"
],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"],
"env": {
"jest": true
}
}
]
}
- 现在,如果你想运行
sst dev
,可以使用sst dev "nx dev next-site"
,类似地部署可以使用sst deploy
...但你可能希望设置任务链,同样我们可以通过向应用添加 target 并将其dependsOn
设置为open-next-build
来实现,示例如下:
{
"name": "next-site",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/next-site",
"projectType": "application",
"tags": [],
"targets": {
"open-next-build": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
"cache": true,
"outputs": ["{projectRoot}/.open-next"],
"options": {
"cwd": "apps/next-site",
"command": "open-next build"
}
+ },
+ "deploy": {
+ "executor": "nx:run-commands",
+ "dependsOn": ["open-next-build"],
+ "options": {
+ "cwd": "apps/next-site",
+ "command": "sst deploy --stage {args.stage}", // 这里使用 nx 的插值来传递 --stage 参数,下面有一些配置示例
+ "forwardAllArgs": true
+ },
+ "defaultConfiguration": "dev",
+ "configurations": {
+ "production": {
+ "args": ["--stage=production"]
+ },
+ "staging": {
+ "args": ["--stage=staging"]
+ },
+ "dev": {
+ "args": ["--stage=development"]
+ }
+ }
+ }
+ }
}
现在我们可以运行(或者如果你想使用自定义 stage,可以直接执行 nx deploy next-site --stage this-is-my-stage
,参数会直接传递给 sst 命令):
$ nx deploy next-site --configuration dev # 使用 dev 配置(将 stage 设置为 development)
nx deploy next-site -c dev # 或者
nx deploy next-site --stage my-stage # 自定义阶段