Remixでroot.tsxにClerkのUI Componentを使いたい
提供されたUI Componentをレイアウト要素で使用する際の覚書。Remixを使用する場合、app/route.tsxにコンポーネントを配置。ヘッダーナビゲーションコンポーネント内にログインやログアウトボタンを配置したいが、エラーが発生。ClerkProviderの外でUI Componentを使うことがエラーの原因。Layout要素をProviderの内側に配置すると問題解消。ただし、キャッシュとSSRに関して懸念があり、Cloudflareのレポートを参考に検証を行う予定。
目次
Clerkの提供するUI Componentをレイアウト要素で使用した時の覚書です。
やりたいこと
ヘッダーナビゲーションやフッターなどは、Remixだとapp/route.tsx
にコンポーネントを配置します。そして今回はヘッダーのナビゲーションコンポーネントの中に、ログインログアウト系の操作を行うボタンを配置しようとしました。
試したコード
まずはClerkのクイックスタートを見ながら実装してみます。ただしコンポーネントの実装先はapp/root.tsx
に変えてみました。
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className='h-full bg-gray-100'>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</script>
<Meta />
<Links />
</head>
<body>
<Header><SignInButton /></Header>
<div className='py-10'>
{children}
</div>
<Footer />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
function App() {
return <Outlet />;
}
export default ClerkApp(App);
このコードを実行すると、つぎのようなエラーが出てきます。
Uncaught Error: @clerk/clerk-react: SignedIn can only be used within the <ClerkProvider /> component. Learn more: https://clerk.com/docs/components/clerk-provider
app/root.tsx
はClerkProvider
の外側になるらしい?
エラーログを深堀したところ、LayoutコンポーネントがClerkProvider
の外にあることがわかりました。ClerkApp
の実装は次のようなものです。そのため、どうもLayout
がProviderの外に配置されている様子です。
export function ClerkApp(App: () => JSX.Element, opts: ClerkAppOptions = {}) {
return () => {
let clerkState;
const isSpaMode = inSpaMode();
// Don't use `useLoaderData` to fetch the clerk state if we're in SPA mode
if (!isSpaMode) {
const loaderData = useLoaderData<{ clerkState: any }>();
clerkState = loaderData.clerkState;
}
if (isSpaMode) {
assertPublishableKeyInSpaMode(opts.publishableKey);
}
return (
<ClerkProvider
/* @ts-ignore The type of opts cannot be inferred by TS automatically because of the complex
* discriminated unions required for the router props and multidomain feature */
{...(opts as RemixClerkProviderProps)}
clerkState={clerkState}
>
<App />
</ClerkProvider>
);
};
}
ClerkApp
の代わりにClerkProvider
を利用して対応
ClerkProvider
の外側でUI Componentを使うのがエラーの原因ということは、Layout要素がProviderの内側に入れば良いはずです。そこでこのような実装にしました。
export function Layout({ children }: { children: React.ReactNode }) {
const loaderData = useLoaderData<{ clerkState: any }>();
return (
<html lang="en" className='h-full bg-gray-100'>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script
async
src="https://js.stripe.com/v3/pricing-table.js">
</script>
<Meta />
<Links />
</head>
<body>
<ClerkProvider clerkState={loaderData.clerkState}>
<Header><SignInButton /></Header>
<div className='py-10'>
{children}
</div>
<Footer />
<ScrollRestoration />
<Scripts />
</ClerkProvider>
</body>
</html>
);
}
今の所このコードで動作してくれている様子です。
キャッシュについては要検証・気がかりな点
これで解決・・・に見えるのですが、気がかりなのはキャッシュとSSRまわりです。アプリケーション全体をProvider配下に置いているため、アプリケーションのキャッシュがあまりされない可能性があるかな・・・?と思っています。この辺りはCloudflareのレポートを見ながら様子見していこうと思います。