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이/에 유지되어야 하며 리디렉션되지 않아야 합니다.- 프로필 콘텐츠가 올바르게 로드되어야 합니다.
- 브라우저 콘솔에 식별자 누락 관련 오류가 없어야 합니다.
- 개발자 도구에서
window.__DIVINE_USER__에 예상 데이터가 포함되어 있는지 확인합니다.
예시
divine-web의 경우:
thecomedianriley.divine.video/는/에서 ProfilePage를 렌더링합니다.- ProfilePage는 먼저
useParams()를 확인합니다(/에서는 비어 있음). - 주입된 데이터의
getSubdomainUser()?.npub로 폴백합니다. - URL 변경 없이 프로필이 로드됩니다.
참고 사항
- 이 패턴은 엣지 워커와 클라이언트 코드 간의 조정이 필요합니다.
- 엣지 워커는 SPA 하이드레이션 전에 데이터를 주입해야 합니다.
- 일반
/profile/:npub라우트가 정상 작동하도록 항상 라우트 파라미터를 먼저 확인하세요. - SEO 고려: 엣지 워커는 적절한 meta 태그도 설정해야 합니다.
- 서브도메인의 링크는 소유자 콘텐츠의 경우 로컬(
SmartLink컴포넌트 사용)을 유지하고, 다른 사용자의 콘텐츠는 apex 도메인으로 연결할 수 있습니다.
참조
- React Router v6 Routes: https://reactrouter.com/en/main/route/route
- Fastly Compute@Edge: https://developer.fastly.com/learning/compute/