Cloudflare Pages + React + Hono: 手軽に始めるサーバーレスフルスタック開発

Cloudflare Pages、React、Hono を組み合わせたサーバーレスフルスタック開発の手法。単一リポジトリで管理でき、git push一発でデプロイ。Cloudflareのエッジネットワークを活用した高速応答と、Vite、TypeScriptによる快適な開発体験。

広告ここから
広告ここまで

目次

    今回は、最近のプロジェクトで活用している「Cloudflare Pages + React + Hono」という組み合わせでのWeb開発手法について紹介します。この構成を使えば、フロントエンドとバックエンドを一体的に開発でき、デプロイも簡単なサーバーレスアプリケーションを構築できます。

    はじめに:この構成を選んだ理由

    多くの開発者が直面する課題の一つに「フロントエンドとバックエンドの統合」があります。特に小〜中規模のプロジェクトでは、デプロイの手間やインフラ管理のコストを最小限に抑えたいものです。そこで注目したいのが、Cloudflare Pagesを使ったサーバーレスアプリケーション開発です。

    今回紹介する構成のメリットは:

    • 単一リポジトリでの管理: フロントエンド(React)とバックエンド(Hono)を一つのリポジトリで管理
    • シンプルなデプロイフロー: git push一発でフロントエンドとAPIの両方がデプロイ
    • スケーラブルな構成: Cloudflareのエッジネットワークを活用した高速な応答
    • 開発体験の向上: ViteとTypeScriptによる快適な開発環境

    それでは、実際の手順を見ていきましょう。

    前提知識

    今回必要になる前提知識・環境は以下の通りです:

    • Node.js と npm がインストール済み
    • Git の基本的な操作知識
    • Cloudflareアカウント(無料プランでOK)
    • 基本的なReactの知識

    1. Cloudflare Pagesプロジェクトの作成

    まずは、Cloudflareの提供するコマンドを使って新しいプロジェクトを作成します。

    npm create cloudflare@latest
    

    対話式のプロンプトに従って、以下のように設定していきます:

    ├ In which directory do you want to create your application?
    │ dir ./school-app
    

    アプリケーションの種類を選択します:

    ╰ What type of application do you want to create?
      ○ "Hello World" Worker
      ● Website or web app
      ○ Example router & proxy Worker
      ○ Scheduled Worker (Cron Trigger)
      ○ Queue consumer & producer Worker
      ○ ChatGPT plugin
      ○ OpenAPI 3.1
    

    フレームワークとしてReactを選択します:

    ├ What type of application do you want to create?
    │ type Website or web app
    │
    ╰ Which development framework do you want to use?
      ● Angular
      ○ Astro
      ○ Docusaurus
      ○ Gatsby
      ○ Hono
      ○ Next
      ○ Nuxt
      ○ Qwik
      ○ React
      ○ Remix
      ○ Solid
      ○ Svelte
      ○ Vue
    

    ├ Which development framework do you want to use?
    │ framework React
    │
    ╰ Continue with React via `npx [email protected] school-app`
    
    Need to install the following packages:
      [email protected]
    Ok to proceed? (y)
    

    ここでyesを選択すると、内部的にcreate-react-app(CRA)を使ってプロジェクトが作成されます。

    ├ Adding command scripts for development and deployment
    │ added commands to `package.json`
    │ 
    ├ Committing new files 
    │ git commit
    │ 
    ╰ Application configured 
    
    ╭ Deploy with Cloudflare Step 3 of 3
    │ 
    ╰ Do you want to deploy your application?
      Yes / No
    

    デプロイを選択すると、すぐに初期バージョンがCloudflare Pagesにデプロイされます:

    │ 
    ├  SUCCESS  View your deployed application at https://demo-app-15i.pages.dev
    │ 
    │ Navigate to the new directory cd school-app
    │ Run the development server npm run pages:dev
    │ Deploy your application npm run pages:deploy
    │ Read the documentation https://developers.cloudflare.com/pages
    │ Stuck? Join us at https://discord.gg/cloudflaredev
    

    これで基本的なReactアプリケーションのセットアップが完了しました。しかし、CRAは少し重く、ビルド時間も長いため、次のステップでViteに移行します。

    2. Viteへの移行

    Viteはモダンなビルドツールで、開発時の高速なHMR(Hot Module Replacement)やビルドの高速化が特徴です。CRAから移行するために、以下の手順を実行します。

    プロジェクトのルートディレクトリにvite.config.jsファイルを作成し、以下の内容を記述します:

    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    
    export default defineConfig({
      server: {
        open: true,
      },
      build: {
        minify: true,
        outDir: './pages',
      },
      plugins: [react()],
    });
    

    このファイルではViteの設定を定義しています:

    • server.open: 開発サーバー起動時に自動的にブラウザを開く
    • build.outDir: ビルド結果を./pagesディレクトリに出力
    • plugins: Reactを使用するためのプラグインを設定

    次に、必要な依存関係をインストールします:

    npm install -D vite @vitejs/plugin-react
    

    そして、package.jsonのスクリプトセクションを更新して、Viteを使用するようにします:

    "scripts": {
      "start": "vite",
      "build": "vite build",
      "pages:dev": "vite",
      "pages:build": "vite build",
      "pages:deploy": "npm run pages:build && wrangler pages publish pages"
    }
    

    これでViteへの移行が完了しました。次に、TypeScriptを導入していきます。

    3. TypeScriptの導入

    TypeScriptを導入することで、型安全なコードが書けるようになり、開発時のミスを減らすことができます。

    まず、必要なパッケージをインストールします:

    npm i -D typescript @types/node @types/react @types/react-dom
    

    次に、TypeScriptの設定ファイルを作成します:

    npx tsc --init
    

    これにより、tsconfig.jsonが生成されます:

    % npx tsc --init
    
    Created a new tsconfig.json with:        
                                          TS 
      target: es2016
      module: commonjs
      strict: true
      esModuleInterop: true
      skipLibCheck: true
      forceConsistentCasingInFileNames: true
    

    次に、JavaScriptファイルをTypeScriptファイルに変換します:

    • src/App.js → src/App.tsx
    • src/index.js → src/index.tsx
    • src/reportWebVitals.js → src/reportWebVitals.ts

    そして、tsconfig.jsonを以下のように編集して、React JSXの設定を追加します:

    {
      "compilerOptions": {
        "target": "es2016",
        "jsx": "react-jsx",
        "module": "commonjs", 
        "esModuleInterop": true, 
        "forceConsistentCasingInFileNames": true,
        "strict": true, 
        "skipLibCheck": true
      }
    }
    

    src/index.tsxファイルを編集して、TypeScript対応にします:

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    let rootElement = document.getElementById('root')
    if (!rootElement) {
      rootElement = document.createElement('div')
      rootElement.setAttribute('id', 'root');
      document.body.appendChild(rootElement)
    }
    const root = ReactDOM.createRoot(rootElement);
    
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    
    reportWebVitals();
    

    src/reportWebVitals.tsも修正します:

    const reportWebVitals = (onPerfEntry?: any) => {
      if (onPerfEntry && onPerfEntry instanceof Function) {
        import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
          getCLS(onPerfEntry);
          getFID(onPerfEntry);
          getFCP(onPerfEntry);
          getLCP(onPerfEntry);
          getTTFB(onPerfEntry);
        });
      }
    };
    
    export default reportWebVitals;
    

    これでTypeScriptの導入が完了しました。次は、バックエンドAPIとして機能するHonoを導入します。

    4. Honoを使ったAPIの実装

    HonoはTypeScriptファーストな軽量Webフレームワークであり、Cloudflare Workersとの親和性が高いです。今回はCloudflare Pages Functionsとして実装します。

    まず、必要なパッケージをインストールします:

    npm i hono @hono/zod-validator zod
    

    次に、Pages Functions用のディレクトリを作成し、APIのルートファイルを作成します:

    mkdir -p functions/api
    touch "functions/api/[[route]].ts"
    

    [[route]].tsファイルに以下の内容を記述します:

    import { zValidator } from '@hono/zod-validator'
    import { Hono } from 'hono'
    import { handle } from 'hono/cloudflare-pages'
    import { z } from 'zod'
    
    const app = new Hono().basePath('/api')
    
    const route = app.get(
      '/hello',
      zValidator(
        'query',
        z.object({
          name: z.string()
        })
      ),
      (c) => {
        const { name } = c.req.valid('query')
        return c.json({
          message: `Hello ${name}!`
        })
      }
    )
    
    export type AppType = typeof route
    
    export const onRequest = handle(app)
    

    このコードでは:

    1. /apiをベースパスとするHonoアプリケーションを作成
    2. /helloエンドポイントを定義し、クエリパラメータとしてnameを受け取る
    3. zodを使って入力パラメータのバリデーションを実施
    4. 受け取った名前を使って挨拶メッセージを返すJSON APIを実装

    これで、/api/hello?name=JohnというURLにアクセスすると、{"message":"Hello John!"}というレスポンスが返るようになりました。

    5. フロントエンドからAPIを呼び出す

    最後に、作成したAPIをReactフロントエンドから呼び出す部分を実装します。src/App.tsxを以下のように修正します:

    import React, { useState, useEffect } from 'react';
    import './App.css';
    
    interface GreetingResponse {
      message: string;
    }
    
    function App() {
      const [greeting, setGreeting] = useState<string>('');
      const [name, setName] = useState<string>('');
      const [isLoading, setIsLoading] = useState<boolean>(false);
    
      const fetchGreeting = async () => {
        if (!name.trim()) return;
        
        setIsLoading(true);
        try {
          const response = await fetch(`/api/hello?name=${encodeURIComponent(name)}`);
          if (!response.ok) {
            throw new Error('APIリクエストに失敗しました');
          }
          const data: GreetingResponse = await response.json();
          setGreeting(data.message);
        } catch (error) {
          console.error('エラーが発生しました:', error);
          setGreeting('エラーが発生しました。もう一度お試しください。');
        } finally {
          setIsLoading(false);
        }
      };
    
      return (
        <div className="App">
          <header className="App-header">
            <h1>Cloudflare Pages + React + Hono</h1>
            <div className="greeting-form">
              <input
                type="text"
                value={name}
                onChange={(e) => setName(e.target.value)}
                placeholder="あなたの名前を入力"
                className="name-input"
              />
              <button 
                onClick={fetchGreeting} 
                disabled={isLoading || !name.trim()}
                className="greet-button"
              >
                {isLoading ? '読み込み中...' : '挨拶を取得'}
              </button>
            </div>
            {greeting && <p className="greeting-message">{greeting}</p>}
          </header>
        </div>
      );
    }
    
    export default App;
    

    これで、画面上の入力フィールドに名前を入力してボタンをクリックすると、APIリクエストが送信され、結果が表示されるようになりました。

    6. ローカル開発環境でのテスト

    ローカル開発環境でアプリケーションをテストするには、以下のコマンドを実行します:

    npm run pages:dev
    

    このコマンドは、Viteの開発サーバーとCloudflare Pagesのエミュレータを同時に起動し、フロントエンドとAPIの両方をローカルで実行します。

    7. デプロイ

    アプリケーションをCloudflare Pagesにデプロイするには、以下のコマンドを実行します:

    npm run pages:deploy
    

    このコマンドは、ビルドしたアプリケーションをCloudflare Pagesにデプロイします。GitHubなどのリポジトリと連携している場合は、プッシュするだけで自動的にデプロイされます。

    まとめと発展

    今回は、Cloudflare Pages、React、Honoを組み合わせたフルスタック開発の基本的な流れを紹介しました。この構成は、以下のような特徴があります:

    • サーバーレスアーキテクチャによる運用コストの最小化
    • TypeScriptによる型安全な開発
    • フロントエンドとバックエンドの統一的な管理
    • Cloudflareのグローバルネットワークによる高速な応答

    このベースを元に、さらに以下のような拡張が可能です:

    1. 認証機能の追加: Cloudflare Access / Cloudflare D1 / Supabaseなどを使った認証システムの実装
    2. データベース連携: Cloudflare D1(SQLiteベースのエッジデータベース)との連携
    3. キャッシング戦略: Cloudflare Cache APIを使った効率的なキャッシング実装
    4. CI/CD: GitHub ActionsなどによるCI/CDパイプラインの構築

    コミュニティリソースとして、Honoの公式サンプルも参考になります:
    https://github.com/honojs/examples/tree/main/pages-stack/

    サーバーレスでのフルスタック開発は、特に小〜中規模のプロジェクトや、MVPの迅速な立ち上げに非常に有効です。ぜひ皆さんも試してみてください!

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark