SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Remixを通じてWebを学ぶ

Remixで作ったWebサイトをCloudflare Pagesの環境で動かす

Remixを通じてWebを学ぶ 第10回

  • X ポスト
  • このエントリーをはてなブックマークに追加

 Remixで作ったWebサイトをCloudflare Pagesの環境で動かすために、通常のRemixプロジェクトとは少し異なる設定が必要になります。本記事では、Remixの設定項目が通常と違っている点を解説します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

対象読者

  • Reactの基本を修めている方
  • 通信回線が弱いユーザーにも高速に表示できるサイトを作りたいエンジニア
  • WebブラウザとNode.jsという異なるランタイムをそれぞれキャッチアップするのが辛くなってきたエンジニア

前提環境

 筆者の検証環境は以下の通りです。

  • macOS Sonoma 14.5
  • Node.js 22.4.1
  • NPM 10.8.1
  • Remix 2.9.2

プロジェクトの構成を確認する

 さて、前回の手順でプロジェクトが作成できましたが、通常のRemixプロジェクトとは少し異なる部分があります。簡単におさらいしていきましょう。図1では、前回作成したRemixプロジェクトの構成を示しています。通常のnpm create remix@latestというコマンドで作成したプロジェクトと比べて、追加されたファイルにピンクの下線を引き、大きな変更があったファイルには青い囲みを付けています。

図1:プロジェクト構成
図1:プロジェクト構成

 それぞれのファイルの役割や変更点を上から順に簡単に解説します。

app/entry.server.tsx

 app/entry.server.tsxは、サーバーサイドレンダリング処理のエントリーポイントです。サーバーが受け取ったリクエストに対して、レスポンスを構築するための入り口となるファイルのことですね。

 このファイルには大きな変更がありました。Node.js向けにnpm create remix@latestで作成した場合、このentry.server.tsxのテンプレートは140行程度です。一方、Cloudflare向けのテンプレートでは40行程度に収まっています。これは、RemixサーバーがWeb標準のストリームAPIのインターフェースであるReadableStream型のデータをレスポンスボディとして返しているために起きた差ではないかと思われます。

 Cloudflare Workersの処理系は、ブラウザのService WorkerをCDNのエッジサーバーで動かしているため、ブラウザと同じようにストリームAPIを扱うことができます。一方、Node.jsのストリームAPIはv16で初登場し、長らくExperimental(実験的な機能)扱いだったのが、v21でStable(安定版)になりました。

 RemixではNode.js v21未満のバージョンもサポートしているため、2024年現在は安定版ではないストリームAPIには頼らない形でentry.server.tsxを実装しているものと思われます。いつか、Node.jsの古いバージョンをサポートしなくなった頃に、Node.js向けのテンプレートもReadableStreamを前提としたものに変わるのかもしれません。

functions/[[path]].ts

 functions/[[path]].tsは、Page Functionsのエントリーポイントです。Webサイトにアクセスがあったときに、URLのパスに対応するパスのファイルがfunctions内にあった場合、そのファイルが実行されます。たとえば、/aboutにアクセスがあった場合には、functions/about.tsが実行されるわけですね。

 今回のように[[abc]].tsのような[[]]で囲まれた部分は、ワイルドカードとして扱われ、任意のパスにマッチします。つまり、functions/[[path]].tsは、どんなパスにもマッチするエントリーポイントになるのです。詳しくは、Page Functionsのドキュメントを参照してください。

 内容はシンプルで、Remixのハンドラに処理を委譲しているだけです。Remixフレームワーク内の流れとしては、まずはfunctions/[[path]].tsがすべてのリクエストを受け付けて、Remixのハンドラを通じてapp/entry.server.tsxに具体的な処理を委譲する形になっています。

public/_headers

 public/_headersCloudflare Pages固有の設定ファイルです。所定のパスにアクセスがあったときに、デフォルトで付与するヘッダー情報を記述することができます。今回のケースでは、リスト1のように設定されています。

[リスト1]public/_headers
/favicon.ico
  Cache-Control: public, max-age=3600, s-maxage=3600
/assets/*
  Cache-Control: public, max-age=31536000, immutable

 /favicon.icoのキャッシュを1時間に設定するとともに、/assets/*以下のファイルは1年間キャッシュするように設定されています。/assetsは見慣れないパスかもしれませんが、npm run buildコマンドでビルドしたときに生成される.jsファイルや.cssファイルが格納される、build/client/assetsフォルダを指しています。Cloudflare Pagesには/build/clientフォルダをルートディレクトリとして静的ホスティングするので、デプロイ後にキャッシュ対象とするURLは/assets/*になるわけです。

public/_routes.json

 public/_routes.jsonPage Functions固有の設定ファイルです。リクエストがあったときにfunctions/配下の処理を呼び出すかどうかを設定することができます。リスト2のように設定されています。

[リスト2]public/_routes.json
{
  "version": 1,
  "include": ["/*"],
  "exclude": ["/favicon.ico", "/assets/*"]
}

 静的配信したい(そして_headersでキャッシュしている)ファイルへのアクセスではPage Functionsが呼び出されないようにexcludeを設定しつつ、それ以外のすべてのリクエストに対してPage Functionsを呼び出すようにincludeを設定しています。

load-context.ts

 load-context.tsは、Remixのloaderなどでアクセスできるcontextオブジェクトを定義・拡張するためのファイルです。今回のケースでは、context.cloudflareを追加しています。Cloudflareの各種APIを利用するため、TypeScriptの型定義の拡張を行なうことが目的です。

 contextをさらに細かく拡張したい場合は、公式ドキュメントを参照して、getLoadContext関数を定義するとよいでしょう。

package.json

 package.jsonでは@remix-run/node@remix-run/cloudflare@remix-run/cloudflare-pagesに置き換えられたのが大きな変更です。また、Cloudflare Workersをはじめとした開発者向けサービスの管理用CLIツールである、Wrangler(ラングラー)も新たにインストールされ、NPM ScriptsにもWranglerを使ったデプロイコマンドが追加されています。

vite.config.ts

 vite.config.tsには、小さいようで大きな変更として、@remix-run/devに含まれるcloudflareDevProxyVitePluginというプラグインが設定されています。これは、開発時にローカル環境を立ち上げるタイミングで、Wranglerと連携するためのプラグインです。

 本来、Cloudflare Workersの実行環境はNode.jsとは異なるため、ローカル環境では動作確認が困難ですが、WranglerにはMiniflare(ミニフレア)という、ローカル環境でCloudflare Workersをエミュレートするためのツールが内蔵されています。

 cloudflareDevProxyVitePluginを通じて、Viteの開発サーバーとMiniflareを連携させることで、ローカル環境でCloudflare PagesやPage Functionsの動作をエミュレートできるようになっているのです。

wrangler.toml

 説明の都合で順番が前後しますが、wrangler.tomlについて先に解説させてください。wrangler.tomlは、Wranglerコマンドの設定ファイルでもあり、Cloudflare WorkersやCloudflare Pagesによってのプロジェクト管理ファイルでもあります。リスト3のように設定されています。

[リスト3]wrangler.toml
#:schema node_modules/wrangler/config-schema.json
name = "remix-on-cloudflare-sample"
compatibility_date = "2024-06-14"
pages_build_output_dir = "./build/client"

 nameはプロジェクト名を指しており、このままデプロイすれば、Cloudflare Pagesのプロジェクトがこの名前で作成され、pages.devのサブドメインとして公開されることになります。「remix-on-cloudflare-sample」という名前は本記事で筆者がとってしまったので、読者の皆さんが試すときは別の名前に書き換えてもよいかもしれません。

 compatibility_dateは、Wranglerのバージョンとの互換性を指定するためのフィールドです。pages_build_output_dirは、ビルドしたファイルが格納されるディレクトリを指定しています。Cloudflare Pagesは、このディレクトリをルートディレクトリとして静的ホスティングするため、Remixのビルド先のディレクトリを指定しています。

 また、wrangler.tomlには、Cloudflare Workers向けの各種サービスを紐づけるための設定を記述する役割もあります。たとえば、SQLiteデータベースのサービスであるCloudflare D1を利用する場合は、リスト4のように設定します。

[リスト4]wrangler.toml(Cloudflare D1の設定)
# (省略)
[[d1_databases]]
binding = "MY_DB"
database_name = "my-database"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

 [[d1_databases]]は、Cloudflare D1のデータベースを指定するためのセクションです。bindingは、WorkersやPage Functionsからアクセスするための変数名を指定します。database_nameは、事前にセットアップ済みのCloudflare D1のデータベース名を指定します。database_idは、database_nameに対応する、Cloudflare D1のデータベースIDを指定します。このように、wrangler.tomlには、Cloudflareの各種APIを利用するための設定を記述することができます。

 同様に、ストレージサービスのR2や、Workers AIといった、Cloudflareの各種APIを利用する際には、wrangler.tomlに設定を追加していくことになります。

worker-configuration.d.ts

 最後に、worker-configuration.d.tsは、Wranglerによって生成される、Cloudflare Workersの各種APIを利用するためのTypeScript型定義ファイルです。wrangler typesコマンドで生成されます。

 リスト4の例で解説してみましょう。wrangler.tomlbinding = "MY_DB"を書いただけでは、TypeScriptからはどこにAPIがあるのかわかりませんね。そこで、wrangler typesコマンドを実行すると、Cloudflare D1のAPIにアクセスするための型定義ファイルが生成されます。実際にリスト4のように設定して、wrangler typesコマンドを実行したときの結果を見てみましょう(リスト5)。

[リスト5]wrangler typesを実行する
$ npm run typegen

> typegen
> wrangler types

 ⛅️ wrangler 3.57.1 (update available 3.60.3)
-------------------------------------------------------
interface Env {
        MY_DB: D1Database;
}

 NPM Scriptsにtypegenが追加されているので、こちらを実行しました。worker-configuration.d.tsを見ると、MY_DBという変数が追加されており、D1Database型であることがわかります(リスト6)。

[リスト6]worker-configuration.d.ts
// Generated by Wrangler on Sat Jun 15 2024 18:39:59 GMT+0900 (日本標準時)
// by runningwrangler types

interface Env {
	MY_DB: D1Database;
}

 ここで定義されたEnvインターフェースは、load-context.tsで参照され、contextの型を拡張するために使われます。この設定を終えると、loaderでリスト7のようなコードが書けるようになります。

[リスト7]loaderでCloudflare D1のAPIを利用する
import { LoaderFunctionArgs } from "@remix-run/cloudflare";

export function loader({ context }: LoaderFunctionArgs) {
  const db = context.cloudflare.env.MY_DB;
  // (省略)
}

 これによって、TypeScriptからCloudflareの各種APIを利用する際にも、型安全なコードを書くことができるようになります。

 このように、Create Cloudflare CLIを使うことで、Cloudflareの力を最大限活用するための設定が行われたRemixテンプレートを利用できます。npm create remix@latestで作成したプロジェクトを自分で設定していくのもアリですが、テンプレートの力を借りることで、Cloudflare社のおすすめ設定を取り込めるので、初めての方にはおすすめです。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
Cloudflare Pagesのサイトにカスタムドメインを設定する

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Remixを通じてWebを学ぶ連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/20047 2024/09/05 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング