Firebase公式動画を参考にFirebaseとNuxt.jsを使ってSSRをServerlessで実現する

# TL;DR

サンプルコードは[Github](https://github.com/wataruoguchi/nuxtjs-firebase-example)にあります。
今回、FirebaseとNuxt.jsを使ってSSR(サーバーサイドレンダリング)をサーバーレスで実現するところまで、動画を見ながら足りないところを補完して自分でまとめた手順を説明していきます。
Firebase公式動画は[こちら](https://youtu.be/ZYUWsjUxxUQ)です。

# はじめに

Wataruです。カナダのバンクーバーというところでCivic Tech関連の小さな会社でソフトウェアデベロッパーとして働いています。
なぜカナダで働いているか、もし興味があったら[note書きました](https://note.mu/wataruoguchi/n/n8992fd5d3cef)ので読んで頂けると嬉しいです。
仕事の半分以上はフロントエンドを担当しています。もちろん、PostgreSQLのトリガー書いたり、PerlでAPIを書くこともあるし、たまにNginx、DockerやJenkins Pipelineのメンテしたりもします。色々学べるのは楽しいですね。

弊社のような小さな会社では、フロントエンド担当やバックエンド担当は特に設けておらず、DevOps担当のデベロッパーもアプリ側のコードを触ったりすることがあります。

最近になってようやく[脱jQuery](https://itnext.io/how-we-install-vue-js-in-jquery-app-17e745df735e)を試みているのですが、フロントエンド担当で無くても直感的に理解ができるものを、という理由でVue.jsを使っています。
フレームワークに慣れていない人でも触れるフレームワーク。いいですよね。

今回はVue.jsに慣れているけどFirebaseに触ったことがない、触ってみたい、ということで、どうせなら、とNuxt.jsでFirebaseを触ってみることにしました。

# Step 0: 参考

本記事を書くにあたって参考にした記事や動画です。
- [Server-Side Render Vue Apps with Nuxt.js(Server-side Rendering with JavaScript Frameworks)](https://youtu.be/ZYUWsjUxxUQ) (Firebase公式動画)
- [Nuxt.jsとFirebaseでSPA×SSR×PWA×サーバーレスを実現する](https://inside.dmm.com/entry/2018/04/10/nuxt-firebase)
- [Nuxt.jsとFirebaseを組み合わせて爆速でWebアプリケーションを構築する](https://qiita.com/potato4d/items/cfddeb8732fec63cb29c) by [potato4d](https://qiita.com/potato4d)

上のリンクの動画をベースに、Firebase初心者向け（主に自分）のために書きました。[英語版はこちら。itnext.ioに掲載されました！](https://itnext.io/how-to-create-a-ssr-serverless-app-with-firebase-nuxt-js-in-an-hour-6e6e03d0b3b8)

# Step 1: プロジェクト用のフォルダを作りましょう

```1.sh
$ # プロジェクト用のフォルダ(<proj>、任意に決めてください)を作って、そのフォルダの中へ移動。
$ mkdir <proj> && cd $_
```

# Step 2: Nuxt.jsをインストールしてみましょう

```2.sh
$ # vue-cliとyarnをインストールします。
$ sudo npm i -g vue-cli yarn
$ # nuxt-community/starter-templateを元にsrcフォルダへNuxtアプリが自動設定されます。
$ vue init nuxt-community/starter-template src
$ # 質問への回答は全部デフォルトでいいかと思います。Enterを押し続けてください。
  - ? Generate project in current directory?
  - ? Project name
  - ? Project description
  - ? ? Author
$ # srcフォルダへ移動し、npmパッケージをインストールします。
$ cd src/ && yarn
```

何も分からなくても、これだけで初期プロジェクトの設定ができてしまいました。いい時代ですね。

# Step 3: トップページを作ってみましょう

今回の手順ではトップページしか作りません。Nuxt.jsのトップページは`src/pages/index.vue`にあたります。

```3.sh
$ # srcフォルダにいることを確認してください。
$ # 動画ではHTTPリクエストにisomorphic-fetchを使っています。
$ # しかしNuxt.js公式がaxiosの使用を超勧めていますので長い物には巻かれます。
$ yarn add axios
$ # デフォルトのindex.vueの中身を消して、一から作ります。
$ echo '' > pages/index.vue && vi pages/index.vue
```

pages/index.vueはこんな感じです。https://nuxt-ssr.firebaseio.com/facts.json で得た結果をリストに表示。簡単ですね。

```3.pages/index.vue
<template>
  <ul>
    <li v-for="(fact, factIdx) in facts" :key="factIdx">
      {{ fact.text }}
    </li>
  </ul>
</template>
<script>
import axios from 'axios'
export default {
  async asyncData() {
    const res = await axios.get('https://nuxt-ssr.firebaseio.com/facts.json')
    const facts = res.data
    return { facts }
  }
}
</script>
<style>
</style>
```

```3.1.sh
$ # nuxt.config.jsという設定ファイルを編集します。
$ vi nuxt.config.js
```

nuxt.config.jsのファイルの中の、`build`の中に以下を追加してください。

```3.nuxt.config.js
publicPath: '/public/',
vendor: ['axios'],
extractCSS: true,
```

# Step 4: localhostで確認してみましょう

インストールとトップページの編集しかしていないNuxt.js。これが本当に動くのか、localhostで確認してみましょう。

```4.sh
$ # srcフォルダにいることを確認してください。
$ # yarn devでdev webサーバを起動して、Nuxt.jsも起動させます。
$ yarn dev
```

http://localhost:3000 で起動したよ、というメッセージが出てくるので、ブラウザで開いて確認してください。

ページを開いて三行くらいリストアイテムが表示されていたら、おめでとうございます。Nuxtアプリが動いていることが確認できました。

Chromeの'View Page Source'でページのソースを確認してみると、サーバーサイドレンダリングされていることがわかりますね。

# Step 5: Babelを使って古いブラウザーをサポートしておきましょう

昔話になりますが、日本で働いていた当時、IEのシェアがとても大きかったです。新しいJSで作られたアプリはIEのようなブラウザが対応できない可能性があります。
これを解決するためにBabelを使います。

```5.sh
$ # srcフォルダにいることを確認してください。
$ # babelプラグインをインストールします。
$ yarn add -D babel-plugin-module-resolver babel-plugin-transform-runtime babel-preset-es2015 babel-preset-stage-0
$ # nuxt.config.jsという設定ファイルを編集します。
$ vi nuxt.config.js
```

nuxt.config.jsにbabelプラグイン等を認識させるために、ファイルを編集します。`build`の中に以下を追加してください。

```5.nuxt.config.js
babel: {
  presets: [
    'es2015',
    'stage-0'
  ],
  plugins: [
    ['transform-runtime', {
      'polyfill': true,
      'regenerator': true,
    }]
  ],
},
```

また、同ファイルの`vendor`を以下のように更新してください。`'babel-polyfill'`を追加するだけです。

```5.1.nuxt.config.js
vendor: ['axios', 'babel-polyfill'],
```

再度ブラウザで確認してみましょう。

```5.1.sh
$ # srcフォルダにいることを確認してください。
$ # yarn devでdev webサーバを起動して、Nuxt.jsも起動させます。
$ yarn dev
```

http://localhost:3000 で起動したよ、というメッセージが出てくるので、ブラウザで開いて確認してください。先ほどと同じ結果が表示されるはずです。うまく行かなかった場合は`package.json`の各パッケージのバージョンを確認してみてください。[[GitHub]](https://github.com/wataruoguchi/nuxtjs-firebase-example/blob/master/src/package.json)

# Step 6: Firebaseをインストールしてみましょう

ここからFirebaseをインストールしていきます。

```6.sh
$ # <proj>フォルダに移動します。
$ cd <proj>
$ # firebase-toolsをインストールします。
$ sudo npm i -g firebase-tools
$ # ここでユーザログインをします。firebase公式のチュートリアルに沿って操作しました。
$ firebase login
$ # Cloud Functionsを含め、初期設定を行います。
$ firebase init functions
$ # プロジェクトが無いと思うので[create a new project]を選択します。
  - ? Select a default Firebase project for this directory:
$ # どの言語（JSかTS）を使いたいか聞かれます。以降の質問に対してはデフォルトで良いです。
  - ? What language would you like to use to write Cloud Functions?
  - ? Do you want to use ESLint to catch probable bugs and enforce style? 
  - ? Do you want to install dependencies with npm now?
```

https://console.firebase.google.com を開くよう指示が出ると思うので開きます。確かユーザー認証だったと思います。

```6.1.sh
$ # プロジェクトを選択できるよう追加します。
$ firebase use --add
```

# Step 7: Cloud Functionsの設定をしてみましょう

Firebaseの機能であるCloud Functionsの設定をしてみましょう。Cloud FunctionsはSSRのために使います。サーバー側での仕事をこの機能に丸投げします。

```7.sh
$ # nuxtの設定ファイルを編集します。
$ vi src/nuxt.config.js
```

以下の一行を`nuxt.config.js`のトップレベルに追加します。これでビルド先が`functions`フォルダに設定されます。

```7.nuxt.config.js
buildDir: '../functions/nuxt',
```

次は`firebase init functions`のコマンドで作成された`functions`フォルダに移動して作業をしましょう。

```7.1.sh
$ # functonsフォルダに移動します。
$ cd functions
$ # package.jsonを編集します。
$ vi package.json
```

Cloud Functionsは[2018年8月にnode 8に対応できるようになりました](https://firebase.googleblog.com/2018/08/cloud-functions-for-firebase-config-node-8-timeout-memory-region.html)。node 8を有効にするため、`"engines": { "node": "8" }`をトップレベルに追加してください。
`src/package.json`にある全てのパッケージをコピペして追加してください。

```7.2.sh
$ # 追加したパッケージをインストールします。
$ yarn
$ # express をインストールします。
$ yarn add express
$ # srcフォルダに移動し、アプリをビルドし、functionsフォルダに戻ってきましょう。
$ cd ../src/ && yarn build && cd ../functions/
```

アプリのビルドによって、今まで無かった`nuxt`フォルダが`functions`フォルダに作成されています。

# Step 8: Cloud FunctionsでNuxt.jsを動かせるようにしましょう

上記までの手順でNuxt.jsのコードがfunctionsフォルダにコピーされました(symlink作っても良いと思いますが)。
Cloud Functionsは`functions`フォルダの`index.js`を起点として動きます。`index.js`を編集してみましょう。

```8.sh
$ # functionsフォルダにいることを確認してください。
$ echo '' > index.js && vi index.js
```

`functions/index.js`はこんな感じになります。アプリがNuxtにリクエストを与えてページをレンダリングさせていますね。

```8.index.js
const functions = require('firebase-functions')
const express = require('express')
const { Nuxt } = require('nuxt')

const app = express()

const config = {
  dev: false,
  buildDir: 'nuxt',
  build: {
    publicPath: '/public/'
  }
}
const nuxt = new Nuxt(config)

function handleRequest(req, res) {
  res.set('Cache-Control', 'public, max-age=600, s-maxage=1200')
  nuxt.renderRoute('/').then(result => {
    res.send(result.html)
  }).catch(e => {
    res.send(e)
  })
}
app.get('*', handleRequest)
exports.nuxtApp = functions.https.onRequest(app)
```

# Step 9: Firebase Hostingで静的アセットを配信できるようにしましょう

静的アセットを動的配信する理由はないので、Firebase Hostingを利用して静的配信しましょう。

```9.sh
$ # functionsフォルダにいることを確認してください。
$ # <proj>フォルダに移動します。
$ cd ../
$ firebase init hosting
$ # 質問に対する回答は全部デフォルトで良いです。
  - ? What do you want to use as your public directory?
  - ? Configure as a single-page app (rewrite all urls to /index.html)?
```

これで`public`フォルダが作成されました。このフォルダに静的アセットを置いていきます。

```9.1.sh
$ # ページは全てNuxt.jsがレンダリングするので、標準でできたファイルは消しましょう。
$ rm public/404.html public/index.html
$ # functions/nuxtからdistをpublic/assetsにコピーしましょう。
$ cp -R functions/nuxt/dist public/assets
$ # src/static/からもpublicにコピーしましょう。
$ cp -R src/static/* public
```

# Step 10: Firebaseでページ表示してみましょう

いよいよアプリをFirebase上にデプロイしてみます。

```10.sh
$ # <proj>フォルダにいることを確認してください。
$ # firebase.json を編集します。
$ vi firebase.json
```

`hosting`の下にある`rewrites`を以下のように編集します。これで`functions/index.js`で作った`nuxtApp`が実行されるようになります。

```10.firebase.json
"rewrites": [
  {
    "source": "**",
    "function": "nuxtApp"
  }
]
```

まずはlocalhostで表示を確認してみましょう。

```10.1.sh
$ sudo firebase serve --only functons,hosting
```

functionsの準備ができるまで時間がかかるのですが、緑色のチェックがついたら http://localhost:5000 を開いてみましょう。先ほどの http://localhost:3000 はNuxt.jsの、http://localhost:5000 はFirebaseの設定したlocalhost portになります。
他のportを選択したい場合、`firebase serve --only functons,hosting -p 5001`のようにportを設定できるオプションもあります。

ページが表示されたら成功です。

...

しかし、ブラウザ上でコンソールを開いてみてください。"Uncaught SyntaxError"が表示されているかと思います。これを直していきましょう。

```10.2.sh
$ # <proj>フォルダにいることを確認してください。
$ # src/nuxt.config.jsを編集します。
$ vi src/nuxt.config.js
```

publicPathを`/public/`から`/`に変更してください。`functions/index.js`にも同様に変更を加えます。

```10.3.sh
$ # <proj>フォルダにいることを確認してください。
$ vi functions/index.js
```

変更が終わったら、`src`フォルダへ入って再度アプリをビルドしてみましょう。

```10.4.sh
$ # <proj>フォルダにいることを確認してください。
$ # srcフォルダへ移動してビルドをします。
$ cd src && yarn build
```

改めて、localhostで確認してみましょう。

```10.5.sh
$ # <proj>フォルダに移動して静的アセットをコピー
$ cd .. && cp -R functions/nuxt/dist/ public && cp -R src/static/* public
$ # localhostで実行
$ sudo firebase serve --only hosting,functions
```

ブラウザ上で http://localhost:5000 を開き、コンソールを見てみるとエラーが消えているはずです。エラーが消えていたらおめでとうございます。最後にデプロイをしましょう。

```10.6.sh
$ # <proj>フォルダにいることを確認してください。
$ firebase deploy
```

少し時間がかかります。"✔  Deploy complete!"のメッセージが表示されるまで待っててください。表示されたら"Hosting URL:"に書かれたURLを開いて見ましょう。
https://localhost:5000 で見たものと同じページが表示されたら成功です。お疲れ様でした。

# おわりに

無事にデプロイできたことを祈っています。

この手順で複雑なことをやっている訳ではありませんが、SSR+サーバーレスがこんなに簡単に作れてしまうのは驚きですね。Nuxt.jsとFirebase。無料で手早くサービスが作れることから、ハッカソン、MVP(Minimal Viable Product)なサービス開発や初期ステージでのイテレーションで役に立ちそうですね。私自身、Firebase初心者ですが、これから楽しんでいけたらと思っています。

読んでいただきありがとうございました。