Remix Clerk で`clerkState`エラーが出た時の解決方法

Remix と Clerk 連携時に発生する「clerkState」エラーの原因と解決方法を解説。Layout コンポーネントから ClerkProvider を削除し、ClerkApp のみで認証状態を管理する正しい実装パターンを紹介します。

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

目次

    Remix と Clerk を組み合わせた Web アプリケーションを開発中に、予期しない認証エラーに遭遇しました。

    Unexpected Server Error
    
    Error: 🔒 Clerk: Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".
    
    Use 'rootAuthLoader' as your root loader. Then, simply wrap the App component with ClerkApp and make it the default export.
    

    この記事では、このエラーの原因と解決方法について解説していきます。

    環境構成

    今回のプロジェクトで使用している環境は以下の通りです。

    • Remix: v2.15.3
    • Clerk: @clerk/remix v4.5.5

    発生した問題について

    開発サーバーを起動してアプリにアクセスすると、Error: 🔒 Clerk: Looks like you didn't pass 'clerkState' to "<ClerkProvider clerkState={...}>".というエラーが表示され、アプリケーションが正常に動作しませんでした。エラーメッセージから、clerkState の受け渡しに問題があることは分かりましたが、具体的な解決方法がすぐには見つかりませんでした。

    原因の調査

    初期の実装(問題のあるコード)

    最初の実装では、以下のような構造で Clerk を設定していました。

    // app/root.tsx(問題のあるバージョン)
    import { ClerkApp, ClerkProvider } from "@clerk/remix";
    import { rootAuthLoader } from "@clerk/remix/ssr.server";
    import { useLoaderData } from "@remix-run/react";
    
    export const loader = (args: LoaderFunctionArgs) => {
      return rootAuthLoader(args, {
        publishableKey: (args.context.cloudflare.env as any).CLERK_PUBLISHABLE_KEY,
        secretKey: (args.context.cloudflare.env as any).CLERK_SECRET_KEY,
      });
    };
    
    export function Layout({ children }: { children: React.ReactNode }) {
      const loaderData = useLoaderData<typeof loader>();
      
      return (
        <ClerkProvider clerkState={loaderData as any} publishableKey={(loaderData as any)?.publishableKey || ""}>
          <html lang="ja">
            {/* ... */}
            <body>
              {children}
            </body>
          </html>
        </ClerkProvider>
      );
    }
    
    function App() {
      return <Outlet />;
    }
    
    export default ClerkApp(App, {
      publishableKey: process.env.CLERK_PUBLISHABLE_KEY || ""
    });
    

    問題の特定

    この実装には複数の問題がありました。

    まず、Layout コンポーネント内で ClerkProvider を使用していることです。Remix v2 の Layout コンポーネント内で useLoaderData を使用して Clerk の状態を取得しようとしていましたが、これは推奨されるパターンではありませんでした。

    次に、ClerkApp で App をラップしているにも関わらず、Layout 内でも ClerkProvider を使用していたことです。これにより、認証の状態管理が二重になってしまい、エラーの原因となっていました。

    さらに、Clerk の公式ドキュメントで推奨されている実装パターンと大きく異なっていたことも問題でした。独自の実装を試みた結果、フレームワークとライブラリの想定する動作から外れてしまっていたのです。

    解決方法

    正しい実装

    Clerk の公式ドキュメントに従って、以下のように修正しました。

    // app/root.tsx(修正後)
    import type { LinksFunction, LoaderFunctionArgs } from "@remix-run/cloudflare";
    import {
      Links,
      Meta,
      Outlet,
      Scripts,
      ScrollRestoration,
    } from "@remix-run/react";
    import { ClerkApp } from "@clerk/remix";
    import { rootAuthLoader } from "@clerk/remix/ssr.server";
    
    import "./tailwind.css";
    
    export const loader = (args: LoaderFunctionArgs) => {
      return rootAuthLoader(args, {
        publishableKey: (args.context.cloudflare.env as any).CLERK_PUBLISHABLE_KEY,
        secretKey: (args.context.cloudflare.env as any).CLERK_SECRET_KEY,
      });
    };
    
    export function Layout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="ja">
          <head>
            <meta charSet="utf-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            <Meta />
            <Links />
          </head>  
          <body className="min-h-screen bg-gray-50 font-sans text-gray-900 antialiased">
            {children}
            <ScrollRestoration />
            <Scripts />
          </body>
        </html>
      );
    }
    
    function App() {
      return <Outlet />;
    }
    
    export default ClerkApp(App);
    

    修正のポイント

    修正で最も重要だったのは、Layout コンポーネントから ClerkProvider を完全に削除したことです。useLoaderData の使用も停止し、レイアウトは純粋に UI の構造だけを担当するようにしました。

    また、ClerkApp(App) のみで App をラップするようにし、追加の設定パラメータを削除しました。これにより、Clerk の内部実装に任せることができるようになりました。

    最終的に、rootAuthLoaderloader として使用し、Clerk の認証状態管理を ClerkApp に完全に委譲する標準的な Remix 構造を採用しました。この方法により、フレームワークとライブラリが意図した通りに動作するようになったのです。

    動作確認

    修正後、開発サーバーを再起動してアプリにアクセスすると、正常な HTML レスポンスが返されるようになりました。

    curl -s http://localhost:5173/ | head -20
    

    Clerk のエラーは完全に解消され、認証機能も期待通りに動作するようになりました。

    まとめ

    Remix + Cloudflare Pages + Clerk の組み合わせでは、シンプルな実装を心がけることが大切です。ClerkApp で App コンポーネントをラップするだけで十分であり、Layout コンポーネント内で ClerkProvider を手動設定する必要はありません。rootAuthLoader を適切に設定すれば、Clerk が自動的に認証状態を管理してくれます。

    この経験を通じて、フレームワークとライブラリの組み合わせでは、それぞれの責任範囲を理解し、公式ドキュメントに従うことの重要性を再認識しました。特に認証のような重要な機能では、独自の実装よりも実績のあるパターンを採用することで、開発効率と保守性の両方を向上させることができるでしょう。

    参考資料


    この記事が同様の問題に遭遇した開発者の助けになれば幸いです。

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