【Nuxt3×SPA×SSR】ハイブリッドレンダリングを駆使してFirebaseでホスティングする

破壊的なアップデートであることを除けば、パフォーマンスやコード量の少なさなど、慣れれば旨みがたくさんのNuxt3です。今回は素敵アップデートの一つ、ハイブリッドレンダリングを試してみます。

2023年05月21日

先日当ブログを「Nuxt2×SSG」から「Nuxt3×SPA×SSR」にアップデートしてみました。
Firebaseにもハイブリッドレンダリングに対応したホスティングをしていきます。
1からハンズオン形式で説明しようと思いましたが、とってもボリュームがあるのでそのうち。

今回やりたいこと

従来のNuxt2では、SSG, SSR, SPA の選択肢しかありませんでしたが、Nuxt3へのアップデートにより、静的なページと動的なページをそれぞれ別のレンダリング方式で対応できるようになりました。

  • トップページはSPA
  • 記事ページではSSR

のような選択肢を選べるようになったのです。
これらの機能を使って、SPAとSSRのルートをそれぞれFirebaseにデプロイしていこうと思います。

ISRという者

ISR = インクリメンタル静的再生成
CDN キャッシュに応答を追加することで、リクエストに対して静的ページを返してくれます。
キャッシュの有効期限が切れたらSSRにて再生成を行うという最強レンダリングモードです。

サポートしているプラットフォームが Netlify または Vercel ということで、今回の対応では省きました。
※ Netlifyへの移行をとっても悩みました

必要な設定

  • nuxt.config.ts
  • 必要なモジュールを入れる
  • firebase.json
  • firebase-hosting-merge-xxx.yml
    ※ 自動デプロイの場合

レンダリングルール

まず、レンダリングルールをルート別に振り分けます

nuxt.config.ts

export default defineNuxtConfig({
  routeRules: {
    '/': { ssr: false },
    '/posts/*': { headers: { 'Cache-Control': 'public, max-age=60, immutable' } },  
  },
  nitro: {
    preset: "firebase"
  },
  ~~~
})

トップページへのリクエストには静的ページを返す。
記事ページへのリクエストには動的にページを生成して返す。

こちらの設定方法については公式で詳しく説明してくれてます

mode 指定方法 プラットフォーム
SPA ssr: false Firebase, Netlify, Vercel
SSG prerender: true Firebase, Netlify, Vercel
SSR 指定なし Firebase, Netlify, Vercel
ISR isr: true Netlify, Vercel
SWR swr: true Netlify, Vercel

モジュールを入れる

Nitroのビルドに対応するモジュールを入れます

$ yarn add firebase 
$ yarn add --dev firebase-admin firebase-functions firebase-functions-test

Firebaseの設定

firebase.json

{
  ~~~
  "functions": [
    {
      "source": ".output/server", # SSR用の関数がここに配置される
      "codebase": "default"
    },
    {
      "source": "functions",
      "codebase": "original",
      "ignore": [
        "node_modules",
        ".git",
        "firebase-debug.log",
        "firebase-debug.*.log"
      ],
      "predeploy": [
        "npm --prefix \"$RESOURCE_DIR\" run build"
      ]
    }
  ],
  "hosting": [
    {
      "target": "front",
      "cleanUrls": true,
      "rewrites": [
        {
          "source": "/*", 
          "function": "server", # SSR用の関数を指定する
          "region": "asia-northeast1" # ちょっとテクいことをする
        }
      ],
      "public": ".output/public", # 静的ファイルがここに配置される
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ]
    }
  ],
  {
    "target": "admin",
    ~~~
  }
}

※ デプロイした関数にprefixが設定するため、それぞれcodebase に名前を当てています。
コメントを入れている箇所以外は実装に合わせて変えてもらって大丈夫です。

asia-northeast1 にしたい

Nitroサーバーのデプロイ先は、デフォルトで us-central1 に設定されています。

そのため、デプロイ前に以下のファイルを書き換えなければいけません。
.output/server/chunks/nitro/firebase.mjs

自動デプロイが絡むとちょい手間です。

実はreplaceできる

先ほどのnuxt.config.tsに変更を加えていきます。
※ Firebase CLI 12.x系だとちょっと違うので注意

export default defineNuxtConfig({
  routeRules: {
    '/': { ssr: false },
    '/posts/*': { headers: { 'Cache-Control': 'public, max-age=60, immutable' } },  
  },
  nitro: {
    preset: "firebase",
    replace: {
      [`functions.https.onRequest`]: `functions.region('asia-northeast1').https.onRequest`,
    }
  },
  ~~~
})

手で書き換える必要がなくなりました!うれしい

自動デプロイ(Git Actions)

SPA, SSRと分けているため、HostingとSSR用関数を分けてデプロイします。

name: Deploy to Firebase on merge
'on':
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x]
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: yarn

      - name: Create .env file
        run: |
          touch .env
          echo "${{ secrets.ENV_PRD }}" > .env
          cat .env

      - name: 📥 Download
        run: yarn && yarn build

      - name: 🚀 Deploy Server
        run: npx firebase-tools deploy --only functions:server
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

      - name: 🚀 Deploy Hosting
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_XXX }}'
          channelId: live
          projectId: xxx
          target: front

おわり

実装上、SPAの部分もサーバーサイドで生成されるようなわけのわからん形です。
ただ、クライアントサイドでレンダリングされるので、レスポンシブ対応などが割と楽だったら、隠れた旨みでもあります。

当ブログの実装とはちょいちょい違いますが、SSRへの移行は割とすんなりといきました。
Firebaseの認証やVuetifyのSSR対応など、1から実装するには本来もう少し範囲は広めです。

Nuxt3×SSRはまだまだ知らない箇所が多いので、今後この記事も修正しつつアウトプットしていこうと思います。

筆者情報
IT業界経験6年目のフルスタックエンジニア。
フロントエンドを軸として技術を研鑽中でございます。