AWS
Nx 单体仓库

在 Nx Monorepo 中配置 OpenNext

以下是详细的示例,展示如何在一个已有的 Nx 工作区中添加 OpenNext + SST,其中已有一个位于 apps/next-site 的 NextJS 应用。

  1. 安装 open-next: pnpm add —save-dev @opennextjs/aws

  2. 更新 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);
  1. 在应用根目录创建 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;
  1. 设置 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 应用及其所有依赖项,非常方便!

  1. 使用 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 # 自定义阶段