vue.js
nuxt.js

nuxt.js を serverless framework と一緒に使う

まだまだ検証段階だが、手法を載せる。

追記: nuxtjs 1.0 がリリースされましたが、残念ながら node.js 8 以降のサポートとなり、2018年2月26日現在、AWS Lambda上で動かすことができません。動かしたい場合は、node.js 6 で動作する、RC版の利用が必要そうです。早く、AWS Lambda に、node.js 8 Runtime が来てほしいですね。

追記2: 2018年4月 に AWS Lambda 上に、node.js 8 ランタイムが実装されました。https://aws.amazon.com/jp/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/ 同様の方法で nuxtjs 1.4 が動作することを確認済みです。

目的

たいしてリクエストのないアプリなので、serverless framework 上で、nuxt.js を動作させたい。

参考にしたもの

手順

nuxt.config.js の調整

js や css などのアセットに関しては、Lambda 経由でのアクセスではなく、CDNで配信したいため、publicPath の設定を変更する。ローカル開発 (nuxt dev) では、publicPath を設定しても反映されないので、ファイルを分けたり分岐を作る必要はない。

また、Lambda上で動作させる場合は、gzip圧縮をしてしまうとうまく動かないことがわかっている。この点は、手前にCloudFrontのCDNを挟み圧縮をかけることでなんとかなると思う。

nuxt.config.js
module.exports = {
  build: {
    // CSSについても別ファイルになるようにする。こちらも開発環境では反映されない。
    extractCSS: true,
    publicPath: `https://xxxxxxx.cloudfront.net/nuxt/`,
  },
  render: {
    gzip: false
  }
}

ビルドを行う

yarn run build 

.nuxt/dist ディレクトリにある中身をCDNで配信する。
自分は、S3に上げて CloudFront から参照しているので、以下のように同期かけてます。

aws s3 sync ./.nuxt/dist/ s3://$ASSET_BUCKET/nuxt/ --acl public-read --cache-control "public, max-age=31557600"

サーバを組み立てる

aws-serverless-express を使う。

yarn add aws-serverless-express
server.js
import express from 'express'
import { Nuxt } from 'nuxt'
import awsServerlessExpress from 'aws-serverless-express'
import awsServerlessExpressMiddleware from 'aws-serverless-express/middleware'

const app = express()
const config = require('./nuxt.config')
config.dev = false
config.mode = 'universal'
const nuxt = new Nuxt(config)

app.use(nuxt.render)
app.use(awsServerlessExpressMiddleware.eventContext())

const server = awsServerlessExpress.createServer(app)

export function render (event, context) {
  awsServerlessExpress.proxy(server, event, context)
}

babel と backpack 使ってビルド

yarn add -D backpack-core babel-core babel-plugin-dynamic-import-node babel-plugin-transform-es2015-modules-commonjs babel-preset-stage-2 babel-preset-env
.babelrc
{
  "presets": [
    ["env", {
      "modules": false
    }],
    "stage-2"
  ],
  "plugins": ["transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}
backpack.config.js
module.exports = {
  webpack: (config, options, webpack) => {
    config.entry.server = './server.js'
    return config
  }
}
package.json
{
  "scripts": {
    "build": "nuxt build && backpack build"
  }
}
yarn run build

serverless.yml を作る

service:
  name: ${self:custom.name}

provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs6.10
  stage: ${opt:stage, 'dev'}
  memorySize: 2048

custom:
  name: sample

package:
  include:
    - .nuxt/**
    - build/**
  exclude:
    # 要らないファイルが結構あるので除去しまくる
    - .git/**
    - .idea/**
    - README.md
    - backpack.config.js
    - components/**
    - layouts/**
    - nuxt.config.js
    - pages/**
    - scripts/**
    - store/**
    - assets/**
    - middleware/**
    - plugins/**
    - server/**
    - static/**
    - test/**

functions:
  render:
    handler: build/server.render
    events:
      - http:
          path: '/'
          method: 'get'
          private: false
      - http:
          path: '{proxy+}'
          method: 'get'
          private: false

これで、 sls deploy で出来上がったエンドポイントにアクセスすればいい感じに展開された。
serverless-domain-manager などを使ってやり、ドメイン割当をすれば、より良い感じに使うことができそう。