ブログ機能
AK²Engine のブログシステム — データ構造・コレクション・フィルター・ページネーションの完全ガイドです。
概要
AK²Engine は Eleventy のコレクション・フィルター・ページネーション機能を活用した静的ブログシステムを提供します。記事はファイルベースで管理され、カテゴリ・タグ・アーカイブなどの分類はビルド時に自動生成されます。
ブログ記事のデータ構造
フロントマター
各ブログ記事の .md ファイルには以下のフロントマターを記述します。
---
title: "AK²Engine v0.1.4 リリースノート"
date: 2026-03-08
postCategory: "お知らせ"
postTags:
- ak2engine
- eleventy
---
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
title |
文字列 | ○ | 記事タイトル |
date |
日付 | ○ | 公開日(YYYY-MM-DD)。コレクションのソート・アーカイブに使用 |
postCategory |
文字列 | — | カテゴリ名(日本語)。categories.json でスラッグに変換 |
postTags |
配列 | — | タグスラッグの配列。postTagNames.json で表示名に変換 |
必要なデータファイル
ブログ機能を使用するには、サイトの _data/ ディレクトリに以下の 2 ファイルが必要です。
_data/categories.json — カテゴリ名とスラッグの対応表:
{
"お知らせ": "news",
"技術メモ": "tech",
"デザイン": "design"
}
_data/postTagNames.json — タグスラッグと表示名の対応表:
{
"eleventy": "Eleventy",
"css": "CSS",
"javascript": "JavaScript",
"ak2engine": "AK²Engine"
}
ディレクトリ構造
ブログ記事はパスベースのコレクションで管理されます。ブログ専用ディレクトリ内の .md ファイルが自動的にブログ記事として認識されます。
src/
├── blog/
│ ├── first-post.md
│ ├── second-post.md
│ └── ...
├── _data/
│ ├── categories.json
│ └── postTagNames.json
コレクション
.eleventy.js の addCollection で事前計算されたコレクション群です。テンプレートから collections.コレクション名 でアクセスできます。
基本コレクション
| コレクション名 | 説明 | データ構造 |
|---|---|---|
blogAll |
全ブログ記事(フィルタなし) | Eleventy ページオブジェクトの配列 |
blog |
日付付き記事(新しい順) | Eleventy ページオブジェクトの配列(date でソート) |
分類用コレクション
| コレクション名 | 説明 | データ構造 |
|---|---|---|
blogCategorySlugs |
全カテゴリスラッグ一覧 | Set<string> → 配列 |
blogTagSlugs |
全タグスラッグ一覧 | Set<string> → 配列 |
blogArchiveMonths |
年月一覧(降順) | ["2026/03", "2026/02", ...] |
集計済みコレクション
これらはテンプレートでの算術演算を避けるため、.eleventy.js で事前に集計されたデータです(ルール 14 参照)。
blogArchiveData — 年月階層データ:
[
{
year: "2026",
totalCount: 5,
months: [
{ month: "03", count: 3 },
{ month: "02", count: 2 }
]
}
]
blogCategoryData — カテゴリ集計データ(件数降順):
[
{ name: "お知らせ", slug: "news", count: 3 },
{ name: "技術メモ", slug: "tech", count: 2 }
]
blogTagData — タグ集計データ(件数降順):
[
{ name: "Eleventy", slug: "eleventy", count: 4 },
{ name: "CSS", slug: "css", count: 2 }
]
フィルター
.eleventy.js の addFilter で定義されたフィルター群です。テンプレートから | フィルター名 で使用できます。
日付フィルター
| フィルター | 入力 | 出力 | 使用例 |
|---|---|---|---|
dateFormat |
Date オブジェクト | YYYY.MM.DD |
{{ post.date | dateFormat }} |
dateIso |
Date オブジェクト | YYYY-MM-DD |
{{ post.date | dateIso }} |
dateYearMonth |
Date オブジェクト | YYYY/MM |
{{ post.date | dateYearMonth }} |
カテゴリ・タグフィルター
| フィルター | 入力 | 出力 | 使用例 |
|---|---|---|---|
catSlug |
カテゴリ名(日本語) | スラッグ文字列 | {{ "お知らせ" | catSlug }} → "news" |
catName |
スラッグ | カテゴリ名(日本語) | {{ "news" | catName }} → "お知らせ" |
tagName |
タグスラッグ | タグ表示名 | {{ "eleventy" | tagName }} → "Eleventy" |
ユーティリティフィルター
| フィルター | 入力 | 出力 | 使用例 |
|---|---|---|---|
isArray |
任意の値 | 真偽値 | {% if value | isArray %} |
head |
配列, 件数 | 先頭 N 件の配列 | {{ posts | head(5) }} |
カレンダーフィルター
| フィルター | 入力 | 出力 |
|---|---|---|
buildCalendar |
"YYYY/MM" 文字列, 記事コレクション |
カレンダーデータオブジェクト |
buildCalendar は月間カレンダーグリッドを構築する特殊フィルターです。
{% set calData = "2026/03" | buildCalendar(collections.blog) %}
返却されるオブジェクト:
{
year: "2026",
month: "03",
title: "2026年3月",
weeks: [
// 各週は7要素の配列(日曜〜土曜)
[null, null, null, null, null, null, {day: 1, link: null}],
[{day: 2, link: null}, {day: 3, link: "/blog/sample/"}, ...],
// ...
]
}
null: 月外の空セル{day, link}: 日付セル。linkは当日に記事があればその URL、なければnull
ページネーション
Eleventy の Pagination 機能を使い、カテゴリ別・タグ別・アーカイブ別のリストページを自動生成できます。
カテゴリ別ページ
---
pagination:
data: collections.blogCategorySlugs
size: 1
alias: catSlug
permalink: "/blog/category/{{ catSlug }}/"
---
タグ別ページ
---
pagination:
data: collections.blogTagSlugs
size: 1
alias: tagSlug
permalink: "/blog/tag/{{ tagSlug }}/"
---
アーカイブ別ページ(年月)
---
pagination:
data: collections.blogArchiveMonths
size: 1
alias: yearMonth
permalink: "/blog/archive/{{ yearMonth }}/"
---
.eleventy.js での設定例
ブログ機能に必要なコレクションとフィルターの実装例です。
module.exports = function(eleventyConfig) {
const categories = require('./_data/categories.json');
const tagNames = require('./_data/postTagNames.json');
// ---------- コレクション ----------
// 全ブログ記事(日付順)
eleventyConfig.addCollection("blog", function(collectionApi) {
return collectionApi.getAll()
.filter(p => p.inputPath.startsWith('./src/blog/') && p.inputPath.endsWith('.md'))
.filter(p => p.data.date)
.sort((a, b) => b.data.date - a.data.date);
});
// カテゴリスラッグ一覧
eleventyConfig.addCollection("blogCategorySlugs", function(collectionApi) {
const slugs = new Set();
collectionApi.getAll()
.filter(p => p.inputPath.startsWith('./src/blog/') && p.data.postCategory)
.forEach(p => {
const slug = categories[p.data.postCategory];
if (slug) slugs.add(slug);
});
return [...slugs];
});
// カテゴリ集計データ
eleventyConfig.addCollection("blogCategoryData", function(collectionApi) {
const map = {};
collectionApi.getAll()
.filter(p => p.inputPath.startsWith('./src/blog/') && p.data.postCategory)
.forEach(p => {
const name = p.data.postCategory;
const slug = categories[name];
if (!slug) return;
if (!map[slug]) map[slug] = { name, slug, count: 0 };
map[slug].count++;
});
return Object.values(map).sort((a, b) => b.count - a.count);
});
// アーカイブ集計データ
eleventyConfig.addCollection("blogArchiveData", function(collectionApi) {
const yearMap = {};
collectionApi.getAll()
.filter(p => p.inputPath.startsWith('./src/blog/') && p.data.date)
.forEach(p => {
const d = new Date(p.data.date);
const y = String(d.getFullYear());
const m = String(d.getMonth() + 1).padStart(2, '0');
if (!yearMap[y]) yearMap[y] = {};
yearMap[y][m] = (yearMap[y][m] || 0) + 1;
});
return Object.keys(yearMap).sort().reverse().map(year => ({
year,
totalCount: Object.values(yearMap[year]).reduce((a, b) => a + b, 0),
months: Object.keys(yearMap[year]).sort().reverse()
.map(month => ({ month, count: yearMap[year][month] }))
}));
});
// ---------- フィルター ----------
eleventyConfig.addFilter("dateFormat", d => {
const dt = new Date(d);
const y = dt.getFullYear();
const m = String(dt.getMonth() + 1).padStart(2, '0');
const day = String(dt.getDate()).padStart(2, '0');
return `${y}.${m}.${day}`;
});
eleventyConfig.addFilter("catSlug", name => categories[name] || name);
eleventyConfig.addFilter("catName", slug => {
for (const [name, s] of Object.entries(categories)) {
if (s === slug) return name;
}
return slug;
});
eleventyConfig.addFilter("tagName", slug => tagNames[slug] || slug);
};
前のページ: ← ベストプラクティス | ↑ 目次 | 次のページ: ブログウィジェット →