poem

Nuxt.js & Netlify-Lambda で Serverless SPA 開発

netlify で lambda が使えるようになったので、静的サイト & シンプルなAPI のWebサイトを構成してみようと思い、実践的な内容も踏まえて挑戦してみました。

Lambda On Netlify の詳細に関しては以下を参照。

https://qiita.com/mikakane/items/d643d31790293a7df854

今回は Connpass API を使って、 Connpass のイベント情報を表示するSPAを作成します。

環境構築

とりあえず nuxt テンプレートを生成

bash
$ vue init nuxt-community/pwa-template myapp
$ cd myapp
$ npm i 

必要モジュールをインストール

$ npm i babel-core babel-loader netlify-lambda
$ npm i axios

Lambda 関数の作成

まずは API から作っていきます。裏で Connpass API を叩く部分は再利用可能にしたいので、Lambda のエントリからは分離しておきます。

$ mkdir src
$ mkdir service
$ touch src/events.js
$ touch service/http.js
events.js
const axios = require('axios')

exports.api = () => {
  const url = 'https://connpass.com/api/v1/event/'
  const params = {
    series_id: 1712 // PUT YOUR SERIES ID
  }
  return axios.get(url, {params}).then((result)=>{
    return result.data.events
  })
}

exports.handler = (event, context, callback) => {
  exports.api().then((result) => {
    console.log(result)
    callback(null, {
      statusCode: 200,
      headers: {
        'Content-type': 'application/json'
      },
      body: JSON.stringify(result)
    })
  })
}

netlify-lambda serve src で疎通を確認してください。

環境変数を利用する

series_id などを 柔軟に変更したいので、dotenv-module を利用して環境変数を参照します。

https://github.com/nuxt-community/dotenv-module

$ npm i @nuxtjs/dotenv

.env ファイルを作成して、イベントIDを環境変数で記録します。

CONNPASS_SERIES_ID=1712

.env は忘れずに ignore に入れておきましょう。

ただ、nuxt 側では、モジュールを利用して.env を参照出来るものの、 netlify-lamdba serve では .env を認識しません。

Nuxtのモジュールの中身は dotenv が入るだけなので、以下のコマンドで .env を食わせながら、netlify-lamdba コマンドを実行出来ます。

$ node -r dotenv/config ./node_modules/.bin/netlify-lambda serve src

準備ができたところで、service/http.jsの中身を以下の様に書き換えます。

events.js
exports.api = () => {
  const url = 'https://connpass.com/api/v1/event/'
  const params = {
    series_id: process.env.CONNPASS_SERIES_ID
  }
  return axios.get(url, {params}).then((result)=>{
    return result.data.events
  })
}

セキュアな情報でもないので、配信時の環境変数は、netlify.toml に記述しておきます。

netlify.toml
[build]
  Command = "npm run build"
  functions = "functions"
  publish = "dist"
[context.production]
  [context.production.environment]
    CONNPASS_SERIES_ID="1712"

toml 側に数値を書くとエラーになる?っぽいので、 ""で囲っておく必要があります。

npm run build をビルドコマンドにしているため、 package.json 側には

  "scripts": {
    ....
    "build": "touch .env && netlify-lambda build src && nuxt generate",
    ....
  },

のような設定を入れておきます。

以上を netlify 側に deploy してうまいこと動くこと確認できたらOKです。

Nuxt 側の開発

次は、Nuxt 側の開発です。

まずローカル環境で API を Proxy したいので, @nuxtjs/proxy を入れます。

$ npm i @nuxtjs/proxy

modules に @nuxtjs/dotenv も合わせて設定を追加します。

nuxt.config.js
  modules: [
    '@nuxtjs/dotenv',
    '@nuxtjs/proxy',
    ....
  ],
  proxy: {
    '/events': 'http://localhost:9000'
  }

とりあえず 簡単な store を作成します。

store/index.js
import axios from 'axios'

export const state = () => ({
  events: []
})

export const getters = {
  eventById: (state) => (id) => {
    for (let event of state.events) {
      if (event.event_id === parseInt(id)) {
        return event
      }
    }
    return false
  }
}

export const mutations = {
  ADD_EVENTS (state, events) {
    state.events = events
  }
}

export const actions = {
  async LOAD_EVENTS ({commit}) {
    const result = await axios.get('/events', {
      baseURL: process.env.FRONT_API_URL
    })
    commit('ADD_EVENTS', result.data)
    return result.data
  }
}

process.env.FRONT_API_URL は API のURL です。とりあえず以下のように .env に追加します。

.env
CONNPASS_SERIES_ID=1712
FRONT_API_URL=http://localhost:3000

netlify.toml 側では以下のようにします。

netlify.toml
[context.production]
  [context.production.environment]
    CONNPASS_SERIES_ID="1712"
    FRONT_API_URL="https://{YOUR_SITE_NAME}.netlify.com/.netlify/functions"

.envの内容は ブラウザ側では動作しないため、 nuxt.config.js に以下のような指定をして、コンパイルのJS内に組み込んでもらいます。

nuxt.config.js
  env: {
    FRONT_API_URL: process.env.FRONT_API_URL
  },

トップのイベント一覧ページの .vue は次のような script 構成になります。

page/index.vue
  export default {
    data () {
      return {}
    },
    computed: {
      events () {
        return this.$store.state.events
      }
    },
    async fetch ({store}) {
      await store.dispatch('LOAD_EVENTS')
    }
  }

イベントの個別ページは次のような形です。

page/event/_eventId.vue
  export default {
    data () {
      return {}
    },
    computed: {
      eventId () {
        return this.$route.params.eventId
      },
      event () {
        const event = this.$store.getters.eventById(this.eventId)
        return event
      },
    },
    async fetch ({store, params}) {
      const event = store.getters.eventById(params.eventId)
      if (event === false) {
        await store.dispatch('LOAD_EVENTS')
      }
    }
  }

注意が必要なのは、 fetch 内で this.$store などが使えない点です。

generate 向けの設定

最後に動的なルートを描画出来るように、generate オプションを指定します。

nuxt.config.js
  generate: {
    routes (callback) {
      const {api} = require('./src/events')
      require('dotenv').config()
      return api().then((result) => {
        const routes = result.map((event) => {
          return `/event/${event.event_id}`
        })
        callback(null, routes)
      }).catch(callback)
    }
  },

@nuxt/dotenv は公式サイトにも以下の記述があるように、ビルドフェーズで動作しないため、明示的にコレを読み込む必要があります。

The dotenv-module won't overload the environment variables of the process running your build.

以上で構築は完了です。

うまいこと レンダリングされた、静的ページが複数生成されると思います。

後で暇があったらデザイン整えて netlify buttonにして公開しようと思います。
(誰かやる気の出るデザインを下さい。)