$libとファイルベースルーティングで保守しやすい構成を作る

SvelteKitはディレクトリ構造がそのままURLになるファイルベースルーティングを採用しています。これ自体はシンプルな仕組みですが、src/lib$libエイリアス)との線引きを最初に決めておかないと、プロジェクトが育つにつれてどこに何を置けばいいか分からなくなります。Alcogyでは複数の業務システムをOEM提供している都合上、この線引きのルールをプロジェクト間である程度統一しています。

routesとlibの基本的な役割分担

src/routes配下は「このURLでどんな画面を出すか」に専念させます。逆にsrc/libは「URLに依存しない再利用可能なロジック・コンポーネント」を置く場所です。判断基準は「このコードは特定のページ以外でも使うか」の一点に絞っています。

src/
  routes/
    tech/
      +layout.svelte
      +page.svelte
      articles/[slug]/+page.server.ts
      categories/[slug]/+page.server.ts
  lib/
    tech/
      config.ts
      categories.ts
      articles.ts
      components/CategoryNav.svelte

このテックブログの実装でも、記事一覧の取得やカテゴリ定義といったロジックは$lib/tech配下に切り出し、+page.server.ts側はgetArticlegetSlugsを呼び出すだけのごく薄い層にしています。

// $lib/tech/articles.ts
import type { Article } from './config';

const modules = import.meta.glob<{ default: string; metadata: Article }>(
	'/src/content/tech/articles/*.md',
	{ eager: true }
);

export function getSlugs(): string[] {
	return Object.keys(modules).map((path) =>
		path.split('/').pop()!.replace('.md', '')
	);
}

+page.server.tsにこのロジックを直接書くこともできますが、そうすると「記事一覧ページ」と「カテゴリページ」の両方で同じようなimport.meta.globの記述が重複します。$libに寄せておけば、ルーティング側は取得したデータをどう表示するかだけに集中できます。

コンポーネント分割の基準

$lib/componentsと、各ルート配下のローカルコンポーネントも同様の考え方で分けています。2ページ以上で使い回す、あるいはHeader・Footerのようにレイアウトを構成する要素は$lib/componentsへ。特定ページのレイアウトに強く依存する見た目のパーツは、そのルートディレクトリ内、あるいは+page.svelteにインラインで書いてしまうことも珍しくありません。

無理に共通化しようとすると、1つのコンポーネントに複数ページ分の条件分岐が詰め込まれ、かえって可読性が落ちることがあります。Alcogyでは「2箇所目の利用が発生したタイミングで共通化する」というルールを緩く敷いており、最初から汎用化を狙いすぎないようにしています。

OEM提供におけるディレクトリ構成の意味

OEM/ホワイトレーベルとして複数の顧客向けに業務システムを提供する場合、この構成ルールが揃っていることは想像以上に効いてきます。顧客ごとにカスタマイズが必要な部分はroutes側に閉じ込め、共通のドメインロジックや認証まわりは$libにまとめておくことで、案件間でのコードの持ち回りや差分管理がしやすくなります。ファイルベースルーティングと$libエイリアスは、単なるSvelteKitの機能というより、複数プロジェクトを横断して保守するための設計上の土台になっていると考えています。