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](https://www.npmjs.com/package/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](https://github.com/nuxt-community/dotenv-module) は公式サイトにも以下の記述があるように、ビルドフェーズで動作しないため、明示的にコレを読み込む必要があります。

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

以上で構築は完了です。

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

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