Next.js dev serverでCloudflare Service Bindingが503エラーを返す問題の解決方法

CloudflareでNext.jsアプリを開発していて、Service bindingを使った別のWorkerへの通信が、Next.js dev server(npm run dev)で503エラーを返す問題が発生する […]

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

目次

    CloudflareでNext.jsアプリを開発していて、Service bindingを使った別のWorkerへの通信が、Next.js dev server(npm run dev)で503エラーを返す問題が発生することがあります。この記事では、この問題の原因と、開発環境でも正常に動作させるためのフォールバック実装について説明します。

    発生する503エラー

    Next.js dev serverでService bindingを使ったAPIにアクセスすると、以下のようなエラーが発生します。

    HTTP 503 Service Unavailable
    Couldn't find a local dev session for the "default" entrypoint of service "your-worker-name" to proxy to
    

    このエラーは、Service bindingが別のWorkerのローカル開発セッションを見つけられないために発生します。具体的には、Service binding経由のfetchが実行されますが、接続先のWorkerが起動していない(またはNext.js dev serverから見えない)ため、503エラーが返されます。

    なぜこのエラーが発生するのか

    Service bindingは、Cloudflare Workers間の通信を高速化するための仕組みです。本番環境では、Cloudflare内部で自動的にWorker間の接続が確立されます。しかし、ローカル開発環境では状況が異なります。

    OpenNextを使ったNext.jsアプリでは、2つの開発サーバーが存在します。

    • Next.js dev servernpm run dev): Next.jsの標準開発サーバーで、高速なホットリロードを提供します。Node.js環境で動作します。
    • wrangler dev: Cloudflareのworkerd runtimeで動作する開発サーバーで、本番環境により近い環境を提供します。

    Next.js dev serverは、Cloudflare Workersの環境を完全にはエミュレートしません。そのため、Service bindingが別のWorkerのローカル開発セッションを見つけられず、503エラーを返します。一方、wrangler devを使用すると、Service bindingは正常に動作します。これは、wrangler devが各Workerのローカル開発セッションを管理し、Service binding経由で接続できるためです。

    この問題が発生するのは、あくまでNext.js dev serverを使用している場合のみです。wrangler devopennextjs-cloudflare previewを使用している場合は、Service bindingは正常に動作します。

    フォールバック実装で動くようにする

    この問題を解決するには、Service bindingが503エラーを返した場合に、通常のHTTP fetchにフォールバックする実装を追加します。このアプローチにより、Next.js dev serverでも開発を継続でき、かつ本番環境ではService bindingの高速な通信を維持できます。

    フォールバックの流れは以下の3ステップです。

    1. Service binding経由でfetchを試行する
    2. 503エラーが返され、エラーメッセージに「Couldn’t find a local dev session」が含まれる場合
    3. 通常のHTTP fetchにフォールバックする

    この実装により、開発環境では通常のHTTP経由でWorkerにアクセスし、本番環境ではService bindingの高速な通信を使用します。

    実装コード

    以下は、Next.js API RouteでService bindingを使用し、503エラー時にフォールバックする実装例です。

    import { NextRequest, NextResponse } from 'next/server';
    import { getCloudflareContext } from '@opennextjs/cloudflare';
    
    export async function GET(request: NextRequest) {
      const { env } = await getCloudflareContext();
      
      // Service bindingが利用可能かチェック
      const ogImageGenerator = env.OG_IMAGE_GENERATOR;
      
      // fetchするURL(localhostを指定)
      const ogImageUrl = new URL('http://localhost:8788/generate');
      
      // リクエストヘッダーを準備
      const headers = new Headers();
      headers.set('Authorization', `Bearer ${env.AUTH_TOKEN}`);
      
      let response: Response;
      
      if (ogImageGenerator) {
        // Service binding経由でfetchを試行
        response = await ogImageGenerator.fetch(ogImageUrl, { headers });
        
        // 503エラーでローカル開発セッションが見つからない場合
        if (!response.ok && response.status === 503) {
          const responseBodyText = await response.clone().text().catch(() => '');
          
          if (responseBodyText.includes("Couldn't find a local dev session")) {
            // 通常のHTTP fetchにフォールバック
            response = await fetch(ogImageUrl, { headers });
          }
        }
      } else {
        // Service bindingが存在しない場合は最初から通常のfetch
        response = await fetch(ogImageUrl, { headers });
      }
      
      return new NextResponse(response.body, {
        status: response.status,
        headers: response.headers,
      });
    }
    

    実装のポイント

    response.clone()の使用

    レスポンスボディを確認するためにresponse.clone()を使用します。Responseオブジェクトのボディは一度しか読み取れないため、cloneしてからテキストを取得します。これにより、元のレスポンスは保持されたまま、エラーメッセージの内容を確認できます。

    エラーハンドリング

    response.clone().text()が失敗する可能性を考慮し、.catch(() => '')で空文字列を返すようにします。これにより、予期しないエラーでアプリケーション全体が停止することを防ぎます。

    セキュリティ上の注意点

    フォールバック時のfetchは、必ずlocalhostのみを対象にします。外部のURLへフォールバックすると、認証トークンが意図しないサーバーに送信される危険があります。

    // 良い例:localhostを明示的に指定
    const ogImageUrl = new URL('http://localhost:8788/generate');
    
    // 悪い例:環境変数から任意のURLを取得
    // const ogImageUrl = new URL(env.OG_IMAGE_URL); // 本番URLが設定されている可能性
    

    localhostに限定する理由は、開発環境では開発者のマシン上でWorkerが動作しているためです。本番環境のURLが設定されていると、フォールバック時に本番環境のWorkerにリクエストが送信され、開発用の認証トークンが本番環境に漏洩する可能性があります。

    また、本番環境では以下の設定を追加し、localhostへのfetchを無効化することを推奨します。

    // wrangler.jsonc または wrangler.toml
    {
      "compatibility_flags": ["global_fetch_strictly_public"]
    }
    

    このcompatibility flagを有効にすると、Workerからprivate IPアドレス(localhostを含む)へのfetchが禁止されます。これにより、フォールバック処理が本番環境で誤って実行されることを防ぎます。

    動作確認

    実装後、以下の手順で動作を確認します。

    1. Next.js dev serverを起動(npm run dev
    2. Service bindingを使用するAPIエンドポイントにアクセス
    3. レスポンスが正常に返ることを確認

    この時点で、ブラウザの開発者ツールのNetworkタブを確認すると、最初にService binding経由のリクエストが503を返し、その後通常のHTTP fetchが成功していることがわかります。

    本番環境での動作確認は、wrangler devopennextjs-cloudflare previewを使用します。

    npm run preview
    

    この場合、Service bindingが正常に動作するため、フォールバック処理は実行されません。Service binding経由のリクエストが最初から成功し、高速な通信が行われます。

    本番環境への影響

    この実装は、本番環境には影響しません。本番環境では、Service bindingが正常に動作するため、最初のfetchが成功し、フォールバック処理は実行されません。

    本番環境でのService bindingの利点は以下の通りです。

    • レイテンシがゼロ: Worker間の通信がCloudflare内部で完結し、パブリックなネットワークを経由しません
    • 追加コストなし: Service binding経由の通信は、subrequestとしてカウントされますが、追加の課金は発生しません
    • セキュリティ: Worker間の通信が外部に公開されないため、認証情報の漏洩リスクが低減します

    フォールバック処理は、あくまで開発環境での利便性を向上させるためのものであり、本番環境のパフォーマンスやセキュリティには影響しません。

    まとめ

    Next.js dev serverでService bindingが503エラーを返す問題は、ローカル開発セッションが見つからないことが原因です。この問題は、503エラー時に通常のHTTP fetchにフォールバックする実装により解決できます。

    実装時の重要なポイントは以下の3点です。

    1. フォールバック処理は、503エラーかつ「Couldn’t find a local dev session」のメッセージが含まれる場合のみ実行する
    2. フォールバック時のfetchは必ずlocalhostを対象にし、外部URLへのアクセスを避ける
    3. 本番環境ではglobal_fetch_strictly_publicフラグを有効にし、localhostへのfetchを禁止する

    この実装により、開発環境でも本番環境でも、Service bindingを使った通信が正常に動作します。

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