本文へ移動
cccskills
無料GitHub で公開日本語紹介

spa-routes

SPAの画面追加やルーター設定で、ページの入口と機能実装の置き場所を整理し、Web・デスクトップ・モバイル間のルート差分や読み込み時の表示も一貫させるための指針です。

原文原文の説明を見る

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

lobehub/lobehubスター 83,504(GitHub 83,504 + cccskills 0)

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • SPAに新しいページを追加したいとき
  • routeとfeatureの配置を決めたいとき
  • Web・デスクトップ・モバイルのルートを揃えたいとき

日本語での紹介

できること

SPAのルート追加や整理にあたり、ページの入口と業務ロジック・UIの置き場所を分ける方法を示します。src/routes/にはレイアウトやページの薄い入口だけを置き、ドメイン単位の画面部品や状態管理はsrc/features/にまとめます。共通のデスクトップルートを共有定義へ登録する方法、Web・Electronの差分、モバイル側の対応ルートや非対応ルートの扱いも確認できます。遅延読み込み中に表示するスケルトンの選び方と登録場所も説明します。

こんなときに便利

新しい画面や動的ルートを追加するとき、既存ページの構成を段階的に整理するときに役立ちます。ルートファイルへ機能を詰め込まず、複数ページで使う機能をドメイン別にまとめたい場合や、Web・Electron・モバイルでルートのずれを防ぎたい場合にも参照できます。既存コードは一律に前提を置かず、現在の構成を確認してから新しい実装の置き場所を決める方針です。

使い方の例

  • 「このSPAに新しい設定ページを追加するので、ルートとfeatureの配置を整理してください」
  • 「この画面のUIとロジックを、routeとfeatureのどちらに置くべきか確認してください」
  • 「デスクトップに追加したルートのモバイル対応と読み込み時の表示を確認してください」

注意点

特記事項はありません。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

資料チェック

SKILL.md の記述から、注意が要る操作と導入の前提を AI が確認しました。点数は実際に動かした結果(実行検証)だけで付けています。

安全面送信・削除・コマンド実行などの扱いが明示されているか
A良好記載された作業はルートや配置の変更で、送信・削除等の操作はありません。
導入のしやすさ必要なツールや API キーが少ないか、書かれているか
A良好必要なAPIキーや外部サービスなどの導入前提は記載されていません。

点数には入れていません。 2026年10月26日 時点で AI(OpenAI Codex)が判定しました。目安なので、入れる前に原文も確認してください。

評価が違うと思ったら、見直しを依頼する

どの項目が、なぜ違うと思うかを書いてください。ログインは不要です。内容を SKILL.md と照らし合わせて評価を作り直し、この欄に反映します。

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

含まれるファイル(1)

  • SKILL.md16.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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

無料概要あり

実際の製品を操作して、利用者が確認できる動作証拠を集め、検証計画に沿った受け入れレポートとして公開するスキルです。

  • 変更後の製品動作を実地で検証したいとき
  • 画面キャプチャなどの証拠を集めたいとき
  • 検証計画とレポートを作成したいとき
lobehub/lobehub8.4万2026年10月26日 更新

add-model-provider

無料概要あり

LobeHubにAIモデルプロバイダーを追加し、実行時連携からモデル表示、設定、ブランド表記、文書化、動作確認まで進めるスキルです。

  • 新しいAIモデルプロバイダーを追加したいとき
  • プロバイダーの設定手順や説明文を更新したいとき
  • 既存プロバイダーへのモデル追加要件を見極めたいとき
lobehub/lobehub8.4万2026年10月26日 更新

add-setting-env

無料概要あり

ユーザー設定の初期値をサーバー側の環境変数で切り替えるため、型定義や設定の組み立て、ユーザーストアへの反映、環境変数の例と説明書きを追加する手順です。

  • 設定の初期値を環境変数で変えたいとき
  • 設定項目をサーバー設定に追加したいとき
  • 環境変数の例と説明を更新したいとき
lobehub/lobehub8.4万2026年10月26日 更新

agent-runtime-hooks

無料概要あり

エージェント実行中の各段階をフックで監視し、ツール呼び出しの模擬・制御や人の承認、圧縮、サブエージェント処理に介入する仕組みを扱います。

  • エージェントの実行状況を記録したいとき
  • ツール結果をテスト用に模擬したいとき
  • Webhookでツール実行を許可・拒否したいとき
lobehub/lobehub8.4万2026年10月26日 更新

agent-signal

無料概要あり

イベントを起点にエージェントのバックグラウンド処理を組み立て、ソース・シグナル・アクション・ポリシーの役割分担や非同期連携を実装するためのスキルです。

  • イベント起点の処理を追加したいとき
  • バックグラウンド処理を非同期化したいとき
  • 重複実行を防ぐ仕組みを整えたいとき
lobehub/lobehub8.4万2026年10月26日 更新

agent-testing-bot

無料概要あり

DiscordやSlackなどの実際のチャットアプリでボットにメッセージを送り、応答を画面で確認する受け入れテストを行うスキルです。

  • 実際のDiscordでボット応答を確かめたいとき
  • SlackやTelegramの会話経路をテストしたいとき
  • macOSアプリ上の操作結果を画面で記録したいとき
lobehub/lobehub8.4万2026年10月26日 更新

lobehub のスキルをすべて見る

このスキルの問題を報告する