3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Eleventy】静的サイトでDynamic Routesを実現する方法(Pagination + Data Files)

3
Last updated at Posted at 2025-12-22

はじめに

この記事は、エアークローゼットアドベントカレンダー2025の20日目の記事です。

「静的サイトでダイナミックルーティングなんて無理でしょ?」と言われたことはありませんか?

確かに、EleventyのようなStatic Site Generator(SSG)は静的なHTMLファイルを生成します。サーバーサイドレンダリングもなければ、ランタイム時のデータベースクエリもありません。

しかし、ちょっと待ってください——「静的」は「柔軟性がない」という意味ではありません。

今回は、私が「静的サイトでのダイナミックルーティング」と呼んでいるテクニックを紹介します。EleventyのPermalink機能とNunjucks、そしてJSON Data Filesを組み合わせることで実現できます。

結果は?たった1つのテンプレートから、何百、何千ものページを生成でき、データは完全に分離して管理できます。


EleventyとNunjucksとは?

Eleventy(11ty)

Eleventyは、JavaScriptで書かれたStatic Site Generator(SSG)です。シンプルで高速、そしてReactやVueなどのフロントエンドフレームワークを強制しないのが特徴です。

  • ゼロコンフィグですぐに使える
  • 複数のテンプレート言語に対応
  • ビルドが非常に高速
  • 出力は純粋なHTML、不要なJavaScriptを含まない

Nunjucks

Nunjucksは、Mozillaが開発したテンプレートエンジンで、PythonのJinja2にインスパイアされています。テンプレート継承、マクロ、フィルター、ループなどの強力な機能を備えています。

Eleventy + Nunjucksは人気の組み合わせです。Nunjucksの構文は読みやすく、静的ページ生成に便利な機能が豊富だからです。


🎯 実際のユースケース

50件のプロジェクトを持つポートフォリオサイトを構築するとします。各プロジェクトには個別のページが必要です:

/projects/sunrise-cafe/
/projects/tokyo-tower/
/projects/minimalist-apartment/
...

「従来の」方法(つらい)

src/
  projects/
    sunrise-cafe/index.njk
    tokyo-tower/index.njk
    minimalist-apartment/index.njk
    ... (あと47ファイル 😱)

問題点: レイアウトを変更したい?50ファイル全部修正。新しいフィールドを追加したい?50ファイル全部修正。悪夢です。

「スマートな」方法:Permalink + Data Files

src/
  _data/
    projects.json      ← すべてのデータはここ
  projects/
    project.njk        ← テンプレートは1つだけ

1つのデータファイル。1つのテンプレート。無限のページ。


🏗️ プロジェクト構成

eleventy-dynamic-routing/
├── src/
│   ├── _data/
│   │   └── projects.json
│   └── projects/
│       ├── index.njk      (一覧ページ)
│       └── project.njk    (各プロジェクトのテンプレート)
├── .eleventy.js
└── package.json

📦 Data File:システムの心臓部

src/_data/projects.jsonを作成します:

{
  "items": [
    {
      "slug": "sunrise-cafe",
      "title": "Sunrise Café",
      "category": "インテリアデザイン",
      "year": 2025,
      "location": "東京"
    },
    {
      "slug": "tokyo-tower",
      "title": "東京タワー リニューアル",
      "category": "建築",
      "year": 2024,
      "location": "東京"
    },
    {
      "slug": "minimalist-apartment",
      "title": "The White Canvas Apartment",
      "category": "住宅",
      "year": 2025,
      "location": "大阪"
    }
  ]
}

100件のプロジェクトを追加したい?items配列に100個のオブジェクトを追加するだけ。以上。


✨ Permalinkの魔法:1つのテンプレートで複数ページ

最も重要なファイル——src/projects/project.njk

---
pagination:
  data: projects.items
  size: 1
  alias: project
permalink: "projects/{{ project.slug }}/index.html"
---

<h1>{{ project.title }}</h1>
<p>カテゴリー: {{ project.category }}</p>
<p>所在地: {{ project.location }}</p>
<p>年: {{ project.year }}</p>

<a href="/projects/">← プロジェクト一覧に戻る</a>

これだけです!Eleventyが自動的に以下を生成します:

  • _site/projects/sunrise-cafe/index.html
  • _site/projects/tokyo-tower/index.html
  • _site/projects/minimalist-apartment/index.html

🔍 Front Matterの解説

---
pagination:
  data: projects.items    # _data/projects.jsonから読み込み
  size: 1                 # 1アイテム = 1ページ
  alias: project          # テンプレート内での変数名
permalink: "projects/{{ project.slug }}/index.html"
---
プロパティ 意味
data データソース、_data/projects.jsonから自動マッピング
size: 1 配列内の各オブジェクト = 個別ページ
alias テンプレートで使う変数名を定義
permalink これが「ルーター」! テンプレート文字列で動的URLを生成

📋 一覧ページ

すべてのプロジェクトを表示するsrc/projects/index.njkを作成:

---
title: プロジェクト一覧
---

<h1>プロジェクト一覧</h1>
<p>合計: {{ projects.items.length }}</p>

<ul>
{% for project in projects.items %}
  <li>
    <a href="/projects/{{ project.slug }}/">
      {{ project.title }}{{ project.year }}</a>
  </li>
{% endfor %}
</ul>

🚀 応用編:カテゴリー別ルート

src/_data/categories.jsonを追加:

[
  { "name": "インテリアデザイン", "slug": "interior-design" },
  { "name": "建築", "slug": "architecture" },
  { "name": "住宅", "slug": "residential" }
]

そしてsrc/categories/category.njk

---
pagination:
  data: categories
  size: 1
  alias: category
permalink: "category/{{ category.slug }}/index.html"
---

<h1>{{ category.name }}</h1>

<ul>
{% for project in projects.items %}
  {% if project.category == category.name %}
    <li>
      <a href="/projects/{{ project.slug }}/">{{ project.title }}</a>
    </li>
  {% endif %}
{% endfor %}
</ul>

これで以下のルートも追加されます:

  • /category/interior-design/
  • /category/architecture/
  • /category/residential/

🎨 関心の分離(Separation of Concerns)

┌─────────────────────────────────────────────────────┐
│                    従来の方法                        │
├─────────────────────────────────────────────────────┤
│  project-1.md   project-2.md   project-3.md         │
│  (データ+レイアウト) (データ+レイアウト) (データ+レイアウト)   │
│                                                     │
│  → レイアウト変更 = 全ファイル修正                    │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│                    新しい方法                        │
├─────────────────────────────────────────────────────┤
│  projects.json          project.njk                 │
│  (データのみ)     →     (レイアウトのみ)              │
│                                                     │
│  → レイアウト変更 = 1ファイルだけ                     │
│  → データ追加 = JSONに追加                           │
│  → コンテンツ担当はJSON、開発者はテンプレートを編集     │
└─────────────────────────────────────────────────────┘

メリット

やりたいこと 従来の方法 Data Filesを使う場合
100件追加 100ファイル作成 JSONに追加
レイアウト変更 100ファイル修正 1テンプレート修正
CMSからインポート 複雑 JSONエクスポート

⚠️ 注意:ルートの競合について

問題

もし物理的なファイル/フォルダがDynamic Routeと同じパスを持つ場合、ビルドエラーが発生します。

例えば、以下の状況:

src/
  projects/
    project.njk              ← slug: "sunrise-cafe" を生成
    sunrise-cafe/            ← 同じパスの物理フォルダが存在!
      index.njk

この場合、両方とも /projects/sunrise-cafe/ を出力しようとして競合します。

解決方法

パターン1:物理ファイルを優先する場合

物理ファイル側で明示的にpermalinkを設定すれば、そちらが優先されます:

<!-- src/projects/sunrise-cafe/index.njk -->
---
permalink: "projects/sunrise-cafe/index.html"
---

<h1>カスタムページ(物理ファイル優先)</h1>

パターン2:JSONデータを優先する場合

物理ファイルの出力を無効にするには、permalink: falseを設定:

<!-- src/projects/sunrise-cafe/index.njk -->
---
permalink: false
---

これで物理ファイルは出力されず、JSONデータからのページが生成されます。

ベストプラクティス: 物理ファイルとJSONデータのslugが重複しないように命名規則を決めておくことをおすすめします。


🏁 まとめ

Eleventyは「静的」かもしれませんが、Pagination + Permalink + Data Filesを使えば、ダイナミックルーティングのような柔軟性を実現できます。

ポイント:

  1. 1テンプレートで無限ページpaginationsize: 1を指定
  2. Permalinkが「ルーター」 — テンプレート文字列で動的URL生成
  3. JSONが「データベース」 — 編集・管理が簡単
  4. 関心の分離 — データとコードを完全に分離

エアークローゼット Advent Calendar 2025はまだまだ続きますので、ぜひ他のエンジニア, デザイナー, PMの記事もご覧いただければと思います


📚 参考資料

3
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?