5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SolidJSを使ってFirebase HostingでSSGを試す

Last updated at Posted at 2022-10-10

本記事に記載されている内容は、古いバージョンのSolid Startに基づいていますので、ご注意ください。

Firebase HostingにデプロイしたページにSSGしてみます。
Firebase HostingはSWR(stale-while-revalidate)が使えるようなので、Cloud Functions for Firebaseで得られる値をCDNにキャッシュしてみます。

  • SolidJSとSolid Startを使用します。
  • パッケージマネージャーにはpnpmを使います。

プロジェクトを作成する

プロジェクトのディレクトリを作成

$ mkdir ssg-firebase-solidjs
$ cd ssg-firebase-solidjs

準備

Blazeプランにする

あとで実際にデプロイして動かしてみます。
Cloud Functions for FirebaseはBlazeプランでなければ実行できないのでBlazeプランにしておく必要があります。

firebase tools をインストール

$ npm install -g firebase-tools

Firebase ログイン

CLIからもfirebaseにログインしていなければ先に進めませんので、以下のコマンドでログインしておきましょう。

$ firebase login

Firebaseを初期化

FunctionsとHostingを有効にして、TypeScriptを使えるようにしましょう。

$ firebase init

動的なデータを生成するバックエンド処理を作成する

Functionsで関数を作成する

モジュールをインストール

$ pnpm -C functions install

@firebase/app-typesがないというエラー(ERR_PNPM_PEER_DEP_ISSUES  Unmet peer dependencies)が発生した場合は以下でインストールしましょう。

$ pnpm -C functions add @firebase/app-types

関数を作成

functions/src/index.tsを開いて以下のように書き換えます。
CDNのキャッシュが効いているかどうかをわかりやすくするため、日時を返すようにしています。

functions/src/index.ts
import * as functions from "firebase-functions"

export const lastUpdatedAt = functions.https.onRequest((_, response) => {
  const dateTime = new Date()
  const formattedDate = `${dateTime.getFullYear()}-${dateTime.getMonth() + 1}-${dateTime.getDate()}`
  const formattedTime = `${dateTime.getHours()}:${dateTime.getMinutes()}:${dateTime.getSeconds()}`
  const json = {
    lastUpdatedAt: `${formattedDate} ${formattedTime}`,
  }

  response.send(json)
})

デプロイする

ビルド

$ pnpm -C functions build

デプロイ

$ firebase deploy --only functions

実行

デプロイが成功すると``のようなURLが表示されるので、Webブラウザーでアクセスしてみましょう。
以下のような表示になるはずです。

スクリーンショット 2022-10-10 0.22.52.png

ローカルで起動

後ほど、Webサイトをビルドするときにここで作成した関数を実行して結果をWebページに埋め込みます。
ビルド時にローカルで実行できるようにするために、firebase serveコマンドを使用してローカルでサーバーを起動しておきます。

$ firebase serve -only functions
✔  functions: Using node@16 from host.
i  functions: Watching "/path/to/ssg-firebase-solidjs/functions" for Cloud Functions...
✔  functions: Loaded functions definitions from source: lastUpdatedAt.
✔  functions[asia-northeast1-lastUpdatedAt]: http function initialized (http://localhost:5000/ssg-firebase-solidjs/us-central1/lastUpdatedAt).
i  functions: Beginning execution of "lastUpdatedAt"
i  functions: Finished "lastUpdatedAt" in ~1s

上記ログに表示されている通り、curl http://localhost:5000/ssg-firebase-solidjs/asia-northeast1/lastUpdatedAtを実行すると、以下のような結果が返ります。

$ curl http://localhost:5000/ssg-firebase-solidjs/us-central1/lastUpdatedAt
{"lastUpdatedAt":"2022-10-9 14:3:26"}    

コマンドはCtrl+Cするまで起動したままになるので、これ以降の作業を続けるには別のターミナルを開いてください。

SolidJSでスケルトンコードを作成する

SolidJSの環境を作成

firebase initで作成されたpublicディレクトリは作り直すので、いったん削除します。

$ rm -rf public

続いて同名のディレクトリを作成して、SolidJSのプロジェクトを作成していきます。

$ mkdir public
$ cd public
$ pnpm create solid

テンプレートの選択を要求されます。今回は深く考えずbareを選択します。

? Which template do you want to use? › - Use arrow-keys. Return to submit.
❯   bare
    durable-objects-websocket
    hackernews
    todomvc
    with-auth
    with-mdx
    with-solid-styled
    with-tailwindcss
    with-vitest

SSRかどうかと聞かれるのですが、デフォルトでよいのでEnterを押します。

? Server Side Rendering? › (Y/n)

TypeScriptを使うか問われるのでYを押します。

? Use TypeScript? › (y/N)

ディレクトリをプロジェクトのルートに戻しておきます。

$ cd ..

モジュールをインストールします。

$ pnpm -C public install

公開ディレクトリを変更する

ビルドすると、public/build/publicに結果が格納されるのでfirebase.jsonのhosting.publicの設定を以下のように変更しておきます。

firebase.json(抜粋)
  "hosting": {
    "public": "public/dist/public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }

SSGのアダプターを適用する

solid-start-nodeは不要なのでアンインストールします。

$ pnpm -C public uninstall solid-start-node

代わりに solid-start-static をインストールします。これはSSGを実現するアダプターです。

$ pnpm -C public install -D solid-start-static

CDNにキャッシュさせる設定を行う

Functionsの関数の実行にCDNを通す

rewrites設定で、Firebase Hostingを経由させることでCDNを通すようにし、headersで、キャッシュの設定を行っています。

キャッシュする時間は、あとで実験しやすくするために60秒とし、stale-while-revalidateの時間も60秒としています。(つまり120秒間キャッシュの値を返す設定になっている)

firebase.json(抜粋)
  "hosting": {
    "public": "public/dist/public",
    "rewrites": [{
      "source": "/lastUpdatedAt",
      "function": "lastUpdatedAt",
      "region": "us-central1"
    }],
    "headers": [ {
      "source": "/lastUpdatedAt",
      "headers": [ {
        "key": "Cache-Control",
        "value": "public, max-age=60, stale-while-revalidate=60"
      } ]
    }],
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }

静的サイトを作成する

404.htmlのファイルの修正

SSGのためにsolid-start-staticアダプターを使うと、ビルドしたときにエラーが発生してしまうので、public/src/routes/[...404].tsxの以下をコメントアウトします。

  • import { HttpStatusCode } from "solid-start/server";
  • <HttpStatusCode code={404} />

参考: https://pullanswer.com/questions/solid-start-static-adapter-throws-err_invalid_arg_type

index.tsxを書き換える

ビルド時にアクセスするAPIのURLと、実行時にアクセスするAPIのURLを切り替えるようにしていますが、動的に変更したいデータがなければビルド時のAPIだけで良いでしょう。
下記のビルド時のAPIのURLは、ビルド時に実行可能でなければなりません。

onMountはブラウザでしか動作しないのでビルドタイムとランタイムでAPIを分けることができます。

public/src/routes/index.tsx
import { createResource, createEffect, onMount } from "solid-js";

type LastUpdatedAtType = {
  lastUpdatedAt: string;
}

const BUILD_TIME_API = "http://localhost:5000/ssg-firebase-solidjs/us-central1/lastUpdatedAt";
const RUN_TIME_API = "/lastUpdatedAt";

export default function Home() {
  const [api, setApi] = createSignal<string>(BUILD_TIME_API);
  const [data, { refetch }] = createResource<LastUpdatedAtType, string>(api, async (url) => (await fetch(url)).json());

  onMount(() => setApi(RUN_TIME_API));

  return (
    <main>
      <p>Last updated at: {data()?.lastUpdatedAt || "-"}</p>
      <button onClick={() => refetch()}>Refetch!</button>
    </main>
  );
}

デプロイする

先に hosting のコンテンツをビルドしてから、firebaseにデプロイを行う。

$ pnpm -C public build
$ firebase deploy --only hosting

動作確認

Webブラウザーで表示すると以下のような表示になります。
SSGのため、ビルド時にlastUpdatedAtを実行した結果が埋め込まれた状態になるので、日時が表示されます。

スクリーンショット 2022-10-10 16.38.05.png

SWRを確認してみる

ビルドしてから60秒以上経っている前提ですが、画面を表示してRefresh!ボタンを押します。
それから60秒経過するまでの間、なんどRefresh!ボタンを押しても日時は変わりません。
Cloud Functions for Firebaseの関数は実行されず結果をCDNのキャッシュから返しているためです。

60秒経過して、120秒経過するまでにRefresh!ボタンを押しても日時に変更はありません。

そして、120秒経ってもう一度Refresh!ボタンを押すと日時は変わります。しかし、このときに表示される日時はボタンを押した日時ではありません。60秒経過して、120秒経過するまでにRefresh!ボタンを押した時の日時が表示されます。

上記の通りになればSWRによって日時がフェッチされたことが確認できたと言っていいと思います。

最後に

いかがでしたでしょうか。Firebase HostingでSolidJSを使いSSGを実現してみただけでなく、SWRによるCDNのキャッシュの動きも確認してみました。

なかなか前衛的なモジュールを使っての試みだったので、いつまで本ページの内容が実験可能であるか保証はできませんが、興味のある方はトライしてみてください。
一応、package.jsonの中身も記載しておきます。

functions

package.json
{
  "name": "functions",
  "scripts": {
    "lint": "eslint --ext .js,.ts .",
    "build": "tsc",
    "build:watch": "tsc --watch",
    "serve": "npm run build && firebase emulators:start --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "16"
  },
  "main": "lib/index.js",
  "dependencies": {
    "@firebase/app-types": "^0.8.0",
    "firebase-admin": "^10.0.2",
    "firebase-functions": "^3.18.0"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^5.12.0",
    "@typescript-eslint/parser": "^5.12.0",
    "eslint": "^8.9.0",
    "eslint-config-google": "^0.14.0",
    "eslint-plugin-import": "^2.25.4",
    "firebase-functions-test": "^0.2.0",
    "typescript": "^4.5.4"
  },
  "private": true
}

hosting

package.json
{
  "name": "public",
  "scripts": {
    "dev": "solid-start dev",
    "build": "solid-start build",
    "start": "solid-start start"
  },
  "type": "module",
  "devDependencies": {
    "solid-start-static": "^0.1.5",
    "typescript": "^4.8.3",
    "vite": "^3.1.0"
  },
  "dependencies": {
    "@solidjs/meta": "^0.28.0",
    "@solidjs/router": "^0.5.0",
    "solid-js": "^1.5.7",
    "solid-start": "^0.1.0",
    "undici": "^5.10.0"
  },
  "engines": {
    "node": ">=16"
  }
}

以上です〜!

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?