Communitygithub.com

SPA 子域名根路径渲染(无重定向)

在 React Router SPA 中,直接在子域名根路径 (/) 渲染页面组件,无需 URL 重定向。解决子域名显示空白页或多余路径问题。

SPA 子域名根路径渲染(无重定向) 是什么?

该技能提供了一种在 React Router 单页应用(SPA)中,于子域名根路径(例如 `username.example.com`)直接渲染页面组件,而无需进行 URL 重定向的解决方案。主要解决两个常见问题:一是使用 `<Navigate to="/profile/username" replace />` 会导致 URL 改变,破坏子域名的简洁性;二是依赖 `useParams()` 的组件在根路径缺少路由参数时会显示空白页。实现此模式需要在边缘工作者(如 Fastly Compute、Cloudflare Workers)中向 HTML 注入用户数据(全局变量 `window.__SUBDIVINE_USER__`),并在 SPA 客户端组件中回退使用该数据。核心步骤包括:调整路由配置以直接渲染组件而非重定向,修改组件以优先使用路由参数,并在参数缺失时回退到注入的全局数据。此模式适用于任何需要子域名展示个性化内容(如个人主页)的场景,支持流行的边缘计算平台,并需协调边缘端和客户端的数据注入流程。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/divinevideo/divine-web/tree/HEAD/.agents/skills/spa-subdomain-root-render

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

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;
}

验证

  1. 访问 username.domain.com/ - URL 应保持为 /,不重定向
  2. 个人资料内容应正确加载
  3. 检查浏览器控制台,不应出现关于缺失标识符的错误
  4. 在开发者工具中验证 window.__DIVINE_USER__ 包含预期的数据

示例

对于 divine-web:

  • thecomedianriley.divine.video/ 在根路径 / 渲染 ProfilePage
  • ProfilePage 首先检查 useParams()(在根路径 / 为空)
  • 回退到使用注入数据中的 getSubdomainUser()?.npub
  • 个人资料加载,URL 不变

注意事项

  • 此模式需要边缘工作者和客户端代码之间的协调
  • 边缘工作者必须在 SPA 水合(hydration)之前注入数据
  • 始终优先检查路由参数,确保正常的 /profile/:npub 路由正常工作
  • 考虑 SEO:边缘工作者还应设置适当的 meta 标签
  • 子域名上的链接可以使用 SmartLink 组件,对于所有者内容保持本地链接,但其他用户的内容链接到主域名

参考

相关技能