こんにちは!カミナシでソフトウェアエンジニアをやっているくらさわです! 今回は、以前から触ってみたかった Remix + Cloudflare Pages + Cloudflare Workers KV で簡単に素振りしてみた話を書きたいと思います!
なぜ触ってみたかったのかというと、 エッジコンピューティングはよく聞くけど実際にちゃんと使ってみたことがなく、エッジで SSRできる Remix にも元々興味があったためです。
初めに触った技術に関して軽く説明していますが、本当に軽くなので気になる方は公式サイト等をご覧ください。
- Remix について
- Cloudflare Pages について
- Cloudflare Workers KV について
- 開発について
- Remix プロジェクトの作成
- Cloudflare Pages にデプロイ
- Cloudflare Workers KV の作成
- バインディングの設定
- Pathとルーティング
- Remix の Loader について
- Cloudflare Pages Functions
- Remix の Lodaer でのバインディング
- Local 開発について
- まとめ
Remix について
Remix とは SSR が得意な React 向けの Web フレームワークです。 後述する Cloudflare Pages にデプロイして SSR することができます。
公式サイト: Remix - Build Better Websites
Cloudflare Pages について
Cloudflare Pagesとは Cloudflare が提供する Web サイトを作成、ホスティングするためのサービスです。 Cloudflare Workers の機能を利用して、サーバーレス関数を実行することもできます。
公式サイト: Cloudflare Pages
Cloudflare Workers KV について
Cloudflare Workers KV とは Cloudflareのアプリケーション向けサーバーレス Key-Value ストレージです。
公式サイト: サーバーレスストレージとアプリケーション | Cloudflare Workers KV | Cloudflare
開発について
それでは実際の開発について説明していこうと思います。
create-remix で作成したものに、Remix のチュートリアルの冒頭の部分を追加、編集したりして試してみました。
Blog Post を閲覧できるサイトです。
チュートリアル: Remix | Blog Tutorial (short)
ちなみにチュートリアルはシュッとできて、勉強になるのでおすすめです。
完成品: https://cloudflare-pages-remix-sample.pages.dev
github リポジトリ: GitHub - n-kurasawa/cloudflare-pages-remix-sample
Remix プロジェクトの作成
まずは、Remix のプロジェクトを作成します。
$ npx create-remix@latest ? Where would you like to create your app? cloudflare-pages-remix-sample ? What type of app do you want to create? Just the basics ? Where do you want to deploy? Choose Remix App Server if you're unsure; it's easy to change deployment targets. Cloudflare Pages ? TypeScript or JavaScript? TypeScript ? Do you want me to run `npm install`? Yes
create-remix を実行するとインタラクティブに質問してくれるので、それに答えていくことで簡単にプロジェクトの設定が完了します。
今回は特に、Cloudflare Pages にデプロイしたいため Cloudflare Pages を選択しています。
ここで作成したプロジェクトは GitHub に push しておきます。
Cloudflare Pages にデプロイ
Cloudflare にアカウントを作成して、ダッシュボードを開いて、Pages にプロジェクトを作成します。
GitHub ( GitLab も可能 ) に接続して、先ほど作成したプロジェクトのリポジトリを選択します。
最後にビルドとデプロイのセットアップをして、デプロイします。これ以降、リポジトリに push するたびにデプロイが走ります。
めっちゃ簡単です!
Cloudflare Workers KV の作成
ダッシュボードの KV から名前空間を作成します。
名前には大文字のスネークケースが使われてる例をよく見るので、それっぽく入れておきます。
バインディングの設定
作成した KV を Pages から使用するためにバインディングを設定します。 ここで設定した変数名をコード上では使用することになります。
Pathとルーティング
ここからはコードの説明に入っていきたいと思います。
Path は API を含めると全部で 4 つ作成しています。
Path | 対応するファイル | 内容 |
---|---|---|
/ | app/routes/index.tsx | トップページ |
/posts | app/routes/posts/index.tsx | Blog Post 一覧 |
/posts/$slug | app/routes/posts/$slug.tsx | Blog Post 詳細 |
/api/posts | functions/api/posts/index.ts | Blog Post 一覧取得API |
このように Remix では、Path に対応するファイルを作成する、ファイルベースのルーティングを行っています。
API に関してはRemix ではなく Cloudflare Pages Functions の機能でルーティングされますが、こちらもファイルベースとなっています。
Remix | Routing
Functions の Routing: Routing · Cloudflare Pages docs
Remix の Loader について
以下は Blog Post 一覧の app/routes/posts/index.tsx の一部です。
export const loader = async ({ request }: LoaderArgs) => { const url = new URL(request.url) const res = await fetch(`${url.origin}/api/posts`) const posts: Post[] = await res.json() return json({ posts }) } export default function Posts() { const { posts } = useLoaderData<typeof loader>() return ( <main> <h1>Posts</h1> <ul> {posts.map((post) => ( <li key={post.slug}> <Link to={post.slug}>{post.title}</Link> </li> ))} </ul> </main> ) }
コード: cloudflare-pages-remix-sample/index.tsx at main · n-kurasawa/cloudflare-pages-remix-sample · GitHub
上記のように loader という関数を作成し、外部からのデータ取得をそこで行います。
コンポーネント側では useLoaderData を使用して、取得したデータを使用することができます。
ちなみに loader 内で、ブラウザで動く fetch のノリで fetch('/api/posts')
のように path だけ渡しても動かないので URL の形式で渡す必要がありました。
Cloudflare Pages Functions
上記の loader では Cloudflare Pages Functions で作成した API を叩いています。Functions とは Cloudflare Workers のようにサーバレス関数を定義できるものです。
以下は Blog Post 一覧取得API の functions/api/posts/index.ts の一部です。
export const onRequest: PagesFunction<Env> = async (context) => { const { keys } = await context.env.POSTS_KV.list() let posts: Post[] = [] for (const key of keys) { const post: Post | null = await context.env.POSTS_KV.get(key.name, { type: 'json', }) if (post) { posts.push(post) } } return new Response(JSON.stringify(posts)) }
コード: cloudflare-pages-remix-sample/index.tsx at main · n-kurasawa/cloudflare-pages-remix-sample · GitHub
Functions の onRequest
はリクエストメソッドに関係なく、該当する path へのリクエスト全てに対して呼び出されます。
ここでは、 Cloudflare Workers KV に保存してある Post の一覧を取得しています。
Functions から KV には、引数の context.env から バインディングで設定した変数名でアクセスすることができます。
POSTS_KV
というのが私が設定した変数名です。
API Reference · Cloudflare Pages docs
Bindings · Cloudflare Pages docs
Remix の Lodaer でのバインディング
またRemix では、 functions ディレクトリ配下にファイルを作成しなくても Remix の loader 関数から KV にアクセスすることができます。 以下は Blog Post 詳細 の app/routes/posts/$slug.tsx の一部です。
export const loader = async ({ context, params }: LoaderArgs) => { invariant(params.slug, `params.slug is required`) const kv = context.POSTS_KV as KVNamespace const post: Post | null = await kv.get(params.slug, { type: 'json' }) invariant(post, `Post not found: ${params.slug}`) const html = marked(post.markdown) return json({ post, html }) }
コード: cloudflare-pages-remix-sample/$slug.tsx at main · n-kurasawa/cloudflare-pages-remix-sample · GitHub
loader で バインディングを利用する場合も、引数の context から取得します。ただし、Functions とは違って、env からではなく、context から直接利用する形になっています。
ちょっと型付けをどうするのがいいのかわからなかったので無理やり感がありますが。
Local 開発について
また KV を使用する場合でも、ローカルで開発できるようになっています。
以下のようにpackage.json に定義してある dev 用のコマンドに --kv {{ バインディング変数名 }}
をつけるといいです。
"dev:wrangler": "cross-env NODE_ENV=development wrangler pages dev ./public --kv POSTS_KV",
そうすると local storage を使って KV の動作をシミュレートしてくれます。
ちなみに、データはデフォルトでは永続化されないので永続化したい場合は、--persist
をつけます。
Local Development · Cloudflare Pages docs
まとめ
いかがでしたでしょうか? 簡単な説明しかしていませんが、なんとなく雰囲気が伝わっていれば幸いです。 個人的な感想としては、思ったより、Remix も Cloudflare Pages も使いやすくていいな〜と思いました!
今回触った技術は、業務では使っておらず週末の自由研究としてやっていましたが、まだまだ表面的な部分しか触れていないので実際に何か作ってみようと思います。
そんな自由研究が好きな方も、そうでもない方もカミナシでは絶賛採用中です! 少しでもご興味があればよろしくお願いします!