AWS
TypeScript
AWSLambda
nuxt.js
nuxt2

Nuxt.js(v2.2.0)+TypeScriptなアプリをAWS Lambda+αにデプロイしてみた

Nuxt.jsのv2系でTypeScriptを利用しつつ、AWS Lambda+αにデプロイできるか確認してみました。

手順

GitHubにいい感じのコードをアップいただけている御方がおられたので、利用させてもらいます。

jeehyukwon/nuxt-serverless: Nuxt.js Serverless SSR Starter on AWS (Lambda + API Gateway + S3) with Serverless Framework
https://github.com/jeehyukwon/nuxt-serverless

静的ファイルはS3、AWS LambdaのNode.jsでExpressを立ち上げて、SSRをして、API Gatewayがエンドポイント、ドメインはカスタムドメインを利用する構成です。

カスタムドメインを利用する前提でしたので、それを外してデプロイするようにしてみます。

Githubに上記リポジトリをフォークしたものがありますので、よければご参考ください。
https://github.com/kai-kou/nuxt-serverless

ソースの取得と環境構築

ローカル環境で構築します。

nodenpm がインストールされている前提です。

> node -v
v10.11.0

> npm -v
6.4.1
> git clone https://github.com/jeehyukwon/nuxt-serverless.git
> cd nuxt-serverless
コンテナ内
> npm install

動作確認

ローカルで動作するか確認します。

> npm run dev

()
✔ success Builder initialized
✔ success Nuxt files generated

 READY  Listening on http://localhost:3000

ブラウザでアクセスします。
http://localhost:3000
スクリーンショット 2018-11-13 11.07.17.png

スクリーンショット 2018-11-13 11.07.37.png

はい。
ソースを確認するとSSRしてますね。

デプロイ準備

API Gatewayのステージ対応

AWS Lambdaへデプロイする場合、HTTPアクセスにはAWS API Gatewayを利用することになります。
カスタムドメインを利用しない場合、API GatewayのURLはhttps://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/ のように、ステージ名dev が含まれます。

Nuxt.jsでこのステージ名をBaseURL として設定してやる必要があります。
詳細は下記記事で詳しく解説されています。感謝!

Nuxt.js on AWS Lambda with Serverless Framework - mya-ake com
https://mya-ake.com/posts/nuxtjs-on-aws-lambda/

nuxt.config.jsrouter.base を追加します。

nuxt.config.js(一部抜粋)
module.exports = {
  ()
  router: {
    base: '/dev/'
  },
  ()
}

Nuxt.jsの設定以外にExpressでも設定が必要となります。リクエストのURLにステージ名dev が含まれないため、強制的に含めてやります。BaseURLはNuxtの設定nuxtConfig.router.base から取得しています。

handler.js
app.use((req, res) => (
  setTimeout(() => {
    req.url = `${nuxtConfig.router.base}${req.url}`.replace('//', '/')
    nuxt.render(req, res)}
  , 0)
))

カスタムドメインを利用しない設定

カスタムドメインを利用しないので、package.jsonscripts に指定されているsls create_domainsls delete_domain を削除します。

package.json(一部抜粋)
  "scripts": {
    "dev": "nuxt",
    "build": "cross-env NODE_ENV=production nuxt build",
    "start": "nuxt start",
    "sls:local": "sls offline",
    "sls:create": "npm run build && sls deploy",
    "sls:delete": "sls remove",
    "sls:deploy": "npm run build && sls deploy"
  },

serverless.yml でもカスタムドメイン作成の定義を削除します。(ここではコメントアウトしています。)serviceregionBUCKET_NAMESPACE は任意で変更してください。

serverless.yml
service: nuxt-serverless  # 1. Edit whole service name

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, 'dev'}
  region: ap-northeast-1 # 2. Edit AWS region name
  environment:
    NODE_ENV: production
    BUCKET_NAMESPACE: nuxt-serverless  # 3. Specify a new AWS S3 bucket namespace for bundled assets and static assets (should be unique)
    ASSETS_BUCKET_NAME: ${self:provider.environment.BUCKET_NAMESPACE}-assets-${opt:stage, 'dev'}
    STATIC_BUCKET_NAME: ${self:provider.environment.BUCKET_NAMESPACE}-static-${opt:stage, 'dev'}
    ASSETS_BUCKET_URL: https://s3.${self:provider.region}.amazonaws.com/${self:provider.environment.ASSETS_BUCKET_NAME}
    STATIC_BUCKET_URL: https://s3.${self:provider.region}.amazonaws.com/${self:provider.environment.STATIC_BUCKET_NAME}

custom:
  # customDomain:
  #   domainName: service.mydomain.io  # 4. Specify a new domain name to be created
  #   stage: ${opt:stage, 'dev'}
  #   certificateName: mydomain.io  # 5. Enter the certificate name in AWS Certificate Manager (us-east-1) for https connection
  #   createRoute53Record: true
  serverless-offline:
    port: 4000
  s3Sync:
    - bucketName: ${self:provider.environment.ASSETS_BUCKET_NAME}
      localDir: .nuxt/dist/client
    - bucketName: ${self:provider.environment.STATIC_BUCKET_NAME}
      localDir: static

package:
  exclude:
    - src/**
  include:
    - serverless.yml

functions:
  nuxt-renderer:
    handler: handler.render
    memorySize: 512
    timeout: 30
    events:
      - http:
          path: /
          method: ANY
          cors: true
      - http:
          path: /{proxy+}
          method: ANY
          cors: true

resources:
  Resources:
    AssetsBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:provider.environment.ASSETS_BUCKET_NAME}
    AssetsBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: AssetsBucket
        PolicyDocument:
          Version: "2012-10-17"
          Statement: [
            {
              Action: ['s3:GetObject'],
              Effect: 'Allow',
              Resource: {
                Fn::Join: ['', ['arn:aws:s3:::', { Ref: 'AssetsBucket' }, '/*']],
              },
              Principal: '*'
            },
          ]
    StaticBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:provider.environment.STATIC_BUCKET_NAME}
    StaticBucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: StaticBucket
        PolicyDocument:
          Version: "2012-10-17"
          Statement: [
            {
              Action: ['s3:GetObject'],
              Effect: 'Allow',
              Resource: {
                Fn::Join: ['', ['arn:aws:s3:::', { Ref: 'StaticBucket' }, '/*']],
              },
              Principal: '*'
            },
          ]

plugins:
  - serverless-offline
  - serverless-s3-sync
  # - serverless-domain-manager

デプロイする

npm run sls:create またはnpm run sls:deploy することで、Nuxt.jsのビルドと、AWSへのデプロイがされます。

> npm run sls:create
()
Entrypoint app = 55e8f003a83a598dd113.js 5e45cc8df25adbaac94b.js 1d1bc122ddb9c7b7f271.css 6a943e90e669a1de7258.js
[13:27:00] Compiling server
[13:27:04] Compiled server in 4s

Hash: 2c7c34f6c1ea4aefd63d
Version: webpack 4.25.1
Time: 3924ms
Built at: 2018-11-13 13:27:04
             Asset     Size  Chunks             Chunk Names
server-bundle.json  213 KiB          [emitted]
Entrypoint app = server-bundle.js
✨  Done in 114.07s.
()
Serverless: Stack update finished...
Service Information
service: nuxt-serverless
stage: dev
region: ap-northeast-1
stack: nuxt-serverless-dev
api keys:
  None
endpoints:
  ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
  ANY - https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
functions:
  nuxt-renderer: nuxt-serverless-dev-nuxt-renderer
S3 Sync: Syncing directories and S3 prefixes...
....
S3 Sync: Synced.

デプロイができたら、ブラウザでアクセスしてみます。xxxxxxxxxx はご自身の環境に合わせてください。

https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/

Hello__Nuxt_Serverless_Template.png

Hello__TypeScript.png

view-source_https___vvb2o02739_execute-api_ap-northeast-1_amazonaws_com_dev_.png

なんとか動きました^^

はまりポイント

nuxt dev でホスト名に0.0.0.0 が指定できない

当初Dockerコンテナ内で実行させようとしたのですが、ホスト名に0.0.0.0 が指定できなかったので、ローカル環境で構築しました。

Host and Port - Nuxt.js
https://nuxtjs.org/faq/host-port

0.0.0.0 を指定すると、ランダムっぽいIPが振られてしまいます。。。
環境変数HOSTNUXT_HOSTpackage.json で指定も --hostname もだめ。むむぅ。なんでしょう。。。
ドキュメントには

Note: If port is assigned the string value of '0' (not 0, which is falsy), a random port will be assigned to your Nuxt application.

とありますので、ポートはわかるのですが、ホスト名でも同じような挙動です。。。ローカルでもDockerコンテナ内でも同じ挙動でした。

ローカル
INFO Building project

✔ success Builder initialized
✔ success Nuxt files generated

READY Listening on http://192.168.34.25:3001
Dockerコンテナ
INFO Building project

✔ success Builder initialized
✔ success Nuxt files generated

READY Listening on http://172.28.0.2:3001

API Gatewayのパスが面倒

Nuxt.jsとExpressと別でBaseURL の設定が必要だったのが非常にわかりにくかったです。今回Serverless-httpを利用していたのですが、いくつかIssueが上がっていました。どうにかならないものでしょうか^^

Correct path for non-custom endpoints by bsdkurt · Pull Request #42 · dougmoscrop/serverless-http
https://github.com/dougmoscrop/serverless-http/pull/42

Include baseUrl in request object · Issue #35 · dougmoscrop/serverless-http
https://github.com/dougmoscrop/serverless-http/issues/35

まとめ

ローカル環境の構築がさくっと終わったのでAWS Lambdaにもさくっと構築できるだろうと思って取り組んだら、思いの外ハマって時間がかかりました。Nuxt.jsもv2がリリースされたこともあり、かつTypeScriptを利用だったので、正しい情報・設定を見極めるのに試行錯誤することになりました。

デプロイできることは確認できましたので、ひとまずは

やったぜ^^

参考

jeehyukwon/nuxt-serverless: Nuxt.js Serverless SSR Starter on AWS (Lambda + API Gateway + S3) with Serverless Framework
https://github.com/jeehyukwon/nuxt-serverless

Nuxt.js on AWS Lambda with Serverless Framework - mya-ake com
https://mya-ake.com/posts/nuxtjs-on-aws-lambda/

Host and Port - Nuxt.js
https://nuxtjs.org/faq/host-port

Correct path for non-custom endpoints by bsdkurt · Pull Request #42 · dougmoscrop/serverless-http
https://github.com/dougmoscrop/serverless-http/pull/42

Include baseUrl in request object · Issue #35 · dougmoscrop/serverless-http
https://github.com/dougmoscrop/serverless-http/issues/35