AWS
Nx Monorepo

配置 OpenNext 以便在 Nx Monorepo 中使用。

这是一个详细的示例,说明如何将 OpenNext + SST 添加到一个现有的 Nx 工作区,其中现有的 NextJS 应用程序位于 apps/next-site

  1. 安装 open-nextpnpm 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: {
+      // 这应该是你仓库根目录的路径,在这种情况下只是向下两层。open-next 需要它来检测这是一个 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

现在我们已经设置了 open-next 配置,你可以尝试运行 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 应用,以及 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"]
+        }
+      }
+    }
+  }
}
 
 

现在我们可以运行(或者如果你想要一个自定义阶段,你可以简单地执行 nx deploy next-site --stage this-is-my-stage,它将直接传递给 sst 命令)。

$ nx deploy next-site --configuration dev # 使用 dev 配置(它将阶段设置为 development)
# nx deploy next-site -c dev # 或
# nx deploy next-site --stage my-stage # 自定义阶段