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


はじめに


よく使うけど使うたびにググったり過去のプロジェクトから引っ張り出してくる自分だけのお気に入りのコードありませんか?
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