npx skills add https://github.com/lobehub/lobehub --skill spa-routesSPA 结构:
src/spa/ – 入口点 (entry.web.tsx, entry.mobile.tsx, entry.desktop.tsx) 和路由配置 (router/)。路由配置放在此处,以避免与 src/routes/ 混淆。src/routes/ – 仅包含页面片段(根目录)。src/features/ – 按业务领域划分的业务逻辑和 UI。本项目采用 根目录与功能模块分离 的结构: 仅存放页面片段;业务逻辑和 UI 按领域存放在 中。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
src/routes/src/features/代理约束 — 桌面端路由一致性: 对桌面端路由树的修改必须在同一变更中同时更新 src/spa/router/desktopRouter.config.tsx 和 src/spa/router/desktopRouter.config.desktop.tsx(相同的路径、嵌套结构、索引路由和片段注册)。只更新其中一个会导致不一致;缺失的路由树可能无法注册路由,并在受影响的构建版本中表现为空白屏幕或导航损坏。
src/routes/ 下定义或重构布局/页面文件时src/features/ 时src/routes/(根目录)每个路由目录应仅包含:
| 文件 / 文件夹 | 用途 |
|---|---|
_layout/index.tsx 或 layout.tsx | 此片段的布局:使用 <Outlet /> 包装,可选的壳(例如侧边栏 + 主区域)。应保持精简:优先从 @/features/* 重新导出或组合。 |
index.tsx 或 page.tsx | 此片段的页面入口。仅从功能模块导入并渲染;不包含业务逻辑。 |
[param]/index.tsx (例如 [id], [cronId]) | 动态片段页面。遵循相同规则:精简,委托给功能模块。 |
规则: 路由文件应仅导入和组合。不要在 src/routes/ 内创建新的 features/ 文件夹或重型组件。
src/features/将面向领域的 UI 和逻辑放在此处:
按领域(例如 Pages、Home、Agent、PageEditor)组织,而不是按路由路径。一个路由可以使用多个功能模块;一个功能模块可以被多个路由使用。
每个功能模块应:
src/features/<FeatureName>/ 下index.ts 或 index.tsx 导出清晰的公共 API@/features/<FeatureName>/... 进行内部导入选择路由组
(main)/ – 桌面端主应用(mobile)/ – 移动端(desktop)/ – 特定于 Electrononboarding/、share/ – 特殊流程仅在src/routes/下创建片段文件
src/routes/(main)/my-feature/_layout/index.tsx 和 src/routes/(main)/my-feature/index.tsx(以及可选的 [id]/index.tsx)。在src/features/中实现布局和页面内容
src/features/MyFeature/)。index 导出。保持路由文件精简
export { default } from '@/features/MyFeature/MyLayout' 或组合几个功能模块组件 + <Outlet />。@/features/MyFeature(或特定子路径)导入并渲染;路由文件中不包含业务逻辑。注册路由(桌面端 — 两个文件,必须同时更新)
desktopRouter.config.tsx: 添加片段,使用指向路由模块的 dynamicElement / dynamicLayout(例如 @/routes/(main)/my-feature)。desktopRouter.config.desktop.tsx: 镜像相同的 RouteObject 结构:相同的 path / index / 父子结构。使用该文件中已有的静态导入和元素(参考相邻路由)。切勿只在这两个文件中的一个中注册。mobileRouter.config.tsx 代替(除非路由确实同时存在于两端,否则无需复制到桌面端配对文件中)。desktopRouter.config × 2)| 文件 | 作用 |
|---|---|
desktopRouter.config.tsx | 通过 dynamicElement / dynamicLayout 进行动态导入 — 用于代码分割;由 entry.web.tsx 和 entry.desktop.tsx 使用。 |
desktopRouter.config.desktop.tsx | 具有同步导入的相同路由树 — 为 Electron / 本地一致性及可预测的打包而保留。 |
任何改变路由树的操作(新增片段、重命名 path、移动布局、新增子路由)都必须在同一 PR 或提交中反映在两个文件中。删除路由时,需同时从两个文件中移除。
| 问题 | 放在 src/routes/ | 放在 src/features/ |
|---|---|---|
| 它是路由的布局包装器或页面入口吗? | 是 – _layout/index.tsx、index.tsx、[id]/index.tsx | 否 |
| 它包含业务逻辑或非简单的 UI 吗? | 否 | 是 – 放在正确的领域下 |
| 它是一个可复用的布局组件(侧边栏、页眉、主体)吗? | 否 | 是 |
| 它是一个钩子、状态管理使用或领域逻辑吗? | 否 | 是 |
| 它仅仅是重新导出或组合功能模块组件吗? | 是 | 否 |
示例
src/routes/(main)/page/_layout/index.tsx → export { default } from '@/features/Pages/PageLayout'src/features/Pages/PageLayout/ → 侧边栏、DataSync、主体、页眉、样式等。src/routes/(main)/page/index.tsx → 从 @/features/Pages 和 @/features/PageExplorer 导入 PageTitle、PageExplorerPlaceholder;使用 <PageTitle /> 和占位符进行渲染。src/features/Pages/ 下。我们正在逐步将现有路由迁移到此结构:
/page 路由 – 片段文件在 src/routes/(main)/page/,实现在 src/features/Pages/。当处理一个旧路由,且该路由在 src/routes/ 内仍有逻辑或 features/ 时:
src/features/<Domain>/ 并从路由导入。git mv 以保留历史记录。路由(精简):
src/routes/(main)/page/
├── _layout/index.tsx → 从 @/features/Pages/PageLayout 重新导出或组合
├── index.tsx → 从 @/features/Pages, @/features/PageExplorer 导入
└── [id]/index.tsx → 从 @/features/Pages, @/features/PageExplorer 导入
功能模块(实现):
src/features/Pages/
├── index.ts → 导出 PageLayout, PageTitle
├── PageTitle.tsx
└── PageLayout/
├── index.tsx → 侧边栏 + Outlet + DataSync
├── DataSync.tsx
├── Sidebar.tsx
├── style.ts
├── Body/ → 列表、操作、抽屉等
└── Header/ → 面包屑、添加按钮等
路由配置继续指向路由路径(例如 @/routes/(main)/page、@/routes/(main)/page/_layout);路由文件随后委托给功能模块。
每周安装量
84
代码仓库
GitHub 星标数
74.6K
首次出现
2026年3月1日
安全审计
安装于
codex82
cursor82
kimi-cli81
gemini-cli81
amp81
cline81
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 — desktop router parity: Edits to the desktop route tree must update both src/spa/router/desktopRouter.config.tsx and src/spa/router/desktopRouter.config.desktop.tsx in the same change (same paths, nesting, index routes, and segment registration). Updating only one causes drift; the missing tree can fail to register routes and surface as a blank screen or broken navigation on the affected build.
src/routes/src/features/src/routes/ (roots)Each route directory should contain only :
| File / folder | Purpose |
|---|---|
_layout/index.tsx or layout.tsx | Layout 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.tsx | Page entry for this segment. Only import from features and render; no business logic. |
[param]/index.tsx (e.g. [id], ) |
Rule: Route files should only import and compose. No new features/ folders or heavy components inside src/routes/.
src/features/Put domain-oriented UI and logic here:
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:
src/features/<FeatureName>/index.ts or index.tsx@/features/<FeatureName>/... for internal imports when neededChoose the route group
(main)/ – desktop main app(mobile)/ – mobile(desktop)/ – Electron-specificonboarding/, share/ – special flowsCreate only segment files undersrc/routes/
src/routes/(main)/my-feature/_layout/index.tsx and src/routes/(main)/my-feature/index.tsx (and optional [id]/index.tsx).desktopRouter.config × 2)| File | Role |
|---|---|
desktopRouter.config.tsx | Dynamic imports via dynamicElement / dynamicLayout — code-splitting; used by entry.web.tsx and entry.desktop.tsx. |
desktopRouter.config.desktop.tsx | Same route tree with synchronous imports — kept for Electron / local parity and predictable bundling. |
Anything that changes the tree (new segment, renamed path, moved layout, new child route) must be reflected in both files in one PR or commit. Remove routes from both when deleting.
| Question | Put 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.tsx | No |
| Does it contain business logic or non-trivial UI? | No | Yes – under the right domain |
| Is it a reusable layout piece (sidebar, header, body)? | No | Yes |
| Is it a hook, store usage, or domain logic? | No | Yes |
| Is it only re-exporting or composing feature components? |
Examples
src/routes/(main)/page/_layout/index.tsx → export { default } from '@/features/Pages/PageLayout'src/features/Pages/PageLayout/ → Sidebar, DataSync, Body, Header, styles, etc.src/routes/(main)/page/index.tsx → Import PageTitle, PageExplorerPlaceholder from @/features/Pages and @/features/PageExplorer; render with <PageTitle /> and placeholder.We are migrating existing routes to this structure step by step:
/page route – segment files in src/routes/(main)/page/, implementation in src/features/Pages/.When touching an old route that still has logic or features/ inside src/routes/:
src/features/<Domain>/ and importing from routes.git mv when moving files so history is preserved.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.
Weekly Installs
84
Repository
GitHub Stars
74.6K
First Seen
Mar 1, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex82
cursor82
kimi-cli81
gemini-cli81
amp81
cline81
前端开发AI工具 | 5大专业能力构建生产级前端页面 | 设计工程与动效系统
733 周安装
[cronId]| Dynamic segment page. Same rule: thin, delegate to features. |
Implement layout and page content insrc/features/
src/features/MyFeature/).index.Keep route files thin
export { default } from '@/features/MyFeature/MyLayout' or compose a few feature components + <Outlet />.@/features/MyFeature (or a specific subpath) and render; no business logic in the route file.Register the route (desktop — two files, always)
desktopRouter.config.tsx: Add the segment with dynamicElement / dynamicLayout pointing at route modules (e.g. @/routes/(main)/my-feature).desktopRouter.config.desktop.tsx: Mirror the same RouteObject shape: identical path / index / parent-child structure. Use the static imports and elements already used in that file (see neighboring routes). Do not register in only one of these files.mobileRouter.config.tsx instead (no need to duplicate into the desktop pair unless the route truly exists on both).| Yes |
| No |
src/features/Pages/.