mcpField
S
spa-routes

spa-routes

github.com/lobehub開発83.6k204 回インストール今日更新
リポジトリ

spa-routes について

LobeHub のコードベースで SPA のルートと機能を整理する開発スキルです。ルートの配置、Web/Electron 共通ルーティング、遅延読み込み時のスケルトン、プラットフォーム別実装を案内します。

主な機能

  1. 01ルートと機能実装の分離
  2. 02共通デスクトップルートの定義
  3. 03遅延ルートのスケルトン登録
  4. 04Web と Electron の差分処理
  5. 05段階的なルート移行の指針

活用シーン

  1. 01SPA ルートの追加
  2. 02ルートファイルから業務ロジックを移動
  3. 03Web と Electron の共通パスを更新

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/lobehub/lobehub --skill spa-routes

インストール後、Claude Code で/spa-routesを入力して読み込まれたか確認します。

詳細

SPA ルートと機能のガイド

SPA の構成:

  • src/spa/ – エントリーポイント(entry.web.tsx、entry.mobile.tsx、entry.desktop.tsx)とルーター設定(router/)です。src/routes/ との混同を避けるため、ルーターはここに置きます。
  • src/routes/ – ページセグメントのみ(ルート)。
  • src/features/ – ドメインごとのビジネスロジックと UI。

このプロジェクトでは、ルートと機能を分離しています。src/routes/ にはページセグメントのみを置き、ビジネスロジックと UI はドメインごとに src/features/ に置きます。

エージェント向けの制約 — 共有デスクトップルーター: Web/Electron 共通のパス、ネスト、メタデータ、遅延ローダー、プリロードグループは src/spa/router/desktopRouter.shared.tsx に置きます。2 つの desktopRouter.config* ファイルは薄いプラットフォームアダプターです。実行環境による真の違いがある場合にのみ変更してください。共通ルートを両方のアダプターに重複して定義しないでください。

このスキルを使用する場面

  • 新しい SPA ルートまたはルートセグメントの追加
  • src/routes/ 以下のレイアウト/ページファイルの定義またはリファクタリング
  • ルート固有のコンポーネントやロジックを src/features/ に移動
  • 新しいコンポーネントの配置先(ルートフォルダーか機能フォルダーか)の判断

1. src/routes/ に配置するもの(ルート)

各ルートディレクトリには、以下のみを含めます。

ファイル/フォルダー目的
_layout/index.tsx または layout.tsxこのセグメントのレイアウトです。<Outlet /> でラップし、任意でシェル(例:サイドバー+メイン領域)を追加します。薄く保ち、@/features/* から再エクスポートまたは合成することを推奨します。
index.tsx または page.tsxこのセグメントのページエントリーです。機能からのみインポートしてレンダリングし、ビジネスロジックは含めません。
[param]/index.tsx(例:[id]、[cronId])動的セグメントのページです。同じルールに従い、薄く保って機能に委譲します。

ルール: ルートファイルではインポートと合成のみを行います。src/routes/ 内に新しい features/ フォルダーや大きなコンポーネントを作成しないでください。


2. src/features/ に配置するもの

ドメイン指向の UI とロジックをここに配置します。

  • レイアウトの構成要素:サイドバー、ヘッダー、ボディパネル、ドロワー
  • そのドメインのフックとストアの使用
  • ドメイン固有のフォーム、リスト、モーダルなど

ルートパスではなく、ドメイン(例:Pages、Home、Agent、PageEditor)ごとに整理します。1 つのルートで複数の機能を使用でき、1 つの機能を複数のルートで使用できます。

各機能は次の要件を満たす必要があります。

  • src/features/<FeatureName>/ 以下に配置する
  • index.ts または index.tsx を介して明確な公開 API をエクスポートする
  • 必要に応じて、内部インポートには @/features/<FeatureName>/... を使用する

3. 新しい SPA ルートの追加方法

  1. ルートグループを選択する

    • (main)/ – デスクトップのメインアプリ
    • (mobile)/ – モバイル
    • (desktop)/ – Electron 固有
    • onboarding/、share/ – 特殊なフロー
  2. src/routes/ 以下にはセグメントファイルのみを作成する

    • 例:src/routes/(main)/my-feature/_layout/index.tsx と src/routes/(main)/my-feature/index.tsx(任意で [id]/index.tsx も追加)。
  3. レイアウトとページコンテンツを src/features/ に実装する

    • ドメインを新規作成するか再利用します(例:src/features/MyFeature/)。
    • レイアウト(サイドバー、ヘッダー、ボディ)とページ UI を配置し、機能の index からエクスポートします。
  4. ルートファイルを薄く保つ

    • レイアウト:export { default } from '@/features/MyFeature/MyLayout' を使用するか、いくつかの機能コンポーネントと <Outlet /> を合成します。
    • ページ:@/features/MyFeature(または特定のサブパス)からインポートしてレンダリングし、ルートファイルにビジネスロジックを含めません。
  5. 正しい定義レイヤーにルートを登録する

    • Web/Electron 共通ルート: desktopRouter.shared.tsx に dynamicElement / dynamicLayout を使ってセグメントを一度だけ追加します。共有の遅延ローダー定義の一部として、そこで preloadId を指定します。
    • Web 専用または Electron 専用のルート: 対応する薄い desktopRouter.config.tsx アダプターに追加します。プラットフォーム固有の違いは明示し、最小限に抑えます。
    • モバイル専用のフロー: mobileRouter.config.tsx を使用します。モバイルは共有デスクトップツリーを使用しません。
  6. ルートスケルトンを登録する(すべての遅延ルートで必須)

    • メイン領域内のすべての遅延ルートは、チャンクの読み込み中に RouteSegmentSkeleton(src/components/Skeleton/RouteSegment.tsx)をレンダリングします。これは handle.meta.Skeleton のみを通じて解決されます(一致する中で最も深いルートが優先され、親ルートをたどります)。Skeleton を省略するとペインが空白になるため、それが意図的な場合は NoRouteSkeleton を使用します。
    • ルートの追加や再構成に合わせてスケルトンを選択します。
      • 近い形状の汎用スケルトンがある場合 → @/components/Skeleton/Surface の Skeleton: createSurfaceSkeleton('list' | 'form' | 'grid' | 'editor' | 'detail') を使用します。
      • ページに特徴的なレイアウト(ダッシュボード、マルチパネル、会話)がある場合 → src/components/Skeleton/ に専用コンポーネントを作成して登録します(ResourceHome.tsx、Generation.tsx、Conversation/ を参照)。
    • 登録場所:ルートの routeMeta(機能の routeMeta.ts または desktopRouter.shared.tsx 内)に設定します。同じ形状を共有するサブツリー全体では、親/レイアウトルートの handle に一度だけ登録できます。独自の Skeleton を持つ子ルートは、それを引き続き優先します。
    • ページのレイアウトを変更する場合は、同じ PR で登録済みのスケルトンも更新します。ページに合わなくなった古いスケルトンは回帰です。
    • スケルトンのみを持つ親のハンドルは、タイトルについても安全です。タイトル/アイコンの解決も最も深いルートから順にたどり、リーフのメタデータが引き続き優先されます。
    • 同じ最も深いスケルトンが、チェーン内のすべての保留中境界(レイアウトのチャンク、ページのチャンク、さらにページ自身のデータ待機時)でレンダリングされます。ページ内の読み込み状態を登録済みのスケルトンにするか、それを引き継ぐ本文にしてください。ここで異なる形状を使うと、別個の無関係なスケルトンに見えてしまいます。
    • ルートスケルトンは常に静的インポートにします。フォールバックは初回レンダリングで表示する必要があり、lazy() のスケルトンは(プリロード済みかどうかにかかわらず)チャンクが読み込まれるまで空白になります。そのため、スケルトンのインポートグラフ全体がエントリチャンクに含まれます。参照できるのは UI プリミティブ、他のスケルトン、定数、import type のみにしてください。ストア、サービス、機能コンポーネント、バレルからの値インポート(@/types/* は zod スキーマを再エクスポートします)は含めず、代わりに最小限の要素をスケルトンの近くに移動します(ResourceHome/Skeleton および Explorer/viewMode.ts を参照)。
    • カスタムフォールバック(fallback: オプション、ページ内の Suspense、または早期の return <XSkeleton />)で切り替えを連続させるには、delayed() / DelayedFallback または RouteLoading を使用してスケルトンを表示中として登録する必要があります(retainSkeleton)。それ以外の方法でスケルトンをレンダリングすると、次の境界で 200ms の遅延が終わるまでペインが空白になります。

3a. 共通のデスクトップルート定義とプラットフォームアダプター

ファイル役割
desktopRouter.shared.tsx共通パス、ネスト構造、メタデータ、遅延インポート、優先順位付きルートプリロードグループの唯一の信頼できる情報源です。
desktopRouter.config.tsx薄い Web アダプターです。共通コンテンツツリーを / にマウントし、Web 専用ルートを追加します。
desktopRouter.config.desktop.tsx薄い Electron アダプターです。タブごとの Home 動作、TabHost のルートスタブ、Electron 専用のオンボーディングを注入します。

共通ルートの追加や削除は desktopRouter.shared.tsx でのみ行ってください。desktopRouter.sync.test.tsx をパスの動作、遅延読み込みの境界、プリロードの所有関係、意図されたプラットフォーム間の差異を検証するテストとして、常に通過する状態に保ってください。


3b. src/routes/ 内のその他の .desktop.{ts,tsx} バリアント

薄いルーターアダプターはツリーを重複して持つものではありません。ほかのルートモジュールでは、ベースの <name>.{ts,tsx} の隣に <name>.desktop.{ts,tsx} を配置する場合があります。Vite のリゾルバーは、Electron ビルドでは .desktop ファイルに差し替えます。こうした対になるモジュールの実装にも、ずれが生じるリスクがあります。

現在確認されているバリアント:

ベースファイル(Web)デスクトップファイル(Electron)目的
src/features/Settings/features/componentMap.tssrc/features/Settings/features/componentMap.desktop.tsSettings タブからコンポーネントマップへの対応付けです。Web では動的な import() を使用し、デスクトップでは同期インポートを使用します。componentMap.sync.test.ts がキーの一致を検証します。
src/routes/(main)/agent/index.tsxsrc/routes/(main)/agent/index.desktop.tsxページのエントリーポイントです。デスクトップのバリアントは Web ページ全体を上書きします(例:追加のポップアップガード)。
src/routes/(main)/group/index.tsxsrc/routes/(main)/group/index.desktop.tsxagent と同じパターンです。

ルール:

  1. src/routes/ 内の任意の .ts/.tsx を編集した後、同じディレクトリで <filename>.desktop.{ts,tsx} の兄弟ファイルを glob で検索してください。存在する場合は、同等の変更を同じコミットで適用してください。
  2. 新しい SettingsTab を追加する場合は、componentMap.ts(dynamic(...) を使用)と componentMap.desktop.ts(同期 import を使用)の両方に登録してください。そうしないと、componentMap.sync.test.ts によってビルドが失敗します。
  3. デスクトップ専用ページで全体を上書きするものを新たに追加する場合は、新しい .desktop.tsx バリアントを導入するのではなく、プラットフォームに応じたコードを含む単一のベースファイルを優先してください。新しいバリアントを追加するのは、2 つのツリーが実際に異なる場合(異なるストア接続、異なるポップアップガードなど)に限ってください。
  4. 削除する場合は、両方のファイルをまとめて削除してください。

4. ファイルの分け方(route と feature)

質問src/routes/ に配置するものsrc/features/ に配置するもの
ルートのレイアウトラッパーまたはページのエントリーポイントですか?はい — _layout/index.tsx、index.tsx、[id]/index.tsxいいえ
ビジネスロジックまたは複雑な UI が含まれていますか?いいえはい — 適切なドメインの下
再利用可能なレイアウト要素(サイドバー、ヘッダー、本文)ですか?いいえはい
フック、ストアの使用、またはドメインロジックですか?いいえはい
feature コンポーネントの再エクスポートまたは組み合わせだけを行っていますか?はいいいえ

例

  • ルート(薄い構成):
    src/routes/(main)/page/_layout/index.tsx → export { default } from '@/features/Pages/PageLayout'
  • feature(実際の実装):
    src/features/Pages/PageLayout/ → サイドバー、データ同期、本文、ヘッダー、スタイルなど
  • ルート(薄い構成):
    src/routes/(main)/page/index.tsx → @/features/Pages と @/features/PageExplorer から PageTitle、PageExplorerPlaceholder をインポートし、<PageTitle /> とプレースホルダーをレンダリングします。
  • feature:
    ページ一覧、アクション、ドロワー、フックは src/features/Pages/ の下に配置します。

5. 段階的な移行(既存コード)

既存のルートは、この構成に一貫して従っているわけではありません。すでに薄いセグメントファイルになっているもの(例:src/routes/(main)/page/、src/routes/(main)/settings/)もあれば、src/routes/ の下にロジックや features/ フォルダーが残っているものもあります。思い込みで判断せず、ルートの現在の状態を確認してください。

src/routes/ 内にロジックや features/ が残っている古いルートを変更する場合:

  1. 新しいコードは src/features/<Domain>/ に追加し、ルートからインポートすることを優先してください。
  2. 大規模なリファクタリングでは、既存のルート専用ロジックを適切な feature に移してから、ルートファイルを薄くしてください(feature から再エクスポートするか、組み合わせます)。
  3. ファイルを移動する際は、履歴を保持するために git mv を使用してください。

6. 参照構成(フェーズ 1 完了後)

ルート(薄い構成):

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(実装):

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.

ルーターの設定は引き続きルートパス(例:@/routes/(main)/page、@/routes/(main)/page/_layout)を参照します。ルートファイルはその後、feature に処理を委譲します。

コメント