用Lambda分发Nuxt.js(SSR)[个人开发]


概述

用于组织Qiita股票的" Mindexer"服务的前端
我重建了用Vue.js构建的SPA应用程序,以使用Nuxt.js进行SSR。

我正在Lambda和Nuxt.js上运行Express服务器。
在本文中,我想解释与该开发中获得的Nuxt.js相关的技巧和基础结构配置。

另外,不包括有关从Vue.js进行迁移的说明。

关于服务

在本文中解释了" Mindexer"。
它还说明了后端使用的技术,因此希望您能一起看到它。

:link: AWS Laravel Vue.js创建了一项服务来组织Qiita股票! [个人发展]
:link:从EC2到Fargate取代了个人开发的基础架构!

源代码

所有源代码都在这里可用。

Nuxt.js:https://github.com/nekochans/qiita-stocker-frontend
地形:https://github.com/nekochans/qiita-stocker-terraform

迁移到Nuxt.js

的原因

原因如下。

  • 因为我想通过SSR提高页面显示速度
  • 因为我想介绍BFF层(在服务器端执行身份验证部分更安全)
  • 因为我想使用Nuxt的布局功能

选择架构

的原因

我担心选择运行Nuxt.js的体系结构。

选项包括AWS Fargate和GAE(Google App Engine)。
起初,我以为GAE(Google App Engine)会很容易,但是我停下来了,因为在这里的合作似乎很复杂,因为我使用Route53作为域注册商,并使用ACM作为证书。

最后,我选择在AWS Lambda上运行Nuxt.js。

原因如下。

  • 除非访问次数大大增加,否则运行成本很低
  • 只能使用AWS上的服务来完成

在AWS Lambda上运行Nuxt.js的设置变得有些复杂,因此我将在下一部分中对此进行说明。

技术元素

  • Nuxt.js v2.10.0
  • Vue.js Vuex
  • typescript
  • 无服务器框架

基础架构配置

AWS配置图

mindexer-nuxt.png

前端

我正在Lambda和Nuxt.js上运行Express服务器。

首先,使用CloudFront分发对S3和API网关的访问。
静态资源(例如图像和JavaScript文件)是从S3分发的,对SSR和BFF的访问是通过API网关从Lambda分发的。

后端

后端API由ECS Fargate分发。
本文介绍了ECS Fargate,因此,如果您愿意,请在这里看看。
从EC2到Fargate取代了个人开发的基础架构!

基础设施建设

AWS基础结构配置图中显示的资源构造有些复杂,但是它是由两个Serverless FrameworkTerraform构建的。

  • 无服务器框架

    • API网关
    • 拉姆达
  • 地貌

    • CloudFront
    • S3

首先,在解释AWS资源之前,我想解释在Lambda上运行的Express服务器,然后看一下AWS资源。

快递服务器

选择

Nuxt.js的universal模式,并准备将Express服务器用于SSR。
Nuxt.js用作Express服务器的中间件。
除此之外,Express服务器还充当BFF的角色。

Nuxt.js渲染(SSR)

通过调用

nuxt.render(),Nuxt.js在Express上运行。

API:nuxt.render(req,res)--NuxtJS

BFF(前端后端)

主要扮演以下角色。

  • 将对多个Web API的请求处理组合为一个
  • 执行SSR(服务器端渲染)(这是通过Nuxt的机制完成的)

由于您可以正常编写服务器端代码(Express等代码可以正常工作),因此在此处编写围绕身份验证的处理会更简单。

另外,访问外部API时使用的凭据(API Secret等)也可以隐藏为服务器端环境变量,从而提高了安全性。

这次,使用Qiita帐户登录的部分已被修改为可以在此BFF上完成。

作为补充,使用serverMiddleware,您还可以在Nuxt.js中进行Express服务器处理。
API:serverMiddleware属性--NuxtJS

无服务器

到目前为止,我已经解释了Nuxt.js在Express的服务器上运行,但是我想看看Express如何在Lambda上运行。

无服务器框架

无服务器框架是用于配置管理和部署无服务器应用程序的CLI工具。您可以使用无服务器框架轻松地部署到AWS Lmabda。

以下文章对有关无服务器框架的说明很有帮助,因此我将其发布。
如何使用无服务器框架

的摘要

在这种情况下,我们将执行以下操作:

  • 创建Lambda和API网关
  • 部署到Lamnda

为了通过Lambda交付Nuxt.js,您需要一个API网关作为调用Lambda的网关。
准备API网关并将HTTP请求发送到Lambda。

有关API网关设置,请检查以下内容。
无服务器框架--AWS Lambda事件--API网关

另外,以下库用于在Lambda上分发Nuxt.js。

  • AWS无服务器表达
  • 无服务器插件预热

aws-serverless-express

您可以使用AWS Labs提供的aws-serverless-express在Lambda上运行Express服务器。

aws-serverless-express将来自API网关的请求转换为Node.js的HTTP请求,因此您可以轻松地在Lambda上运行Express服务器。

无服务器插件warmup

用于Lambda的冷启动措施。
在下面的文章中详细描述了详细信息,因此我将其发布。
通过无服务器框架

采取措施防止Lambda冷启动

S3,CloudFront

Terraform管理由无服务器框架创建的AWS资源以外的所有资源。
即使SPA是随Vue.js一起交付的,其基础结构仍由Terraform(包括后端)管理。
在这里,我将不接触Terraform来解释S3和CloudFront的设置。

假定Lambda和API Gateway是由Serverless Framework创建的。

S3

准备一个S3存储桶以传递静态资源,例如图像和JavaScript文件。

访问策略设置如下,并且仅允许从CloudFront(GetObject)访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity <CloudFrontのID>"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<S3バケット>/*"
        }
    ]
}

另外,S3存储桶中的对象具有以下目录结构。 (我将省略有关如何部署到S3的说明。)

1
2
├── _nuxt  # Nuxt.jsのクライアントのリソース
└── assets # 画像などの静的リソース

运行

nuxt build将在.nuxt/dist/client中生成客户端代码。
该客户端的源代码将是一个JavaScript文件,可以将其上传到您的CDN。
CDN设置由nuxt.config.js中的publicPath定义。默认值为_nuxt,并且由于我们将按原样使用它,因此S3目录也具有上述结构。

API:构建属性--NuxtJS

CloudFront

基础结构配置部分中,我提到了CloudFrontによって、S3とAPI Gatewayへのアクセスの振り分けを行います。,但是我想详细解释一下它是哪种设置。

起源

S3和Api网关均已注册。

S3

<表格>

项目

设置


<身体>

原始域名

指定静态资源所在的S3存储桶

原始路径

没有设置

原始ID

任意ID

限制存储桶访问


原始自定义标题

没有设置


我将

Restrict Bucket Access设置为No,因为我已经注册了S3存储桶策略。

ApiGateway

<表格>

项目

设置


<身体>

原始域名

指定由Severless Framework创建的API网关API端点

原始路径

/ stage(示例:/ dev)

原始ID

任意ID

最低原始SSL协议

TLSv1.2

原始协议策略

仅HTTPS

原始响应超时

任意值

原点保持活动超时

任意值

HTTP端口

80

HTTPS端口

443

原始自定义标题

没有设置


Origin Path是Serverless Framework的serverless.yml中安装的阶段的值。
如果未设置舞台,则默认值为dev

行为

按路径设置原点分布。

<表格>

优先级

路径模式

Origin


<身体>

0

_nuxt / *

指定静态资源所在的S3存储桶

1

资产/ *

指定静态资源所在的S3存储桶

2

默认(*)

指定API网关


为了解释

设置,Nuxt.js客户端代码将访问/_nuxt/*。通过将客户端代码部署到S3,它将从S3交付。
另外,静态文件(图标等)也被设置为分发到S3存储桶。
其他请求将分发到API网关。

适用的源代码在这里。
https://github.com/nekochans/qiita-stocker-terraform/blob/master/modules/aws/frontend/cloudfront.tf

Nuxt.js提示

目录结构

1
2
3
4
├── app    # Nuxt.jsのコード
├── server # Expressのコード
├── nuxt.config.ts
└── serverless.yml
  • 我将Nuxt.js代码放在

    app中,将Express代码放在server中,并将其设置为在构建时的结果如下。

    • /.nuxt:Nuxt.js代码

    • /dist:快递代码

  • 静态资源(例如

    图像)位于app/static/assets上。
    CloudFront将请求分发到S3和API网关,但是通过这样设置,可以轻松设置CloudFront。

Nuxt.js TypeScript支持

Nuxt.js v2.10正式支持TypeScript。
请检查以下设置。
Nuxt TypeScript

我根据上述文档进行了设置,但是我想描述一下它,因为在某些地方会发生错误。

  • @nuxt/typescript-build移至dependencies

CodeBuild上构建时,如果将@nuxt/typescript-build添加到devDependencies,则会发生错误。正式地,它说将其添加到devDependencies,但是为了避免错误,我将其移至了dependencies

  • 避免在开发模式下生成错误

开发人员模式下,它是由@nuxt/builder构建的。那时,Vuex模块中发生了错误,因此采取了以下措施。

对于

开发模式,将[ts]添加到config.extensions

服务器/核心/nuxt.ts

1
2
3
4
5
6
7
8
9
10
11
12
import config from '../../nuxt.config'
const { Nuxt } = require('nuxt')

config.dev = !(process.env.NODE_ENV === 'production')

if (config.dev) {
  config.extensions = ['ts'] // 追加
}

export const nuxt = new Nuxt(config)

export default config

如果未添加,则.nuxt/store.js将如下所示,并且无法识别ts文件中定义的Vuex模块,并且会发生错误。

.nu??xt / store.js

1
2
3
4
function resolveStoreModules (moduleData, filename) {
  moduleData = moduleData.default || moduleData
  // Remove store src + extension (./foo/index.js -> foo/index)
  const namespace = filename.replace(/\.(js|mjs)$/, '')   //tsが含まれない
  • 添加了nuxt.ready()
    在Lambda上运行Nuxt.js时,出现错误,因为我没有添加以下设置。
    原因是从Nuxt.js 2.5.x开始,在执行new Nuxt()之后,除非我调用nuxt.ready(),否则服务器不会渲染。通过采取以下措施解决了该问题。

服务器/app.ts

1
2
3
4
5
6
7
8
// 変更前
app.use(nuxt.render)

// 変更後
app.use(async (req, res, next) => {
  await nuxt.ready()
  nuxt.render(req, res, next)
})

错误页

有两种显示错误页面的方法。

使用Nuxt.js的Layout属性

视图--NuxtJS(错误页面)

从API返回错误时显示。
发生错误时,通过调用error()函数来显示使用Layout属性的错误页面。
API:上下文--NuxtJS(错误(功能))

页面组件

应用程式/页面/股票/ all.vue

1
2
3
4
5
6
7
8
9
10
11
  async fetch({ store, error }: Context) {
    try {
      await store.dispatch('qiita/fetchUncategorizedStocks')
      await store.dispatch('qiita/saveDisplayCategoryId', 0)
    } catch (e) {
      error({
        statusCode: e.code,
        message: e.message
      })
    }
  }

除页面组件之外的

应用程序/组件/页面/库存/ All.vue

1
2
3
4
5
6
7
8
9
10
  async onClickDestroyCategory(categoryId: number) {
    try {
      await this.destroyCategory(categoryId)
    } catch (error) {
      return this.$nuxt.error({
        statusCode: error.code,
        message: error.message
      })
    }
  }

扩展由Nuxt.js创建的路由,并转换到错误页面

API:路由器属性--NuxtJS(extendRoutes)

如果

BFF中发生错误,它将转换为/error并显示错误页面。

扩展由Nuxt.js创建的路由,以创建错误页面。
如果BFF中发生错误,它将重定向到/error并显示在app/pages/error.vue中定义的错误页面。

extendRoutes添加到nuxt.config.ts中定义的路由器属性中以扩展路由。

nuxt.config.ts

1
2
3
4
5
6
7
8
9
10
11
  router: {
    middleware: ['authCookieMiddleware', 'redirectMiddleware'],
    extendRoutes(routes: any, resolve) {
      routes.push({
        name: 'original_error',
        path: '/error',
        props: true,
        component: resolve(__dirname, 'app/pages/error.vue')
      })
    }
  },

Middllwere

Middllwere用于执行与身份验证相关的处理。
API:中间件属性--NuxtJS

  • authCookieMiddleware.ts:将存储在cookie中的凭据保存在Vuex商店中
  • redirectMiddleware.ts:根据需要从用户的身份验证状态重定向

Middll仅通过将以下内容添加到nuxt.config.ts即可工作。
Middll按照此处描述的顺序工作。 (authCookieMiddleware-