nuxt.js/Firebase超初心者がnuxt.jsのv2をFirebaseにデプロイする

# TL;DR
nuxt.jsもFirebaseも分かってない超初心者がnuxt.jsをFirebaseにデプロイを試みた奮闘記。
参考文献からいいとこ取りしただけなので詳しくはページ下にある「参考文献」をご確認ください。

## ポイント
* nuxt.jsをFirebaseにデプロイ
* nuxt.jsは動的アプリケーションでデプロイしたい
    * `npm run generate` を使いたくない
* Firebase functionsはnode.js v8を使用する

## 今回のオチ
SparkプランだとFirebase functionsが使えない→SSRできない→ここまで頑張ったのに…
（できなかったんですが、方法としてはあってると思うので方法だけ備忘録として記載）
なにか間違ってるかもなのでご指摘いただけると助かります。

# 前提
## 環境
OS: macOS Mojave (バージョン10.14)
node: v8.11.1 (firebase functionsのサポートバージョン)
npm: 5.6.0

## 説明しないこと
* nuxt.jsインストール
* Firebaseの設定は多少省きます

## デプロイするWebアプリ

* nuxt.jsで構築
    * PWA
    * SPAでない
    * 動的アプリケーション

# 方法
## nuxt.jsでアプリを作成
とにかく作る

## Firebaseでプロジェクトの作成
Firebaseでプロジェクトを作成します。

1. [Firebase](https://console.firebase.google.com/) に移動
2. 「プロジェクト作成」からプロジェクトを作る

## ローカル環境でfirebase-toolsをインストール
デプロイするときに使用する `firebase-tools` をインストールします。

```
$npm install -g firebase-tools
```

## firebaseの設定
`firebase-tools` を使って初期設定を行う。

### ログイン
```shell

$ firebase login
```

### 初期設定
```shell
$ cd <<プロジェクトフォルダ>>
$ firebase init
```

今回 `hosting` と `functions` を使うのでこの2つを選択。
（他にも使うのであればほかも選択して良いと思います。）
<img width="1198" alt="スクリーンショット 2018-11-07 15.25.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/311964/ec798ffa-b94f-992a-76e0-89f81ab277d4.png">

### Functionsのセットアップ

```shell
=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? (Use arrow keys)
❯ JavaScript
  TypeScript
```
→JavaScriptを選択

```shell
? Do you want to use ESLint to catch probable bugs and enforce style? (y/N)
```
→お好みで

### Hostingのセットアップ

```shell
=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? (public)
```
→お好みのディレクトリ名で。ここでは一旦 `public` を選択。後ほど変更します。

```shell
? Configure as a single-page app (rewrite all urls to /index.html)? (y/N)
```
→作成したnuxt.jsのアプリに合わせて選択。

### セットアップ完了
```shell
✔  Wrote public/404.html
✔  Wrote public/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!
```
→最終的に publicフォルダと `frebase.json` と `.firebaserc` が作成される。

## 各種必要なソースを追加
順不同です。以下ソースをいじります。

* functions/index.js
* functions/package.json
* nuxt.config.js
* package.json
* firebase.json
* (念の為確認).firebaserc

### functions/index.js
サーバーサイドレンダリングを行うために以下を設定。

```js:functions/index.js
const functions = require("firebase-functions")
const { Nuxt } = require("nuxt")
const express = require("express")
const app = express()

const nuxt = new Nuxt({ buildDir: ".nuxt", dev: false })

function handleRequest(req, res) {
  res.set("Cache-Control", "public, max-age=300, s-maxage=600")
  return new Promise((resolve, reject) => {
    nuxt.render(req, res, promise => {
      promise.then(resolve).catch(reject)
    })
  })
}

app.use(handleRequest)
exports.ssr = functions.https.onRequest(app)
```

**【注意点】**

* `new Nuxt`　の　`buildDir`　は変更できますが、後ほど説明するpackage.jsonに合わせて変更する必要があります
* exports.ssrは使用するので、これも変更しないほうが良いかと思います

### functions/package.json
```json:functions/package.json
{
  "name": "functions",
  "engines": {
    "node": "8"
  },
  "dependencies": {
    "express": "^4.16.3",
    "firebase": "^5.5.7",
    "firebase-admin": "^6.0.0",
    "firebase-functions": "^2.0.5",
    "firebase-tools": "^5.1.1",
    "nuxt": "^2.0.0",
  },
  "private": true
}
```
**ポイント**

* enginesは node.jsのv8を選択
* express/firebase/nuxt/firebase-functionsは必要
* firebase-tools/firebase-adminはなくても良いかも

### nuxt.config.js
publicPathの設定を変更します。
やってもやらなくてもいいのですが、デフォルトの `_nuxt`　が微妙だと思ったら変更。  
デフォルトのまま行くぜ！って場合は今後 `assets` は `_nuxt` で読み替えてください。

```js:node.config.js
module.export = {
  // ...
  build: {
    publicPath: '/assets/',
  }
}
```


### package.json
`package.json` の `scripts` にビルドオプションを追加します。
今回は分かりやすいような名前にしますが、オプション名はお好みで。

```json:package.json
{
  ...
  "scripts" : {
    "build": "nuxt build",
    "build:firebase": "npm run clean && npm run build && npm run copy",
    "clean": "rm -rf public && rm -rf .nuxt",
    "copy": "mkdir -p public && cp -R functions public/server && cp -R static public/client && cp -R .nuxt public/server && cp -R public/server/.nuxt/dist/client public/client/assets",
  }
  ...
{
```

**ポイント**
* `build` : デフォルトのまま変更しません
* `clean` : 念の為、デプロイ前に前回の情報を削除します
* `copy` : server用のフォルダ、client用のフォルダをpublicフォルダ以下に用意します。nuxt.config.jsの `buildDir` や　`publicPath`　の設定を変えた場合はここ要注意です。

### firebase.json
```json:firebase.json
{
  "functions": {
    "source": "public/server"
  },
  "hosting": {
    "public": "public/client",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [{
      "source": "**",
      "function": "ssr"
    }]
  }
}
```

**ポイント**

* hosting, functionsはpublic以下に用意したclient/serverを指定する
* hostingに `rewrites` を追加。functionは functions/index.jsでexportした `ssr` を指定する

### (念の為確認) .firebaserc
`.firebaserc` で指定されているプロジェクト名が作成したプロジェクト名を指しているか確認

```json:.firebaserc
{
  "projects": {
    "default": "<<project_name>>"
  }
}
```

## デプロイ前の確認
### node_modulesのインストール
functions/package.jsonからインストール

```shell
$ cd functions
$ npm install
```

### package.jsonからインストール
```shell
$ cd ../ (functionsにいれば)
$ npm install
```

### ビルド
先程用意したスクリプトのオプションでビルドします。

```shell
$ npm run build:firebase
```

### ローカルでfirebase hosting/functionsを実行する
```shell
$ firebase serve
```

### ブラウザで起動を確認
ポート5000でデプロイされるはずなので http://localhost:5000 にアクセスし起動を確認。問題なければ `Ctrl + C` で終了

## デプロイ
```shell
$ firebase deploy
```

## 最終的なフォルダ構成
```
┬　.nuxt
├ assets
├ components
├ functions
│ ├ node_modules
│ ├ index.js
│ └ package.json
├ layouts
├ node_modules
├ pages
├ plugins
├ public
│ ├ client
│ └ server
├ static
├ store
├ .fireabserc
├ firebase.json
├ nuxt.config.js
└ package.json

```

以上。

# 余談
Sparkプランで上記を試したところ、Firebase Functionsで下記エラーが発生。

```text
Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions
```

なにか間違えたのだろうか…Sparkプランじゃできないと言う解釈なのですが、なにかご存知だったら教えてください。


# 参考文献
[Nuxt v2とFirebase(CloudFirestore)でPWA対応Webアプリ開発](https://qiita.com/_takeshi_24/items/3ee051e1db1b3e8da106)
[【v2対応しました】Nuxt.jsとFirebaseを組み合わせて爆速でWebアプリケーションを構築する](https://qiita.com/potato4d/items/cfddeb8732fec63cb29c)
[FirebaseにホストしていたSPA modeのNuxt.jsをSSRで稼働させるためにやったこと](https://qiita.com/yysaki/items/aa699ac02ad1875f9b21)
[Nuxt.jsとFirebaseでSPA×SSR×PWA×サーバーレスを実現する](https://inside.dmm.com/entry/2018/04/10/nuxt-firebase)
[Nuxt.js Universal App with SSR via Firebase Functions and Firebase Hosting](https://vuejsexamples.com/nuxt-js-universal-app-with-ssr-via-firebase-functions-and-firebase-hosting/)
[nuxt.js + firebase (cloud functions) で最小構成SSR](https://marunouchi-tech.i-studio.co.jp/5237/)