Help us understand the problem. What is going on with this article?

 FirebaseにホストしていたSPA modeのNuxt.jsをSSRで稼働させるためにやったこと

More than 1 year has passed since last update.

Qiita初投稿です。
最近のWebフロントエンドの技術について勉強しています。
趣味プロジェクトをFirebase Hosting上にNuxt.jsがSPA modeで稼働させていたのですが、今回はFirebase Cloud FunctionsでNuxt.jsのSSR modeで動作させるようにしてみました。
前提条件は以下の通りです:

  • node 8.12.0, npm 6.4.1
  • Nuxt.js 2.0.0
  • Firebase Hosting および Cloud Functionsを使用する
  • @nuxtjs/google-analytics などいくつか依存するnpmモジュールを使用している
  • Window.localStorage へのアクセスがあり、client固有の操作が必要

ソースコードとアプリはそれぞれ以下のURLにあります。

SSR modeを動作させるまでに何箇所かハマりどころがあったので、備忘録を兼ねて書きます。

まず nuxt コマンドがSSRモードで動くようにする

Nuxt.js をSPA modeで動かしている分にはあまり気にしないで済むのですが、SSRをするとなるとクライアントサイド、サーバサイド双方の処理で同等の結果が得られるように記述をしなければなりません。
あーありがち - Vue.js x SSRメモ の記事にあるように、

Server Side では data reactivity は不要であり、VM life cycle は以下の 2つしかない。

  • beforeCreate
  • created

であるので、双方で行う処理はこの中に入れる必要があります。
今回は、localStorageからGet/Setするクライアントサイド固有の処理が必要なので、created時点でハイドレートされたものとクライアントサイドのデータを一致させるようにして、描画時に行う処理はmountedイベントに設定しました。
またイベントフックで発生するfunctionには念のためprocess.clientで判定するようにしました。
SSR特有になるライフサイクルの知識はNuxt.jsの公式ガイドを読んでもあまり手に入らないので、先の引用した記事にもあるように Vue SSR ガイド | Vue.js サーバサイドレンダリングガイド を読むと捗りました。

この準備ができてからNuxt.js をSPAからSSRに変更させておきます。

package.json
"dev": "nuxt",
"build": "nuxt build",

デプロイ準備として firebase serve が動くようにする

ここではやることがいくつかあります。
まず、Cloud Functionsの最新であるnode js 8(まだβだけど)を使えるようにenginesを指定しておきます。
参考: Firebase Functions で Node.js 8 使えるようになったぞ〜〜〜!!!

functions/package.json
"engines": {
  "node": "8"
},

次にサーバサイドのエントリであるfunctions/index.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)

全てのルーティングをNuxt.jsに任せたいのでfirebase.json のリライトルールを設定でこれを呼び出すように指定する必要があります。

firebase.json
    "rewrites": [{
      "source": "**",
      "function": "ssr"
    }],

なお、Nuxt.js の設定をSSRに変更したので、ビルド先のデフォルト値が dist/ から .nuxt/dist に変更されます。
SPA時はpublicのディレクトリを dist/ に指定していましたが生成されなくなるため、別のものにしてやる必要があります。(ここではpublic/ にします)

firebase.json
  "hosting": {
    "public": "public",

また functions/index.jsの通りbuildDirを参照できるよう ビルド先を設定します。
参考: API: buildDir プロパティ - Nuxt.js

nuxt.config.js
module.exports = {
  buildDir: "functions/nuxt",

これで準備が整いました。 put push origin master してTravis CIにデプロイさせます。

※2019/03/10 追記:
GithubにpushしたらFirebaseにデプロイされるようにする - Qiita にTravis CIの設定について記事を書きました。

1度目のデプロイ、コケる

ログが見ると、functions/index.jsでrequireしているnuxtがみつからないと言われ失敗します。
Cloud Functionsでは functions/package.json にある依存関係を見るため失敗してしまいます。
functions/ディレクトリを npm install --save nuxt@2.0.0 を実行して再度デプロイします。

2度目のデプロイ、コケる

今度はデプロイに成功しましたが、サーバにアクセスすると500エラーとなってしまいました。
少し悩みましたが、これは1度目のデプロイ時の似た話でNuxt.jsが動作する時の依存モジュールをfunctions/package.jsonに設定していないのが現任でした。
参考 Nuxt.jsとFirebaseによるSPA × SSR × PWAでnode_modulesパッケージを追加する際の注意点
同様に./package.jsonで依存しているものを一通りインストールしておきます。

3度目のデプロイ、今度は成功

これでgit pushしたところ、ようやく想定通り動くアプリがデプロイできました。

アプリURLを開いてChromeの「ページの表示を開く」を見ると、しっかりレンダリングされたHTMLが表示されています。
またサーバサイドで"Cache-Control"レスポンスヘッダを設定しているので、画像の様に何度かアクセスするとCDNにキャッシュされていることが観測できます。
参考: Cloud Functions による動的コンテンツの配信 | Firebase

スクリーンショット 2018-10-21 12.28.59.png

感想

以上のように、Firebaseを使ってNuxt.jsのSSRを稼働させることができました。
SPA->SSRに変更するため頭を悩まされることが多かったですが、Vue.jsおよびNuxt.jsやFirebase自体の理解が浅かったのが原因ですので作業を進めるにつれ勉強になりました。
デプロイしてからのホスティングサーバ自体は簡易ですし、ありがたいことに無料枠の範囲内でここまで遊べているので、趣味のぽっと出の開発でも参入がしやすくなったなと思いました。

以上です。

その他参考URL

timedia
創業20周年の技術者集団。 確かな技術力・豊富な実績をもとに、多種多様な案件を手掛けるシステム会社のパイオニア
https://www.timedia.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした