SPA サブドメインルートでのリダイレクトなしレンダリング
問題
サブドメインベースのルーティング(例:username.example.com)を構築する際、React Router の
<Navigate to="/profile/username" replace /> を使用すると URL が変更され、クリーンなサブドメイン URL を持つという目的が損なわれます。
さらに、ターゲットコンポーネントが useParams() に依存してルートパラメータを取得する場合、/ にはそのパラメータが存在しないため、空白ページが表示される可能性があります。
コンテキスト / トリガー条件
- サブドメインプロファイルページが
/profile/npub1...にリダイレクトされ、/でコンテンツを表示しない - サブドメインルートでページが空白(コンテンツが読み込まれない)になる
- コンポーネントが
useParams()を使用しており、それらのパラメータがないルートでレンダリングされる - エッジワーカーが
window.__SUBDOMAIN_USER__または同様のグローバルにユーザーデータを注入する username.domain.com/を個人のウェブサイトのようにレンダリングしたい
解決策
1. ルーター: ナビゲーションではなくコンポーネントを直接レンダリング
変更前 (リダイレクト + URL 変更を引き起こす):
<Route path="/" element={
subdomainUser
? <Navigate to={`/profile/${subdomainUser.npub}`} replace />
: <Index />
} />
変更後 (URL 変更なしで / にレンダリング):
<Route path="/" element={
subdomainUser
? <ProfilePage />
: <Index />
} />
2. コンポーネント: フォールバックデータソースを追加
コンポーネントは、ルートパラメータが欠落している場合に、注入されたサブドメインデータをチェックする必要があります。
変更前 (ルートパラメータのみをチェック):
export function ProfilePage() {
const { npub } = useParams<{ npub?: string }>();
const identifier = npub;
if (!identifier) {
return <Error message="ユーザー識別子が指定されていません" />;
}
// ... コンポーネントの残りの部分
}
変更後 (注入データにフォールバック):
import { getSubdomainUser } from '@/hooks/useSubdomainUser';
export function ProfilePage() {
const { npub } = useParams<{ npub?: string }>();
const subdomainUser = getSubdomainUser();
// ルートパラメータが優先され、サブドメインデータがフォールバック
const identifier = npub || subdomainUser?.npub;
if (!identifier) {
return <Error message="ユーザー識別子が指定されていません" />;
}
// ... コンポーネントの残りの部分
}
3. エッジワーカー: HTML にユーザーデータを注入
エッジワーカー(Fastly Compute、Cloudflare Workers など)は、ユーザーデータを注入する必要があります:
// サブドメインを処理するエッジワーカー内
const userData = await fetchUserData(username);
const html = await fetchSpaHtml();
const injectedHtml = html.replace(
'</head>',
`<script>window.__DIVINE_USER__ = ${JSON.stringify(userData)};</script></head>`
);
return new Response(injectedHtml, { headers: { 'content-type': 'text/html' } });
4. クライアントサイドのデータアクセス
// useSubdomainUser.ts
interface SubdomainUser {
subdomain: string;
pubkey: string;
npub: string;
username?: string;
displayName?: string;
// ... その他のフィールド
}
export function getSubdomainUser(): SubdomainUser | null {
if (typeof window === 'undefined') return null;
return (window as any).__DIVINE_USER__ || null;
}
確認手順
username.domain.com/にアクセス - URL は/のまま、リダイレクトしないこと- プロファイルコンテンツが正しく読み込まれること
- ブラウザコンソールで識別子欠落に関するエラーがないことを確認
- devtools で
window.__DIVINE_USER__に期待されるデータが含まれていることを確認
例
divine-web の場合:
thecomedianriley.divine.video/は/で ProfilePage をレンダリング- ProfilePage は最初に
useParams()をチェック(/では空) - 注入されたデータから
getSubdomainUser()?.npubにフォールバック - プロファイルが URL 変更なしで読み込まれる
注意点
- このパターンにはエッジワーカーとクライアントコード間の連携が必要です
- エッジワーカーは SPA のハイドレーション前にデータを注入する必要があります
- 通常の
/profile/:npubルートが機能するように、常に最初にルートパラメータをチェックしてください - SEO を考慮: エッジワーカーは適切なメタタグも設定する必要があります
- サブドメイン上のリンクは
SmartLinkコンポーネントを使用して、所有者のコンテンツにはローカルに留まり、 他のユーザーのコンテンツにはエイペックスドメインにリンクすることができます
参考資料
- React Router v6 Routes: https://reactrouter.com/en/main/route/route
- Fastly Compute@Edge: https://developer.fastly.com/learning/compute/