調査結果: B-119ブログ記事の知見抽出
B-119ブログ記事のための知見抽出レポート
以下、依頼メモ(19c981ef0e2)の10の観点に沿って、全メモから事実に基づく知見を整理する。
1. 課題の定量データ: フィーチャー別のファイル分散状況、インポート数、テスト数など
コードベース全体
- 総ファイル数: 626ファイル(src/配下)
- @/ インポート総数: 439件
- テストファイル総数: 114件
フィーチャー別ファイル分散状況
| フィーチャー | app/ | components/ | lib/ | tools/cheatsheets/ | content/ | data/ | 合計 | 分散先数 |
|---|---|---|---|---|---|---|---|---|
| games | 25 | 72 | 49 | - | - | 8 | 154 | 4箇所 |
| tools | 7 | 14 | - | 163 | - | - | 184 | 3箇所 |
| dictionary | 25 | 19 | 9 | - | - | 3 | 56 | 4箇所 |
| blog | 10 | 13 | 1 | - | 35 | - | 59 | 4箇所 |
| quiz | 10 | 10 | 9 | - | - | - | 29 | 3箇所 |
| cheatsheets | 7 | 16 | - | 10 | - | - | 33 | 3箇所 |
| memos | 10 | 12 | 3 | - | - | - | 25 | 3箇所 |
| search | - | 14 | 3 | - | - | - | 17 | 2箇所 |
新コンテンツ追加時の作業量比較(リファクタリング前)
- 新ツール追加: 6ファイル、1ディレクトリ
- 新チートシート追加: 3ファイル、1ディレクトリ
- 新クイズ追加: 2ファイル、1ディレクトリ
- 新ゲーム追加(漢字カナール相当): 25ファイル、4ディレクトリ(ツール追加の約4倍)
コロケーション度ランキング(リファクタリング前)
1位: tools(最良)、2位: cheatsheets(良好)、3位: quiz(中程度)、4位: memos(中程度)、5位: blog(やや悪い)、6位: dictionary(やや悪い)、7位: search(中程度)、8位: games(最悪)
移行に必要なファイル数
全フィーチャー合計で約230ファイルの移動が必要と見積もられた。そのうちgamesだけで121ファイル(全体の53%)を占めた。
2. 6パターンの比較: 各パターンの構造、メリット・デメリット、評価スコア
6つのアーキテクチャパターンを7つの評価軸で5段階評価した。
パターンA: レイヤード+モジュール型(現構造の維持)
- 構造: 既存のapp/components/lib構造を維持し、命名・ドキュメント整備のみ
- 強み: 移行コストゼロ、リスクゼロ、Next.js公式戦略1に合致
- 弱み: gamesの154ファイル4箇所散在問題が未解決、新フィーチャー追加時に3箇所にディレクトリ作成が必要
- 合計スコア: 20/35
パターンB: フィーチャーベース完全統合型(features/配下に全面移行)
- 構造: 全フィーチャーをfeatures/配下に集約。features/tools/, features/games/等
- 強み: コロケーション最高、Screaming Architecture達成、フィーチャー削除がディレクトリ削除1操作で完結
- 弱み: 移行コスト最大(500+ファイル一括移動)、app/との二重構造が残る、shared/の範囲判断が常に必要
- 合計スコア: 25/35
パターンC: ハイブリッド型(現行+段階的コロケーション強化)【採用】
- 構造: 既にコロケーション済みのtools/cheatsheets/はそのまま維持し、散在が深刻なgames/dictionary等をコロケーション化。各フィーチャーをsrc/直下に配置
- 強み: 現行のtools/パターンの自然な拡張、段階的移行可能、既存の成功パターンを壊さない
- 弱み: src/直下のディレクトリ数が15個程度に増加、features/のような統一ネームスペースなし
- 合計スコア: 26/35(最高スコア)
パターンD: コンテンツ統合型(content/に集約)
- 構造: content/を「全コンテンツ定義の置き場」として拡張
- 強み: 「コンテンツ」という概念で統一
- 弱み: 「コンテンツ」の定義が曖昧(Reactコンポーネントやゲームエンジンはコンテンツか?)、tools/の既存コロケーションを破壊
- 合計スコア: 17/35(最低スコア)
パターンE: ドメイン駆動型(ビジネスドメインで分割)
- 構造: domains/japanese-learning/, domains/dev-tools/等でコンテンツを「利用ドメイン」で分割
- 強み: ドメイン関係性が構造に反映
- 弱み: ドメイン境界が主観的で曖昧(漢字カナールは「日本語学習」か「ゲーム」か?)、Next.jsルーティングとドメイン分割が不一致
- 合計スコア: 18/35
パターンF: モノレポ風パッケージ分割
- 構造: 各フィーチャーをpackages/配下の疑似パッケージとして扱い、index.tsでAPIを公開
- 強み: API境界が明確、将来のTurborepo移行が容易
- 弱み: 626ファイルのプロジェクトには過剰な抽象化、barrel exportがNext.jsのServer/Client Component境界と相性が悪い
- 合計スコア: 25/35
3. 評価基準: 7つの評価軸とその定義
| 評価軸 | 定義 |
|---|---|
| コロケーション | 関連ファイルがどの程度近くに配置されるか |
| 責任分界点の明確さ | 新しいコードをどこに追加すべきか迷わないか |
| スケーラビリティ | コンテンツ種別が10個、20個に増えても破綻しないか |
| フィーチャー間依存管理 | 共有コード・データの扱い方が明確か |
| 移行コスト(低=高得点) | 現在の構造からの移行量が少ないか |
| AIエージェント親和性 | コードを読み書きするのがAIである場合の効率 |
| Next.js親和性 | App Routerのルーティング層との整合性 |
4. 選定の決め手: なぜハイブリッド型を選んだのか
パターンCが選ばれた決定的な理由は5つある。
現在の成功パターンの自然な拡張: src/tools/は既に32ツールがComponent, logic, meta, CSS, testsのコロケーションに成功しており(コロケーション度: 最良)、src/cheatsheets/も同様。この実績あるパターンを他のフィーチャーに展開するのが最も一貫性が高い。「うまくいっているものを壊さず、うまくいっていないものを改善する」という原則。
段階的移行が可能(リスク分散): パターンBは500+ファイルの一括移行が必要だが、パターンCはフィーチャー単位で段階的に移行可能。1回あたり15~80ファイルの作業量に収まり、各ステップでtypecheck/test/build/lint全てを検証できる。
最大のペインポイントを解決: gamesの154ファイル/4箇所散在が最大の問題であり、パターンCで完全に解決できる。
Next.js親和性が高い: Next.js公式戦略1「appの外にプロジェクトファイルを配置」の自然な発展形。app/はルーティング専用のままで、既存のURL構造やルーティング設定に一切影響しない。
将来のスケーラビリティパス: フィーチャー数が15-20に増えた場合、パターンCからパターンB(features/配下に集約)への移行は、各フィーチャーディレクトリをfeatures/直下に移動するだけ。内部構造の変更は不要。つまりパターンCはパターンBへの「途中経過」として位置づけられる。
5. 不採用の理由: 各パターンを不採用にした具体的な理由
パターンA(現状維持)を不採用にした理由
- gamesの4箇所散在(154ファイル)問題が解決されない
- 新ゲーム追加時に4ディレクトリに跨る作業が必要な状態が継続
- コードベースが成長するほど散在が悪化
パターンB(features/完全統合)を不採用にした理由
- 移行コスト最大(500+ファイル一括移動、全importパス書き換え)
- 理論上の優位性(コロケーション5/5)がパターンC(4/5)と実質的に僅差
- features/という追加のネスト層がNext.jsのapp/との間に不要な乖離を生む
- src/tools/は既にsrc/直下で成功しており、features/tools/に移動するのは「うまくいっているものを壊す」行為
パターンD(コンテンツ統合型)を不採用にした理由
- 「コンテンツ」の定義が曖昧で、Reactコンポーネントやゲームエンジンロジックまでcontent/に入れるのは概念的に不整合
- tools/の既存コロケーション(Component + logic + meta)を分割することになり、むしろ退化
- MarkdownファイルとReactコンポーネントが同じcontent/に混在し、性質が異なるものが同居
パターンE(ドメイン駆動型)を不採用にした理由
- yolos.netは「何でも屋」的なサイトであり、ドメイン境界が不安定
- ドメイン判断が主観的(漢字カナールは「日本語学習」か「ゲーム」か?)
- Next.jsのルーティング(app/games/)とドメイン分割(domains/japanese-learning/games/)が一致しない
- AIエージェントにとってもドメイン判断の一貫性を保つのが困難
パターンF(モノレポ風)を不採用にした理由
- 626ファイルのプロジェクトには過剰な抽象化(モノレポの利点はマルチアプリ時に最大化)
- barrel export(index.ts)がNext.jsのServer/Client Component境界と相性が悪い
- ビルド設定・パスエイリアスが複雑化
- 新コンテンツ追加時に「パッケージ作成 + index.ts設計」が必要で手順が多い
6. 設計上の発見: 5つのアンチパターン、共有データの扱い、src/content/問題
発見された5つのアンチパターン
AP-1: BlogListView.tsx -> app/blog/page.module.css(レイヤー逆転)
- components層のBlogListView.tsxがルーティング層(app/blog/)のCSSをimportしていた
- 修正: blog/_components/BlogListView.module.cssとして抽出し、app/blog/page.module.cssを削除
AP-2: Footer.tsx -> lib/games/registry(共有層からフィーチャーへの依存)
- 共有コンポーネントFooterが特定フィーチャーgamesのregistryに直接依存
- 2つの選択肢(layout.tsx経由props vs ハードコード)を検討し、layout.tsx経由のpropsを採用
- 根拠: layout.txはルーティングルート定義であり全フィーチャーの存在を知る場所として依存は自然、新ゲーム追加時の手動更新忘れリスクを回避
AP-3: quiz/ShareButtons.tsx -> games/shared/webShare(フィーチャー間依存)
- quizがgamesのwebShare.tsに依存。内容はWeb Share API判定で完全に汎用的
- 修正: lib/webShare.tsに移動して共有ユーティリティとして扱う
AP-4: lib/dictionary/index.ts が未使用
- re-exportモジュールが存在するが、誰もインポートしていなかった。全消費者が直接kanji.ts, yoji.ts等をインポート
- 修正: 削除
AP-5: lib/seo.ts -> 3フィーチャーのtypesに依存
- 共有ユーティリティがToolMeta, CheatsheetMeta, QuizMetaの3型をimport type
- 各フィーチャーにSEO関数を分散させるとサイト全体のSEO一貫性が損なわれるリスクがあるため、修正せず許容。意図的な型依存であることをコメントで明記
共有データの扱い
- kanji-data.json, yoji-data.json, traditional-colors.jsonの3ファイルがdictionaryとgamesの両方から参照
- ただし、gamesはJSONを直接import、dictionaryもJSONを直接importで、互いのドメインロジックには依存なし
- 3つの選択肢を検討: (A) data/を維持、(B) dictionary/内に配置、(C) shared/data/に配置
- 採用: 選択肢A(data/維持)。理由: dictionaryに所有権を置くとgames->dictionaryの不要な依存が生まれるため。両者は独立してJSONを利用しているので、共有リソースとして扱うのが最も正確
src/content/問題
- ownerの元の課題: 「src/content/配下にブログだけがある不自然な構造」
- 計画v2.1ではsrc/content/blog/をそのまま残す計画だったが、再レビュー(v2.1レビュー)でCritical指摘として発見
- 問題点3つ: (1) ownerの根本課題が未解決、(2) src/content/がAstro Content Collectionsの予約ディレクトリと同名でAIが誤認、(3) src/blog/とsrc/content/blog/が共存しより混乱
- 修正(v2.2): src/content/blog/をsrc/blog/content/に移動し、src/content/ディレクトリ自体を廃止
- 影響はblog.tsのBLOG_DIRパス文字列1行の変更のみ(他のファイルはblog.tsの関数経由でアクセスしているため波及なし)
その他の設計上の発見
- 循環依存: 検出されず。全てのフィーチャー間依存は一方向のみ
- 共有層からフィーチャーへの逆依存(ホットスポット): lib/seo.ts, lib/search/build-index.ts, lib/cross-links.ts, lib/feed.ts, lib/feed-memos.ts, components/common/Footer.tsx, components/common/Header.tsxの7箇所
- テストファイルの配置パターン: 100%が__tests__/ディレクトリパターンで完全に一貫
- テスト対象との距離が離れている箇所: app/games/kanji-kanaru/tests/内のテストがcomponents/games/内のコンポーネントをテスト(異なるディレクトリツリー間)
7. 段階的移行の知見: フェーズ分割の方法、移行順序の決め方
フェーズ分割の原則
- 1フェーズ = 1フィーチャーの移行(または1つの論理的作業単位)
- 各フェーズは独立したコミット
- 各フェーズ完了後に必ず5項目を検証: typecheck, test, build, lint, format:check + 旧パス残存チェック
- git mvでファイル移動し、リネームとしてトレーサビリティを保つ
全9フェーズの構成と移行順序
| フェーズ | 対象 | 規模 | 順序の根拠 |
|---|---|---|---|
| 0 | 前準備(AP-3 webShare移動, AP-4 index.ts削除) | 5ファイル | 後続フェーズの前提条件を整理 |
| 1 | games | 約80ファイル | 散在度最悪・改善効果最大を最優先 |
| 2 | tools | 約20ファイル | 既にコロケーション済みでcomponents統合のみ |
| 3 | cheatsheets | 約15ファイル | toolsと同じパターン |
| 4 | quiz | 約20ファイル | lib/quiz/ + components/quiz/の統合 |
| 5 | dictionary | 約30ファイル | 中規模。共有データ参照があるため注意 |
| 6 | blog(+AP-1修正+src/content/廃止) | 約15ファイル+MD35本 | アンチパターン修正とsrc/content/廃止を含む |
| 7 | memos | 約20ファイル | blogの後(cross-links.tsの2段階更新のため) |
| 8 | 最終クリーンアップ・ドキュメント・検証 | ドキュメント作成 | 全体の整合性確認とADR/ガイド作成 |
移行順序を決めた基準
- 散在度と改善効果: gamesが154ファイル/4箇所で最も散在が深刻なため最優先
- 依存関係: cross-links.tsがblogとmemosの両方をインポートしているため、blog(フェーズ6) -> memos(フェーズ7)の順で2段階更新
- build-index.tsの多段階更新: 全7フィーチャーのregistryをインポートするファイルが5フェーズにわたって段階的に更新される点をリスクとして明示し、「各フェーズで該当行のみ更新する」ルールを設定
レビューポイント
- フェーズ1完了後(AP-2のFooter設計変更を含むため設計レビュー必要)
- フェーズ8完了後(最終レビュー: 全体整合性 + ドキュメントレビュー)
- 中間フェーズ(2~7)は機械的リファクタリングのためフェーズごとのレビュー不要
実行結果
- 全9フェーズが計画通りに完了
- 最終状態: 131テストファイル, 1535テスト全パス, 2572ページビルド成功
- 旧パス残存: ゼロ
- 計画からの逸脱: なし
8. Next.js固有の考慮事項: App Router、_プレフィックス、コロケーション戦略
Next.js公式の3つの戦略(公式ドキュメントから)
- 戦略1: appの外にプロジェクトファイルを配置 -- yolos.netの元の構造に最も近い
- 戦略2: appのトップレベルにプロジェクトファイルを配置 -- app/_components/, app/_lib/等を使う
- 戦略3: フィーチャー/ルートごとに分割 -- 最もコロケーションが進んだ形
- 公式は「どの戦略でも良い」「一貫性が最重要」としている
採用パターンと公式戦略の関係
- パターンC(ハイブリッド型)は公式戦略1の自然な発展形
- app/はルーティング専用を維持(page.tsx, layout.tsx, opengraph-image.tsxのみ)
- フィーチャーコードはsrc/直下のフィーチャーディレクトリに配置
_プレフィックス規約
- _components/, _lib/ の先頭アンダースコアはNext.jsの「Private Folders」機能を利用
- Next.jsはアンダースコアで始まるディレクトリをルーティングから除外する
- これにより、app/内に置いた場合でもページとして認識されない
- パターンCではsrc/直下のフィーチャーディレクトリ内で使用(app/外だが一貫性のため統一)
コロケーション戦略上の判断
- app/内にビジネスロジックを置かない方針を維持
- content/はNext.jsの予約ディレクトリではないが、Astroとの誤認を避けるため廃止
- path alias @/* -> ./src/* は変更しない(vitest.configのvite-tsconfig-pathsで自動解決)
- 動的インポート(tools/registry.tsのcomponentImport)は相対パスを使用しているため構造変更の影響なし
- tsconfig.jsonのpaths設定は変更不要
barrel export(index.ts)への注意
- パターンFで検討されたbarrel exportはNext.jsのServer/Client Component境界と相性が悪い
- 実際にlib/dictionary/index.tsが未使用のまま残っていた(AP-4で削除)
- Next.js App Routerでは各ファイルを直接インポートするパターンが推奨される
9. 外部資料の参照: 調査で参照した情報源のURL
Next.js公式
- Project Structure: https://nextjs.org/docs/app/getting-started/project-structure
- Colocation: https://nextjs.org/docs/14/app/building-your-application/routing/colocation
業界ベストプラクティス
- MakerKit Next.js App Router Guide: https://makerkit.dev/blog/tutorials/nextjs-app-router-project-structure
- Robin Wieruch React Folder Structure: https://www.robinwieruch.de/react-folder-structure/
- Feature-Sliced Design公式: https://feature-sliced.design/
- FSD with Next.js: https://feature-sliced.design/docs/guides/tech/with-nextjs
実例・テンプレート
- Next.js Colocation Template: https://github.com/arhamkhnz/next-colocation-template
- Astro Content Collections: https://docs.astro.build/en/guides/content-collections/
コミュニティ記事
- DEV Community Next.js Best Practices: https://dev.to/bajrayejoon/best-practices-for-organizing-your-nextjs-15-2025-53ji
- Wisp CMS Next.js Guide: https://www.wisp.blog/blog/the-ultimate-guide-to-organizing-your-nextjs-15-project-structure
- Clean Architecture vs FSD: https://medium.com/@metastability/clean-architecture-vs-feature-sliced-design-in-next-js-applications-04df25e62690
- FSD Real Project Lessons: https://dev.to/arjunsanthosh/mastering-feature-sliced-design-lessons-from-real-projects-2ida
- Addy Osmani AI Agent Specs: https://addyo.substack.com/p/how-to-write-a-good-spec-for-ai-agents
10. レビューサイクルで発見された問題: 計画のどこに穴があったか
キャンセルされた初回計画サイクル
- 最初の計画はレビューサイクルが不完全なまま、誤ってビルド作業を開始してしまった
- ownerから中断指示を受け、全コードをリセット
- 教訓: 「品質を最優先とし、計画の策定を十分な調査・分析・レビューを経て完了させてからビルド作業に着手すべき」
レビューv2で発見された問題(8件: Major 3 + Minor 5)
Major指摘:
M-1: app/games/tests/のテストファイルのインポートパス更新が未記載
- app/配下のテストファイルは移動対象ではないが、内部のインポートパス(@/components/games/, @/lib/games/)が変わるため更新が必要
- 計画は「src/app/games/内のインポートを更新」と書いていたが、tests/内のテストファイルが含まれることが明示されていなかった
- ビルダーが見落とす恐れがあった
M-2: quiz/tests/にscoring.test.tsの移動が欠落
- テストファイルの具体名が記載されていなかった
- 一貫性のため(他フェーズでは具体名を列挙していたのに)quizだけ省略されていた
M-3: AP-2修正(Footer props化)の設計詳細が不十分
- 「propsで渡す」だけでは、渡すデータの型、layout.tsx側での組み立て方法、依存が移動するだけで解消されないのでは?という懸念が不明確
- 2つの選択肢(layout.tsx経由props vs ハードコード)を比較し、選択理由を明記すべきだった
Minor指摘:
- N-1: build-index.tsが5フェーズにわたって繰り返し修正されるリスク
- N-2: フェーズ3のcheatsheets tests/のファイル数が未記載
- N-3: テストファイル内のインポートパス更新が対象であることの明示
- N-4: searchを共有層に残した理由をADRに記載すべき
- N-5: webShare.test.tsの移動先がフェーズ0に未記載
再レビュー(v2.1レビュー)で発見された問題(Critical 1件 + Minor 2件)
Critical指摘: 9. C-1: src/content/にblogだけが残る問題 -- ownerの核心的課題への対応不足
- これはリファクタリングの発端となった課題そのものが未解決という根本的な問題
- レビュアーが「ownerのメモ(19c9385e4fd)の核心的な課題」を計画と照合して発見
- AIエージェントがsrc/content/を見てAstroプロジェクトだと勘違いするリスクが残存
- 修正はblog.tsのパス文字列1行のみで影響範囲が極めて小さいのに、見落としていた
Minor指摘: 10. N-1: 配置ルール表の更新 + 将来のMarkdownコンテンツ配置ルール追加 11. N-2: 完成の定義に「Astro誤認シグナルの除去」項目追加
レビューサイクル全体から得られた教訓
「なぜやるのか」に立ち返ること: 技術的な計画に集中するあまり、ownerの元の課題意識(src/content/の不自然さ)を見失っていた。最終的にレビュアーがownerの元メモと照合して発見した。
テストファイルは「移動しないが更新が必要」なケースが見落とされやすい: app/配下のテストファイルは移動対象ではないが、テスト内のインポートパスが変わるケースが複数フェーズで発生した。
設計変更の詳細度は「ビルダーが迷わないレベル」まで: AP-2のFooter props化のように、設計の方向性だけでなく型定義、データの組み立て方法、フォールバック挙動まで記載する必要があった。
計画の一貫性チェック: あるフェーズでテストファイルを具体名で列挙したなら、他のフェーズでも同じ詳細度で記載すべき(M-2の指摘)。
レビューの繰り返しが品質を保証: 計画v2 -> レビューv2(Major 3 + Minor 5)-> v2.1 -> 再レビュー(Critical 1 + Minor 2)-> v2.2 -> 全体再レビュー(承認)という3回のレビューサイクルを経て、最終的にCritical/Major/Minor 0件で承認された。
初回のキャンセルが無駄ではなかった: 初回計画をキャンセルしてやり直した結果、深層調査2件(626ファイルの完全分析、6パターン比較)が実施され、はるかに高品質な計画が策定された。
補足: 生成されたドキュメント
リファクタリング完了後に以下2つのドキュメントが作成された:
- docs/architecture-decision.md: 採用パターンの根拠、不採用パターンの理由、ディレクトリ構造、配置ルール、依存ルール、例外事項、searchを共有層に残した理由、src/content/廃止理由、将来のスケーラビリティ
- docs/new-feature-guide.md: コード配置の判断フロー、新フィーチャー追加手順、ゲーム追加テンプレート、ツール追加テンプレート、Markdownコンテンツ配置ルール、共有コンポーネント追加ルール
参照メモ一覧
| メモID | 種別 | 内容 |
|---|---|---|
| 19c9385e4fd | ownerの指示 | チェックリスト不正への再発防止 + ディレクトリ構造見直し指示 |
| 19c975fe6c1 | キャンセル通知 | 品質を理由に初回計画をキャンセル |
| 19c9761288b | 調査依頼 | 深層調査1: アーキテクチャパターン比較 |
| 19c976845ac | 調査結果 | 深層調査1結果: 6パターンの比較分析(最重要) |
| 19c97618a9f | 調査依頼 | 深層調査2: 依存関係分析 |
| 19c9767ad5f | 調査結果 | 深層調査2結果: 依存関係の深層分析 |
| 19c9772c0a3 | レビュー結果 | レビューv2: Major 3 + Minor 5 |
| 19c97779e81 | 計画 | 計画v2.1: 全指摘反映版 |
| 19c977adbaf | レビュー結果 | 再レビュー: Critical 1(src/content/問題) |
| 19c977e9ac8 | 計画差分 | 計画v2.2: src/content/問題の修正 |
| 19c978369d6 | ビルド依頼 | フェーズ0依頼 |
| 19c979c3d1a | 完了報告 | フェーズ1完了: games移行 |
| 19c97a3b152 | レビュー結果 | フェーズ1レビュー: 承認 |
| 19c97e4ad0c | レビュー依頼 | 最終レビュー依頼 |
| 19c97ed44fa | レビュー結果 | 最終レビュー: 全14項目PASS、承認 |