跳到正文
cccskills
免费在 GitHub 公开中文介绍

spa-routes

本指南介绍如何添加和整理 SPA 路由,将页面入口放在 `src/routes/`,将领域功能和界面放在 `src/features/`,并协调桌面与移动端路由及加载骨架。

原文查看原文说明

Use for SPA routes and page segments: Web/desktop/mobile/popup routers, redirects and src/routes-to-features boundaries.

lobehub/lobehub星标 83,506(GitHub 83,506 + cccskills 0)

实测评分 未实测

查看安装方法

适用场景

  • 为 SPA 添加新页面
  • 确定路由与功能的代码位置
  • 协调网页、桌面和移动端路由

中文介绍

功能

本指南说明如何添加和整理 SPA 路由,并将页面入口与业务逻辑、界面分开放置。建议在 src/routes/ 中只保留精简的布局和页面入口文件,在 src/features/ 中按领域组织界面组件与状态管理。指南还介绍如何在共享定义中注册通用桌面路由、处理 Web 与 Electron 的差异、为移动端配置对应路由或注明不支持的路由,以及选择加载骨架并确定其注册位置。

适用场景

添加新页面或动态路由、逐步整理现有页面结构,或判断某个组件应放在路由目录还是功能目录时,可以参考本指南。如果多个页面需要共用领域功能,或需要让 Web、Electron 和移动端的路由保持对应,也适合使用。指南建议先查看项目当前结构,再决定新代码的放置位置。

使用示例

  • “请为这个 SPA 添加设置页面,并整理路由和功能目录的放置方式。”
  • “请判断这个页面的界面和逻辑应放在路由目录还是功能目录。”
  • “请检查桌面端新增路由对应的移动端路由和加载时的显示。”

注意事项

没有特别说明。

本介绍由 AI(OpenAI Codex)根据公开的 SKILL.md 撰写。准确的规格请查看下方原文。

文档检查

AI 根据 SKILL.md 的内容检查了需要注意的操作和使用前提。评分只依据实际运行的结果(实测对比)。

安全性发送数据、删除文件、执行命令等是否有说明
A良好资料描述的是路由结构调整,未提及发送或删除操作。
安装难易度所需工具和 API 密钥是否少且有说明
A良好资料未列出 API 密钥、外部服务或安装前提。

不计入评分。 由 AI(OpenAI Codex)于 2026年10月26日 评估。仅供参考,安装前请同时查看原文。

如果您认为评估有误,请申请复核

请写明哪一项有误以及原因。无需登录。我们会对照 SKILL.md 重新评估,并在此处更新结果。

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

包含的文件(1)

  • SKILL.md16.3 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

SPA Routes and Features Guide

SPA structure:

  • src/spa/ – Entry points (entry.web.tsx, entry.mobile.tsx, entry.desktop.tsx) and router config (router/). Router lives here to avoid confusion with src/routes/.
  • src/routes/ – Page segments only (roots).
  • src/features/ – Business logic and UI by domain.

This project uses a roots vs features split: src/routes/ only holds page segments; business logic and UI live in src/features/ by domain.

Agent constraint — shared desktop router: Common Web/Electron paths, nesting, metadata, lazy loaders, and preload groups belong in src/spa/router/desktopRouter.shared.tsx. The two desktopRouter.config* files are thin platform adapters; change them only for genuine runtime differences. Do not duplicate a common route in both adapters.

When to Use This Skill

  • Adding a new SPA route or route segment
  • Defining or refactoring layout/page files under src/routes/
  • Moving route-specific components or logic into src/features/
  • Deciding where to put a new component (route folder vs feature folder)

1. What Belongs in src/routes/ (roots)

Each route directory should contain only:

File / folderPurpose
_layout/index.tsx or layout.tsxLayout for this segment: wrap with <Outlet />, optional shell (e.g. sidebar + main). Should be thin: prefer re-exporting or composing from @/features/*.
index.tsx or page.tsxPage entry for this segment. Only import from features and render; no business logic.
[param]/index.tsx (e.g. [id], [cronId])Dynamic segment page. Same rule: thin, delegate to features.

Rule: Route files should only import and compose. No new features/ folders or heavy components inside src/routes/.


2. What Belongs in src/features/

Put domain-oriented UI and logic here:

  • Layout building blocks: sidebars, headers, body panels, drawers
  • Hooks and store usage for that domain
  • Domain-specific forms, lists, modals, etc.

Organize by domain (e.g. Pages, Home, Agent, PageEditor), not by route path. One route can use several features; one feature can be used by several routes.

Each feature should:

  • Live under src/features/<FeatureName>/
  • Export a clear public API via index.ts or index.tsx
  • Use @/features/<FeatureName>/... for internal imports when needed

3. How to Add a New SPA Route

  1. Choose the route group

    • (main)/ – desktop main app
    • (mobile)/ – mobile
    • (desktop)/ – Electron-specific
    • onboarding/, share/ – special flows
  2. Create only segment files under src/routes/

    • e.g. src/routes/(main)/my-feature/_layout/index.tsx and src/routes/(main)/my-feature/index.tsx (and optional [id]/index.tsx).
  3. Implement layout and page content in src/features/

    • Create or reuse a domain (e.g. src/features/MyFeature/).
    • Put layout (sidebar, header, body) and page UI there; export from the feature’s index.
  4. Keep route files thin

    • Layout: export { default } from '@/features/MyFeature/MyLayout' or compose a few feature components + <Outlet />.
    • Page: import from @/features/MyFeature (or a specific subpath) and render; no business logic in the route file.
  5. Register the route in the correct definition layer

    • Shared Web/Electron route: add the segment once in desktopRouter.shared.tsx with dynamicElement / dynamicLayout. Put its preloadId there as part of the shared lazy-loader definition.
    • Web-only or Electron-only route: add it to the corresponding thin desktopRouter.config.tsx adapter. Keep platform-only differences explicit and small.
    • Mobile counterpart (required for every desktop main-area route): see §3c. mobileRouter.parity.test.ts fails until the mobile router resolves the new URL to the same pattern, or the route is listed in MOBILE_UNSUPPORTED with a reason.
    • Mobile-only flow: use mobileRouter.config.tsx.
  6. Register a route skeleton (REQUIRED for every lazy route)

    • Every lazy route inside the main area renders RouteSegmentSkeleton (src/components/Skeleton/RouteSegment.tsx) while its chunk loads. It resolves via handle.meta.Skeleton only (deepest match wins, walking up through parent routes). Omitting Skeleton renders a blank pane — use NoRouteSkeleton when that is intentional.
    • Pick the skeleton when adding or restructuring a route:
      • A close-enough generic shape exists → Skeleton: createSurfaceSkeleton('list' | 'form' | 'grid' | 'editor' | 'detail') from @/components/Skeleton/Surface.
      • The page has a distinctive layout (dashboard, multi-panel, conversation) → author a bespoke component under src/components/Skeleton/ and register it (see ResourceHome.tsx, Generation.tsx, Conversation/).
    • Where to put it: on the route's routeMeta (feature routeMeta.ts or inline in desktopRouter.shared.tsx). A whole subtree sharing one shape can register once on the parent/layout route's handle — children with their own Skeleton still override.
    • When changing a page's layout, update its registered skeleton in the same PR — a stale skeleton that no longer matches the page is a regression.
    • Skeleton-only parent handles are safe for titles: title/icon resolution also walks deepest-first, and leaf metas keep winning.
    • The same deepest skeleton renders at every pending boundary of the chain (layout chunk, page chunk, then the page's own data wait). Make the page's in-component loading state the registered skeleton, or a body that continues it; a different shape there reads as a second, unrelated skeleton.
    • Route skeletons are always static imports: a fallback has to paint on its first render, and a lazy() skeleton (preloaded or not) renders blank until its chunk lands. That puts the skeleton's whole import graph in the entry chunk, so it may only reach UI primitives, other skeletons, constants and import type. Keep stores, services, feature components and value imports from barrels (@/types/* re-exports zod schemas) out of it; move the minimal piece next to the skeleton instead (see ResourceHome/Skeleton and Explorer/viewMode.ts).
    • A custom fallback (fallback: option, in-page Suspense, or an early return <XSkeleton />) keeps the handover continuous only through delayed() / DelayedFallback or RouteLoading, which register the skeleton as visible (retainSkeleton). Rendering a skeleton any other way lets the next boundary wait out its 200ms delay on a blank pane.

3a. Shared desktop route definition and platform adapters

FileRole
desktopRouter.shared.tsxSingle source of truth for common paths, nesting, metadata, lazy imports, and prioritized route preload groups.
desktopRouter.config.tsxThin Web adapter: mounts the common content tree at / and adds Web-only routes.
desktopRouter.config.desktop.tsxThin Electron adapter: injects per-tab Home behavior, TabHost root stubs, and the Electron-only /desktop-onboarding local setup.

Add or remove common routes only in desktopRouter.shared.tsx, or in a subtree module it imports (taskWorkspaceRoutes.tsx, projectWorkspaceRoutes.tsx, agentPageRoutes.tsx, memoryRoutes.tsx, resourceRoutes.tsx, workspaceSettingsRoutes.tsx for the padded /:slug/settings/* tabs) — the shared file is over the 1200-line limit, so large subtrees move out instead of growing it. Add any new subtree module to readRouterSources in desktopRouter.sync.test.tsx. Keep desktopRouter.sync.test.tsx passing so path behavior, lazy boundaries, preload ownership, and the intentional platform differences remain verified.


3b. Other .desktop.{ts,tsx} variants inside src/routes/

The thin router adapters are not duplicated trees. Other route modules may still colocate a <name>.desktop.{ts,tsx} next to a base <name>.{ts,tsx}; Vite's resolver swaps in the .desktop file for Electron builds. Those paired module implementations still carry a drift risk.

Known variants today:

Base file (web)Desktop file (Electron)Purpose
src/features/Settings/features/componentMap.tssrc/features/Settings/features/componentMap.desktop.tsSettings tab → component map. Web uses dynamic import(); desktop uses sync imports. componentMap.sync.test.ts enforces identical keys.
src/routes/(main)/agent/index.tsxsrc/routes/(main)/agent/index.desktop.tsxPage entry. Desktop variant overrides the web page wholesale (e.g. extra popup guards).
src/routes/(main)/group/index.tsxsrc/routes/(main)/group/index.desktop.tsxSame pattern as agent.

Rules:

  1. After editing any .ts/.tsx under src/routes/, glob the same directory for a <filename>.desktop.{ts,tsx} sibling. If one exists, apply the equivalent change there in the same commit.
  2. When adding a new SettingsTab, register it in both componentMap.ts (with dynamic(...)) and componentMap.desktop.ts (with a sync import). componentMap.sync.test.ts will fail the build otherwise.
  3. When adding a new desktop-only page wholesale-override, prefer a single base file with platform-aware code over introducing a new .desktop.tsx variant — only add a new variant when the two trees genuinely diverge (different store wiring, different popup guards, etc.).
  4. When deleting, remove both files together.

3c. Mobile counterparts of desktop routes

The mobile web build has its own router (mobileRouter.config.tsx, __MOBILE__), but it mounts the same page segments as desktop. Do not copy a desktop route into the mobile files: reuse the subtree module and adjust only what differs with overlayRoutes (routeOverlay.ts; withHandle, dropRoute, or a mobile element).

Mobile fileReuses
mobileAgentRoutes.tsxagentPageChildren under the agent scope layout; group profile / permission
mobileWorkspaceRoutes.tsxcreateTaskWorkspaceChildren, projectWorkspaceRoute, resourceRoute, memoryChildren, inside the mobile page layout
mobileWorkspaceSettingsRoutes.tsxworkspaceSettingsContentRoute children plus the mobile settings menu and full-bleed tabs
  • A desktop subtree added to one of those modules reaches mobile automatically. A new top-level desktop surface needs one entry in the matching mobile file; wrap full-screen pages in pageLayout ((mobile)/_page-layout).
  • Mobile NavHeader draws back + the route's NavPanelPortal sidebar as a drawer, and falls back to the route titleKey when the page header has no title. A page that paints no NavHeader gets one from the route handle mobileShellHeader: true | 'untitled' (and mobileShellTitle for a record name). Do not add a second back button inside the page; opt out with showTogglePanelButton={false} when the page keeps its own.
  • Desktop-only surfaces go in mobileUnsupportedRoutes and MOBILE_UNSUPPORTED in mobileRouter.parity.test.ts, so they redirect home instead of being read as a /:workspaceSlug.

4. How to Divide Files (route vs feature)

QuestionPut in src/routes/Put in src/features/
Is it the route’s layout wrapper or page entry?Yes – _layout/index.tsx, index.tsx, [id]/index.tsxNo
Does it contain business logic or non-trivial UI?NoYes – under the right domain
Is it a reusable layout piece (sidebar, header, body)?NoYes
Is it a hook, store usage, or domain logic?NoYes
Is it only re-exporting or composing feature components?YesNo

Examples

  • Route (thin):
    src/routes/(main)/page/_layout/index.tsx → export { default } from '@/features/Pages/PageLayout'
  • Feature (real implementation):
    src/features/Pages/PageLayout/ → Sidebar, DataSync, Body, Header, styles, etc.
  • Route (thin):
    src/routes/(main)/page/index.tsx → Import PageTitle, PageExplorerPlaceholder from @/features/Pages and @/features/PageExplorer; render with <PageTitle /> and placeholder.
  • Feature:
    Page list, actions, drawers, and hooks live under src/features/Pages/.

5. Progressive Migration (existing code)

Existing routes follow this structure unevenly: some are already thin segment files (e.g. src/routes/(main)/page/, src/routes/(main)/settings/), while others still hold logic or a features/ folder under src/routes/. Check the route's current state instead of assuming.

When touching an old route that still has logic or features/ inside src/routes/:

  1. Prefer adding new code in src/features/<Domain>/ and importing from routes.
  2. For larger refactors, move existing route-only logic into the right feature and then thin out the route files (re-export or compose from features).
  3. Use git mv when moving files so history is preserved.

6. Reference Structure (after Phase 1)

Route (thin):

src/routes/(main)/page/
├── _layout/index.tsx   → re-export or compose from @/features/Pages/PageLayout
├── index.tsx          → import from @/features/Pages, @/features/PageExplorer
└── [id]/index.tsx     → import from @/features/Pages, @/features/PageExplorer

Feature (implementation):

src/features/Pages/
├── index.ts            → export PageLayout, PageTitle
├── PageTitle.tsx
└── PageLayout/
    ├── index.tsx       → Sidebar + Outlet + DataSync
    ├── DataSync.tsx
    ├── Sidebar.tsx
    ├── style.ts
    ├── Body/           → list, actions, drawer, etc.
    └── Header/         → breadcrumb, add button, etc.

Router config continues to point at route paths (e.g. @/routes/(main)/page, @/routes/(main)/page/_layout); route files then delegate to features.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

acceptance

免费有简介

通过实际操作产品验证交付内容,记录用户可查看的证据,并发布验收报告。可检查已有验证计划或创建检查项,选择合适的操作界面,并使用 `lh` CLI 发布结果。

  • 实际检查更改后的产品行为
  • 收集截图或其他验证证据
  • 编写验证计划和报告
lobehub/lobehub8.4万2026年10月26日 更新

add-model-provider

免费有简介

本技能指导在 LobeHub 中接入 AI 模型提供商,涵盖运行时、模型信息、设置、品牌展示、文档和验收,并帮助判断新增模型是否需要新的提供商集成。

  • 为 LobeHub 接入新的 AI 模型提供商
  • 更新提供商设置说明或介绍
  • 判断添加模型是否需要新的提供商集成
lobehub/lobehub8.4万2026年10月26日 更新

add-setting-env

免费有简介

本技能指导如何用服务器端环境变量设置用户偏好默认值,涵盖变量校验、服务器配置、写入用户状态,以及更新 `.env.example` 和自托管文档。

  • 通过环境变量设置用户偏好默认值
  • 将设置接入服务器配置和用户状态
  • 更新环境变量示例及文档
lobehub/lobehub8.4万2026年10月26日 更新

agent-runtime-hooks

免费有简介

说明如何在智能体执行步骤、工具调用、人工审批、上下文压缩和子智能体调用时注册钩子。钩子可观察事件、模拟本地工具结果,也可通过 Webhook 按配置策略允许或拒绝工具调用。

  • 记录智能体步骤和错误
  • 测试时模拟工具结果
  • 通过 Webhook 允许或拒绝工具调用
lobehub/lobehub8.4万2026年10月26日 更新

agent-signal

免费有简介

此技能用于为智能体设计事件驱动的后台工作流,划分来源、信号、动作和策略的职责,并处理异步交接、去重、执行范围、完成记录与运行观测。

  • 添加事件触发的后台任务
  • 将工作移出对话请求
  • 防止动作重复执行
lobehub/lobehub8.4万2026年10月26日 更新

agent-testing-bot

免费有简介

本技能在 macOS 桌面聊天应用中测试机器人频道:打开应用、进入频道或联系人、发送消息、等待回复并截图。支持 Discord、Slack、Telegram、微信、飞书和 QQ;iMessage 使用 BlueBubbles 桥接。

  • 检查真实 Discord 频道中的机器人回复
  • 测试 Slack 或 Telegram 的对话流程
  • 记录 macOS 应用中的操作结果
lobehub/lobehub8.4万2026年10月26日 更新

查看 lobehub 的全部技能

报告此技能的问题