使用 AWS Amplify 和 Serverless 构建的多环境 Next.js 应用
作为一名从事多个 React 应用开发的独立开发者,我发现有几件事非常重要,例如:
- 我多久能搭建好后端资源,比如数据库和身份验证?
- 如何为我的应用程序维护多个开发和生产环境?
- 我多久能将更新发送给客户?
所以,当我需要开发工具包时, AWS Amplify长期以来一直是我的首选,它支持快速跨环境开发 REST 和 GraphQL API、数据库和对象存储以及身份验证管理。可以说,Amplify 几乎可以做到任何你想做的事情。
但最近,我所参与开发的应用程序的另一个方面变得越来越重要。
这就是我的应用对搜索引擎的友好程度和整体性能。我们都听说过捆绑臃肿的 JS 库,以及搜索引擎机器人抓取和索引我们应用时遇到的问题。我们也知道 Next.js 的出现解决了这些问题,它提供了丰富的动态服务器端渲染功能、自动图像优化等等!
那么,让我们来解决我所有的主要顾虑,并使用 Next.js 和 AWS Amplify 构建一个应用程序吧!两全其美,对吧?
别那么快!
您看,AWS Amplify 虽然可以构建和部署 Next.js 应用,但前提是我们只使用静态生成的页面。AWS Amplify 目前还不具备部署 Next.js 动态组件所需的全部功能。这意味着,如果我们使用 AWS Amplify 构建 Next.js 应用,就只能接受以下两种情况:要么构建一个静态页面,所有数据在构建时加载完毕,页面内容不会随时间变化;要么构建一个静态的页面框架,然后继续在客户端获取动态内容。
这意味着没有 Next.js<Image />组件用于自动图像优化,没有getInitialProps()用于初始数据获取的组件,没有页面的增量静态重生成组件,等等……
依我看,这听起来就像去度假却只待在汽车旅馆房间里……那有什么乐趣可言呢!
为了充分发挥 Next.js 的所有优势,我们需要一个替代方案。Serverless Framework就是其中之一。Serverless Framework 提供了许多优秀的基于 YAML 的模板,我们可以使用这些模板将无服务器应用程序部署到您选择的云提供商,其中包括 Next.js Serverless 插件;该模板允许我们在自己的 AWS 账户中使用 Next.js 的所有强大功能。
听起来都很不错!
但有个陷阱!
所以,Serverless Framework 的上手非常简单。我们只需安装它Serverless CLI,serverless.yml在根目录添加一个模板,运行命令npx serverless——然后一切就都搞定了。Serverless Framework 会构建并将我们的 Next.js 应用部署到 Cloudfront,并由 Lambda@Edge 提供支持,从而实现简洁高效的 AWS 部署。
但是,Serverless Framework 的部署依赖于 CLI 能够.serverless在项目中创建一个文件夹,并且该文件夹的内容能够在构建之间持久保存。这对于 AWS Amplify 来说并非障碍,而是一个难题,因为我们并不一定希望 AWS Amplify 构建服务器在每次构建后都将文件提交到我们的代码仓库中。
每次更新都要手动部署应用程序确实很麻烦。如果 AWS Amplify 能在我的代码仓库中每次提交到特定分支时自动部署 Serverless 组件,并在构建之间管理 Serverless 组件的输出,那就太好了。此外,如果能有多个 Serverless Next.js 环境,并且每个环境都链接到独立的 AWS Amplify 后端环境,那就更完美了。
所以,在我的最新项目中,我想看看如何才能两全其美,使用 Next.js Serverless 插件来管理 Next.js 的所有优点,并使用 AWS Amplify 来配置我的后端资源并控制整个应用程序的构建过程。
前言
为了简洁起见,我假设您已经熟悉如何配置 AWS Amplify 应用程序,或者如何开始使用 Next.js。网上有很多关于如何入门的优秀文章,如果需要,我会在文末提供一些实用资源的链接。
让我们开始建造吧!
设置 Serverless Next.js 插件
使用Serverless Next.js 插件非常简单。我们只需将serverless.yml类似下面的文件放在项目根目录,假设我们已经安装了 Serverless CLI 工具包,就可以运行npx serverless该命令来部署资源。
# serverless.yml
nextslsamplifyApp:
component: "@sls-next/serverless-component@{version_here}"
如果我们只计划部署单个环境,那么使用单个serverless.yml文件就足够了。但是,对于多个环境,最简单的方法是为每个计划配置的环境创建一个单独的serverless.yml模板,并在每个模板中进行特定于环境的更改。
对于这个项目,我计划创建一个master分支,该分支链接到我的prod后端资源;另一个develop分支则链接到所有dev后端资源。为了配置 Serverless Next.js 插件以适应这些环境,我在应用程序的根目录下创建了一个基本的文件夹结构。顶层是 environments 文件夹。再下一层,我分别创建了用于项目后端资源master和develop所有后端资源的文件夹。现在,每个分支文件夹内都将包含各自的serverless.yml模板。
<root>
- amplify
- environments
|--master
|--serverless.yml
|--develop
|--serverless.yml
- pages
- public
etc...
我使用的主分支和开发分支模板之间的差异非常小,因为我只更改了每个环境使用的子域名。因此,我的develop分支将部署到一个dev子域名,而开发master分支也将部署到另一个www子域名。下面的模板展示了两种配置的具体内容。
# master/serverless.yml
nextslsamplifyApp:
component: "@sls-next/serverless-component@{version_here}"
inputs:
domain: ["www", "<your-domain-name>"]
nextConfigDir: "../../"
# develop/serverless.yml
nextslsamplifyApp:
component: "@sls-next/serverless-component@{version_here}"
inputs:
domain: ["dev", "<your-domain-name>"]
nextConfigDir: "../../"
这里需要重点强调的是nextConfigDir两个 Serverless 模板文件中的 `<path>` 参数。默认情况下,Serverless 框架期望我们的serverless.yml模板位于项目根目录。如果我们将serverless.yml模板存储在其他位置,例如environments/${branch}子文件夹中,则可以使用该nextConfigDir参数告知 Serverless 框架项目根目录相对于当前模板的位置。
持久化无服务器构建文件
每次我们使用 Serverless CLI 构建 Serverless 组件时,框架都会在模板.serverless旁边创建一个文件夹serverless.yml,其中包含一组文件,这些文件引用了构建的特定部署细节。这些文件随后会在后续构建中被 Serverless Framework 引用,用于更新和添加现有资源。因此,我们需要一种方法来捕获这些文件,并将它们持久化到 AWS Amplify 构建服务器可以访问的位置。
为了解决这个问题,我们可以设置一个 S3 存储桶,用于在每次构建完成后存储这些资源。在这个项目中,我创建了一个 S3 存储桶,并在其中放置了几个文件夹,就像我们无服务器环境的文件夹一样,这些文件夹以项目中的每个分支命名。
s3://<your-bucket-name>/master/.serverless/
s3://<your-bucket-name>/develop/.serverless/
在我的每个分支文件夹中,我还创建了一个空.serverless文件夹,用于存储 Serverless 组件的输出文件,并在每次构建时从中检索这些文件。
准备 AWS Amplify 构建设置
我们流程的最后一步是最终配置 AWS Amplify 用于部署的构建设置。为此,AWS Amplify 允许我们amplify.yml在项目根目录下创建一个构建规范文件。当我们将该文件提交到分支时,AWS Amplify 将使用此规范覆盖默认的构建指令。
该amplify.yml模板允许我们将构建流程分解为backend多个frontend资源,每个资源都有各自的preBuild构建build步骤postBuild。您可以根据需要对构建配置进行高级设置,但我的项目目标是尽可能保持简洁,最终amplify.yml采用了类似这样的结构。
# amplify.yml
version: 1
backend:
phases:
build:
commands:
# Provision the relevant AWS Amplify resources like Auth etc.
# dependent on which branch we are currently building
- amplifyPush --simple
frontend:
phases:
preBuild:
commands:
- npm ci
# Install the Serverless Framework CLI
- npm i -g serverless
# Copy any existing files from a previous Serverless deployment into our working directory
- aws s3 cp s3://<your-bucket-name>/${AWS_BRANCH}/.serverless ./environments/${AWS_BRANCH}/.serverless/ --recursive
build:
commands:
# Move into the target Serverless env folder, and deploy the Serverless component
- cd ./environments/${AWS_BRANCH} && serverless
postBuild:
commands:
# Copy the updated .serverless folder files and contents out to s3 for referencing in future builds
- aws s3 cp .serverless/ s3://<your-bucket-name>/${AWS_BRANCH}/.serverless --recursive
artifacts:
# IMPORTANT - Please verify your build output directory
baseDirectory: ./
files:
- '**/*'
cache:
- node_modules/**/*
让我们一步一步地完成这些操作。
首先,我们向 Amplify 发送backend构建指令。这里我使用内置的 AWS Amplify 辅助脚本,amplifyPush --simple自动配置正确的 AWS Amplify 后端环境及其关联的分支。假设我已经将生产环境的 AWS Amplify 资源链接到主分支,这将确保我永远不会意外地将开发环境的后端资源推送到生产应用的前端。
# amplify.yml
version: 1
backend:
phases:
build:
commands:
# Provision the relevant AWS Amplify resources like Auth etc.
# dependent on which branch we are currently building
- amplifyPush --simple
后端由 AWS Amplify 处理完毕后,我们就可以搭建一个干净的环境来构建前端npm ci,并安装 Serverless CLI 工具。之后,我们可以使用 AWS CLI 命令与之前创建的 S3 存储桶交互,将文件夹中可能由先前构建生成的npm i -g serverless任何现有文件复制到该存储桶。.serverless
# amplify.yml
preBuild:
commands:
- npm ci
# Install the Serverless Framework CLI
- npm i -g serverless
# Copy any existing files from a previous Serverless deployment into our working directory
- aws s3 cp s3://<your-bucket-name>/${AWS_BRANCH}/.serverless ./environments/${AWS_BRANCH}/.serverless/ --recursive
您可以看到,我在这里使用的是 AWS Amplify 的默认环境变量之一${AWS_BRANCH}。因此,根据 AWS Amplify 构建的环境,我们的构建文件将更新为我们当前正在使用的分支的确切名称。
文件同步完成后,我们就可以启动构建过程了。构建 Serverless 组件非常简单,只需快速cd进入目标环境文件夹,然后调用相应的命令即可serverless。同样,我们可以使用${AWS_BRANCH}环境变量来确保每次构建都切换到正确的分支。
# amplify.yml
build:
commands:
# Move into the target Serverless env folder, and deploy the Serverless component
- cd ./environments/${AWS_BRANCH} && serverless
构建完成后,我们需要收集生成到本地.serverless文件夹的所有输出文件,并将它们存储回 S3 以供将来使用。
# amplify.yml
postBuild:
commands:
# Copy the updated .serverless folder files and contents out to s3 for referencing in future builds
- aws s3 cp .serverless/ s3://<your-s3-bucket>/${AWS_BRANCH}/.serverless --recursive
最后,处理要输出的任何特定工件,或缓存任何其他文件。
artifacts:
# IMPORTANT - Please verify your build output directory
baseDirectory: ./
files:
- '**/*'
cache:
- node_modules/**/*
现在所有组件都已集成到位,并且假设已在 AWS Amplify 中启用自动构建,那么后续任何向 develop 或 master 分支的推送都应该会在 AWS Amplify 中触发新的构建流程,从而配置 AWS Amplify 后端资源以及 Serverless Next.js 插件组件!我们的.serverless资源已成功持久化到 S3 中,可供任何未来的构建引用。
因此,尽管 AWS Amplify 目前还不支持 Next.js 的许多功能,但通过对构建过程进行一些调整,并借助 Serverless Framework 的一些帮助,我们完全可以同时拥有 Next.js 和 AWS Amplify 的最佳功能!
其他资源:
- 入门指南 - AWS Amplify
- Next.js 入门指南
- 多环境 - AWS Amplify(https://docs.amplify.aws/start/q/integration/react?sc_icampaign=react-start&sc_ichannel=choose-integration)
- Serverless Next.js 插件