Cloudflare PagesでNext.jsとHonoを動かす
はじめに
おはようございます、加藤です。先日 Serverless Days 2023 Tokyo に参加してきました。2日目はCloudflare Workers & Hono Workshopに参加し、Hono便利だなーってなったので知識を定着させる目的で環境を構築してみます。
前提
node --version
v20.6.1
npm --version
9.8.1
Wrangler CLIでログインが済んでいない場合は下記のコマンドでログインしてください。
npx wrangler login
Next.jsのセットアップ
プロジェクトを作成します。
npm create cloudflare@latest tmp -- --framework=next
> Need to install the following packages:
> create-next-app@13.4.19
> Ok to proceed? (y) y
> ✔ Would you like to use TypeScript? … Yes
> ✔ Would you like to use ESLint? … Yes
> ✔ Would you like to use Tailwind CSS? … No
> ✔ Would you like to use `src/` directory? … No
> ✔ Would you like to use App Router? (recommended) … Yes
> ✔ Would you like to customize the default import alias? … No
> Do you want to use the next-on-pages eslint-plugin? … Yes
> Do you want to deploy your application? … No
ディレクトリtmp
内のファイルを全てプロジェクトルートへ移動します。ドットファイルの移動を忘れないように注意してください。
mv tmp/* ./
mv tmp/.eslintrc.json tmp/.git tmp/.gitignore ./
rmdir tmp
NPMプロジェクト名がtmp
になっているので変更したい場合はpackage.json
のname
を変更してください。name
はロックファイルにも含まれるので変更後はnpm install
で再生成する必要があります。
初回デプロイを行います。今回は新規プロジェクトを作成しました。${{YOUR_PROJECT_NAME}}
は自由な名前に置換してください。
npm run pages:deploy
> No project selected. Would you like to create one or use an existing project?
> ❯ Create a new project
> Use an existing project
> ✔ Enter the name of your new project: … ${{YOUR_PROJECT_NAME}}
> ✔ Enter the production branch name: … main
> ✨ Successfully created the '${{YOUR_PROJECT_NAME}}' project.
WebブラウザでCloudflareのダッシュボードを開き、作成したPagesのプロジェクトに対して2つの設定を行います。最初wrangler.toml
を書けば良いのかなと思いましたが、Cloudflare Pagesはwrangler.toml
には対応していませんでした。
-
環境変数に
NODE_VERSION = 20
を設定する -
Runtime Featuresのcompatibility flag に
nodejs_compat
を設定する
どちらもProductionとPreviewそれぞれ設定しておきます。
参考元: https://zenn.dev/microcms/articles/1b4331eca6e512#githubリポジトリとの連携
一旦api
ディレクトリを削除します。
rm -rf app/api
再度デプロイします。
npm run pages:deploy
CLIに表示されたURLへアクセスしNext.jsのWelcomeページが表示されることを確認します。
Honoのセットアップ
Honoとクライアントとして使用するSWRをインストールします。
npm install hono swr
/api
配下への全てのリクエストをHonoで処理するエンドポイントを作成します。
mkdir -p app/api/[[...slug]]
touch app/api/[[...slug]]/route.ts
app/api/[[...slug]]/route.ts
import { Hono } from "hono";
export const runtime = "edge";
const app = new Hono().basePath("/api");
app.get("/hello", (c) => c.text("Hello World!"));
app.post("/hello", (c) => c.json({ message: "Hello World!" }));
export const GET = app.fetch;
export const POST = app.fetch;
app/page.tsx
を編集して作成したAPIから取得した値を表示します。
app/page.tsx
"use client";
import useSWR from "swr";
export default function Home() {
const { data: text } = useSWR("/api/hello", (url) =>
fetch(url).then((res) => res.text())
);
const { data: json } = useSWR("/api/hello", (url) =>
fetch(url, { method: "POST" }).then((res) => res.json())
);
return (
<main>
<p>text: {text}</p>
<p>json: {JSON.stringify(json)}</p>
</main>
);
}
デプロイしてともにHello World!が表示されれば完成です。
あとがき
少量の設定で簡単にNext.jsとAPIを作成することができました。
今回はHonoをCloudflare WorkersではなくCloudflare PagesのFunctionsで動かしました、これは汎用的なAPIではなくPagesにデプロイするフロントエンド専用のAPI(BFF的な感じ)の用途をイメージして構築しました。
具体的には
- Cookieセッションで認証した後セッションからユーザーIDや認可の為のJWTを取り出してからバックエンドAPIへリクエストを行う
- バックエンドAPIからのレスポンスをユーザーが理解しやすいものに置き換える
- バックエンドAPIからのレスポンスをセキュリティ要件のために隠蔽する
といった用途を想定しています。
HonoにはRPC機能があり、これを使うことでブラウザからAPIへのリクエストを型の支援を受けながら記述することもできます。まさにフロントエンドからはRPCとして呼び出す感覚なのでマッチしそうです。
Pages Functionsで動かすと同一ドメインなのでCORSやCookieのDomain属性の考慮が不要という利点があります。もし、複数のクライアントが存在するAPIとして構築するなら、別途Workersで構築するのが好ましいでしょう。
誤りやより良い方法などがあればコメント頂けるとありがたいです。
Discussion