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 が動作することを確認済みです。

**追記3**: 2018年8月16日以前の記事では、 S3 に `server-bundle.json` を転送する挙動があったため、CDN側でこちらのファイルを見えないようにする設定がない場合、ソースコードが漏洩する可能性があります。 Nuxtのサーバで動かした場合は、公開されないような挙動になっています。 https://github.com/nuxt/nuxt.js/issues/916 

## 目的

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

## 参考にしたもの

- [mya-ake/vuejs-pwa-and-ssr-on-lambda](https://github.com/mya-ake/vuejs-pwa-and-ssr-on-lambda)

## 手順

### nuxt.config.js の調整

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

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

```js: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 から参照しているので、以下のように同期かけてます。

server-bundle.json に関しては、SSRのみで利用するため除外します。

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

### サーバを組み立てる

[aws-serverless-express](https://github.com/awslabs/aws-serverless-express) を使う。 

```
yarn add aws-serverless-express
```

```js: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
```

```json:.babelrc
{
  "presets": [
    ["env", {
      "modules": false
    }],
    "stage-2"
  ],
  "plugins": ["transform-runtime"],
  "env": {
    "test": {
      "presets": ["env", "stage-2"],
      "plugins": ["transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}
```

```js:backpack.config.js
module.exports = {
  webpack: (config, options, webpack) => {
    config.entry.server = './server.js'
    return config
  }
}
```

```json:package.json
{
  "scripts": {
    "build": "nuxt build && backpack build"
  }
}
```

```
yarn run build
```

### serverless.yml を作る

```yaml
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](https://www.npmjs.com/package/serverless-domain-manager) などを使ってやり、ドメイン割当をすれば、より良い感じに使うことができそう。