FirebaseのCloud FunctionsでNuxtのSSRをする方法

# はじめに

<a href="https://codestock.web.app/" target="_blank">![ogimage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634204/07c66a12-efb0-4c76-01ac-91f37c354366.png)</a>
よく使うけど使うたびにググったり過去のプロジェクトから引っ張り出してくる自分だけのお気に入りのコードありませんか？
CODESTOCKはそんなちょっとした自分のためのコードをストックするサービスです。
https://codestock.web.app/

上記は私が個人で開発しているサービスです。もともとNuxtのSPAモードをFirebaseのHostingでデプロイしていましたが、開発している中でSEO対策のためにSSR化をする必要が出てきました。
ところがもともとNuxt用に作っていたプロジェクトにfirebase functionsを追加したところなかなか思ったような挙動にならなかったので、その時の対応策を書きます。

# プロジェクトにCloud Functionsをインストールする

Nuxtのプロジェクトにfirebase hostingだけインストールしていたもともとのディレクトリ構造は以下のようになっています。
※xxx/はディレクトリを表しています。

```
├── README.md
├── firebase.json
├── jsconfig.json
├── node_modules/
├── nuxt.config.js
├── package-lock.json
├── package.json
├── public/←ホスティング対象のディレクトリ
├── components/
├── layouts/
├── pages/
├── plugins/
├── static/
└── store/
```

調べると以下のインストールコマンドが出てくると思うので、それを実装します。

```
firebase init functions
```
普通にFirebase Cloud Functionsをインストールすると、プロジェクトルートにfunctionsディレクトリができて以下のようなディレクトリ構成になるかと思います。

```
.
├── README.md
├── firebase.json
├── functions/←コマンドで追加されたディレクトリ
├── jsconfig.json
├── node_modules/
├── nuxt.config.js
├── package-lock.json
├── package.json
├── public/
├── components/
├── layouts/
├── pages/
├── plugins/
├── static/
└── store/
```

このまま状態だとfirebase deployコマンドでデプロイされるのが、publicとfunctionsになっておりNuxtのbuildファイルがfunctionsで使えないためSSRができません。

# Cloud FunctionsでのSSR

Nuxtのファイルをfunctionsでレンダリング方法はいくつかあるようなのですが、よく出てくるのが以下のようにfunctionsディレクトリの中にfunctionsのexpressでレンダリングするためのNuxtのビルドファイルを格納するという方法です。

```
.
├── functions
    ├── index.js
    ├── node_modules/
    └── dist/←ここにnuxtのbuildファイル
 .
 .
```

この方法に関する丁寧な記事は結構あるのですが、私のプロジェクトではうまく動きませんでした。

functionsディレクトリの中にもnode_modulesがあるのですが、どうやら調べたところCloud FunctionsでSSRするためには、functionsディレクトリの方にNuxtのbuildファイルだけでなくNuxtで使っているnpmパッケージもインストールする必要があるようでした。

しかしそれを今のディレクトリ構造のまま行ってしまうと、以下のようにNuxt本体のnode_modulesとCloud Functionsのnode_modulesでパッケージを二重管理をしなければいけなくなってしまうので、これをどうにかさける必要がありました。

```
.
├── functions
    └── node_modules/←同じパッケージ
 .
 .
├── node_modules/←同じパッケージ
 .
 .
```



# プロジェクトルートをCloud Functionsのディレクトリにする
そこでディレクトリ構造を以下のようにして、プロジェクトで使用するnode_modulesを一括管理するようにしました。

```
.
├── README.md
├── firebase.json
├── index.js←NEW
├── jsconfig.json
├── node_modules/
├── nuxt.config.js
├── package-lock.json
├── package.json
├── components/
├── layouts/
├── pages/
├── plugins/
├── static/
└── store/
```

上記のディレクトリ構造にするための作業を書いていきます。

### `/functions/package.json`に書いてあるパッケージをインストール
以下のモジュールをNuxt用のプロジェクトにインストールしました。

```
"firebase-admin": "^9.0.0",
"firebase-functions": "^3.8.0",
```

### `/functions/package.json`に書いてあるenginesの設定を転記

以下の記述をNuxt用のpackage.jsonに追記しました。

```
"engines": {"node": "10"}
```

### `/functions/index.js`に書いてあったサーバー側の処理をルートのindex.jsに記述

本当は`server.js`みたいな名前にしたかったのですが、server.jsの状態でdeployしようとしたらエラったのでindex.jsというファイル名にしています。

```
const functions = require('firebase-functions');
const { Nuxt } = require('nuxt');
const nuxt = new Nuxt({
    buildDir: '.nuxt',
    dev: false
});

const runtimeOpts = {
    timeoutSeconds: 300,
    memory: '1GB'
}

exports.ssr = functions.runWith(runtimeOpts).https.onRequest(async (req, res) => {
    res.set('Cache-Control', 'public, max-age=43200, s-maxage=86400')
    await nuxt.ready()
    return nuxt.render(req, res)
})
```

以下の記述はcloud functionsをキャッシュさせる設定です。

```
res.set('Cache-Control', 'public, max-age=43200, s-maxage=86400')
```

以下の記述はCloud Functionsの実行スペック等の設定です。

```
const runtimeOpts = {
    timeoutSeconds: 300,
    memory: '1GB'
}
```

### firebase.josnの記述を修正
画像ファイルなどの静的ファイルはhostingから配信できるように、hostingのpublicはNuxtの`static`ディレクトリに向けて設定します。

```
{
  "hosting": {
    "site": "codestock",
    "public": "./static",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "ssr"
      }
    ]
  },
  "functions": {
    "source": "./",
    "ignore": [
      "firebase.json",
      "**/node_modules/**"
    ]
  }
}
```

これでNuxtベースのディレクトリ構造のままFirebaseのCloud FunctionsでSSRできました。

# 参考URL
- <a href="https://marunouchi-tech.i-studio.co.jp/5237/" target="_blank">nuxt.js + firebase (cloud functions) で最小構成SSR
- <a href="https://qiita.com/okamuuu/items/c9f2989241af4a1bf588" target="_blank">SSR モードの Nuxt.js を Firebase に Deploy する Tutorial
- <a href="https://www.isoroot.jp/blog/1604/" target="_blank">Vue.jsアプリをIP制限とBasic認証つけてFirebaseにHostingする