Edited at

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

More than 1 year has passed since last update.

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

追記: 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 を動作させたい。


参考にしたもの


手順


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

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 を使う。

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 などを使ってやり、ドメイン割当をすれば、より良い感じに使うことができそう。