Jekyll 静态站点完全教程 / 第8章:页面与集合
第8章:页面与集合
8.1 独立页面(Pages)
独立页面是不属于 _posts/ 的内容文件,如关于页、联系页等。任何包含 Front Matter 的非 Post 文件都是页面。
创建页面
<!-- about.md(根目录)-->
---
title: "关于我"
layout: page
permalink: /about/
---
我是张三,一名 Web 开发者...
<!-- pages/contact.md(子目录)-->
---
title: "联系方式"
layout: page
permalink: /contact/
---
邮箱:zhangsan@example.com
页面 vs 文章
| 特性 | 页面 (Pages) | 文章 (Posts) |
|---|---|---|
| 目录 | 任意位置 | _posts/ |
| 文件名 | 无限制 | 需含日期 |
| 排序 | 手动指定 | 按日期自动 |
| 分类/标签 | ❌ | ✅ |
| 分页 | ❌ | ✅ |
| 永久链接 | 手动设置 | 自动生成 |
| site.posts | ❌ | ✅ |
| site.pages | ✅ | ✅ |
页面变量
{{ page.title }}
{{ page.url }}
{{ page.path }} <!-- 源文件路径 -->
{{ page.dir }} <!-- 输出目录 -->
{{ page.name }} <!-- 文件名 -->
{{ page.ext }} <!-- 扩展名 -->
{{ page.content }} <!-- 渲染后的 HTML -->
8.2 集合(Collections)
集合(Collections)是 Jekyll 的高级内容类型系统,允许定义除 posts 和 pages 之外的自定义内容类型。
定义集合
# _config.yml
collections:
projects:
output: true # 生成独立页面
permalink: /projects/:name/
team:
output: true
permalink: /team/:name/
faq:
output: false # 不生成独立页面(仅用于数据)
集合目录结构
_projects/
├── project-a.md
├── project-b.md
└── project-c.html
_team/
├── alice.md
└── bob.md
_faq/
├── question-1.md
└── question-2.md
集合内容文件
<!-- _projects/jekyll-blog.md -->
---
title: "Jekyll 博客系统"
description: "基于 Jekyll 的技术博客"
url: "https://blog.example.com"
github: "https://github.com/user/jekyll-blog"
tech_stack: [Jekyll, Ruby, GitHub Pages]
featured: true
order: 1
---
## 项目简介
这是一个使用 Jekyll 构建的技术博客...
访问集合数据
<!-- 遍历集合 -->
{% for project in site.projects %}
<div class="project-card">
<h3><a href="{{ project.url | relative_url }}">{{ project.title }}</a></h3>
<p>{{ project.description }}</p>
<div class="tech-stack">
{% for tech in project.tech_stack %}
<span class="badge">{{ tech }}</span>
{% endfor %}
</div>
{% if project.github %}
<a href="{{ project.github }}">GitHub</a>
{% endif %}
</div>
{% endfor %}
<!-- 排序集合 -->
{% assign sorted = site.projects | sort: "order" %}
{% for project in sorted %}
{{ project.title }}
{% endfor %}
<!-- 筛选集合 -->
{% assign featured = site.projects | where: "featured", true %}
集合与文档的生命周期
<!-- 集合中的每个文档有完整的 Page 属性 -->
{% for doc in site.projects %}
{{ doc.url }} <!-- 生成的 URL -->
{{ doc.content }} <!-- 渲染后的 HTML -->
{{ doc.path }} <!-- 源文件路径 -->
{{ doc.date }} <!-- 文件修改时间或 Front Matter 日期 -->
{{ doc.title }} <!-- Front Matter 中的 title -->
{{ doc.output }} <!-- 输出的 HTML(仅内部使用) -->
{% endfor %}
8.3 数据文件(Data Files)
数据文件存放在 _data/ 目录,支持 YAML、JSON、CSV 格式,可在模板中通过 site.data 访问。
目录结构
_data/
├── navigation.yml
├── authors.yml
├── team.json
└── products.csv
YAML 数据文件
# _data/navigation.yml
main:
- title: "首页"
url: "/"
- title: "博客"
url: "/blog/"
- title: "项目"
url: "/projects/"
- title: "关于"
url: "/about/"
footer:
- title: "法律信息"
items:
- title: "隐私政策"
url: "/privacy/"
- title: "使用条款"
url: "/terms/"
- title: "社交媒体"
items:
- title: "GitHub"
url: "https://github.com/user"
- title: "Twitter"
url: "https://twitter.com/user"
# _data/authors.yml
zhangsan:
name: "张三"
avatar: "/images/avatars/zhangsan.jpg"
bio: "全栈开发者"
social:
github: "https://github.com/zhangsan"
twitter: "https://twitter.com/zhangsan"
lisi:
name: "李四"
avatar: "/images/avatars/lisi.jpg"
bio: "前端工程师"
social:
github: "https://github.com/lisi"
JSON 数据文件
// _data/team.json
[
{
"name": "Alice",
"role": "Developer",
"skills": ["Ruby", "JavaScript"],
"active": true
},
{
"name": "Bob",
"role": "Designer",
"skills": ["Figma", "CSS"],
"active": true
}
]
CSV 数据文件
# _data/products.csv
name,price,category,in_stock
Widget A,29.99,electronics,true
Widget B,49.99,electronics,false
Gadget C,19.99,accessories,true
在模板中使用数据文件
<!-- 导航菜单 -->
<nav>
{% for item in site.data.navigation.main %}
<a href="{{ item.url | relative_url }}"
{% if page.url == item.url %}class="active"{% endif %}>
{{ item.title }}
</a>
{% endfor %}
</nav>
<!-- 作者信息 -->
{% assign author = site.data.authors[page.author] %}
{% if author %}
<div class="author-box">
<img src="{{ author.avatar }}" alt="{{ author.name }}">
<span>{{ author.name }}</span>
<p>{{ author.bio }}</p>
</div>
{% endif %}
<!-- 团队成员 -->
{% for member in site.data.team %}
{% if member.active %}
<div class="team-member">
<h3>{{ member.name }}</h3>
<p>{{ member.role }}</p>
<ul>
{% for skill in member.skills %}
<li>{{ skill }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endfor %}
<!-- 产品表格 -->
<table>
<thead>
<tr><th>名称</th><th>价格</th><th>库存</th></tr>
</thead>
<tbody>
{% for product in site.data.products %}
<tr>
<td>{{ product.name }}</td>
<td>¥{{ product.price }}</td>
<td>{% if product.in_stock == "true" %}✅{% else %}❌{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
嵌套数据文件
_data/
├── books/
│ ├── fiction.yml
│ └── nonfiction.yml
└── settings/
└── theme.yml
<!-- 访问嵌套数据 -->
{% for book in site.data.books.fiction %}
{{ book.title }}
{% endfor %}
{{ site.data.settings.theme.primary_color }}
8.4 静态文件
静态文件是不被 Jekyll 处理的文件,直接复制到 _site/。
静态文件目录
assets/
├── css/
│ └── main.css
├── js/
│ └── app.js
└── images/
├── logo.png
└── blog/
└── post-1.jpg
static/
├── robots.txt
├── favicon.ico
└── sitemap.xml
访问静态文件
<!-- 引用静态文件 -->
<link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
<script src="{{ '/assets/js/app.js' | relative_url }}"></script>
<img src="{{ '/assets/images/logo.png' | relative_url }}" alt="Logo">
静态文件变量
<!-- 遍历静态文件 -->
{% for file in site.static_files %}
{{ file.path }} <!-- 文件路径 -->
{{ file.name }} <!-- 文件名 -->
{{ file.extname }} <!-- 扩展名 -->
{{ file.modified_time }} <!-- 修改时间 -->
{% endfor %}
<!-- 筛选特定类型文件 -->
{% assign images = site.static_files | where: "extname", ".png" %}
8.5 导航系统
多级导航
# _data/navigation.yml
main:
- title: "首页"
url: "/"
- title: "博客"
url: "/blog/"
children:
- title: "归档"
url: "/archive/"
- title: "分类"
url: "/categories/"
- title: "标签"
url: "/tags/"
- title: "项目"
url: "/projects/"
- title: "文档"
url: "/docs/"
children:
- title: "入门"
url: "/docs/getting-started/"
- title: "进阶"
url: "/docs/advanced/"
<!-- _includes/nav.html -->
<nav class="main-nav">
<ul>
{% for item in site.data.navigation.main %}
<li {% if item.children %}class="has-dropdown"{% endif %}>
<a href="{{ item.url | relative_url }}"
{% if page.url == item.url %}class="active"{% endif %}>
{{ item.title }}
</a>
{% if item.children %}
<ul class="dropdown">
{% for child in item.children %}
<li>
<a href="{{ child.url | relative_url }}"
{% if page.url == child.url %}class="active"{% endif %}>
{{ child.title }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
面包屑导航
<!-- _includes/breadcrumb.html -->
{% assign crumbs = page.url | split: '/' %}
<nav class="breadcrumb" aria-label="breadcrumb">
<ol>
<li><a href="{{ '/' | relative_url }}">首页</a></li>
{% for crumb in crumbs %}
{% if crumb != '' %}
{% assign crumb_url = crumbs | slice: 0, forloop.index | join: '/' | prepend: '/' %}
{% if forloop.last %}
<li aria-current="page">{{ page.title }}</li>
{% else %}
<li><a href="{{ crumb_url | relative_url }}">{{ crumb | replace: '-', ' ' | capitalize }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
</ol>
</nav>
8.6 多语言支持
Jekyll 没有内置多语言支持,需要手动实现。
方案一:基于 URL 前缀
# _config.yml
collections:
posts_en:
output: true
permalink: /en/blog/:year/:month/:day/:title/
posts_zh:
output: true
permalink: /zh/blog/:year/:month/:day/:title/
_posts_en/
├── 2025-01-15-hello.md
_posts_zh/
├── 2025-01-15-ni-hao.md
<!-- _posts_en/2025-01-15-hello.md -->
---
title: "Hello World"
lang: en
layout: post
---
Welcome to my blog!
<!-- _posts_zh/2025-01-15-ni-hao.md -->
---
title: "你好世界"
lang: zh
layout: post
---
欢迎来到我的博客!
方案二:使用 jekyll-polyglot 插件
# Gemfile
gem "jekyll-polyglot"
# _config.yml
languages: ["en", "zh"]
default_lang: "en"
exclude_from_localization: ["assets", "images"]
parallel_localization: true
<!-- _posts/2025-01-15-hello.md -->
---
title: "Hello World"
lang: en
permalink: /en/hello/
---
English content here.
<!-- _posts/2025-01-15-hello.zh.md -->
---
title: "你好世界"
lang: zh
permalink: /zh/hello/
---
中文内容在此。
语言切换器
<!-- _includes/language-switcher.html -->
{% assign posts = site.posts | where: "slug", page.slug %}
<div class="lang-switcher">
{% for post in posts %}
<a href="{{ post.url | relative_url }}"
{% if post.lang == page.lang %}class="active"{% endif %}>
{{ post.lang | upcase }}
</a>
{% endfor %}
</div>
8.7 页面元数据管理
SEO 元标签
<!-- _includes/seo.html -->
<meta property="og:title" content="{{ page.title | default: site.title }}">
<meta property="og:description" content="{{ page.description | default: site.description }}">
<meta property="og:url" content="{{ page.url | absolute_url }}">
<meta property="og:type" content="{% if page.layout == 'post' %}article{% else %}website{% endif %}">
{% if page.image %}
<meta property="og:image" content="{{ page.image | absolute_url }}">
{% endif %}
<meta name="twitter:card" content="summary_large_image">
8.8 业务场景:产品文档站点
# _config.yml
collections:
docs:
output: true
permalink: /docs/:path/
defaults:
- scope:
type: docs
values:
layout: docs
sidebar: true
# _docs/getting-started.md
---
title: "快速入门"
order: 1
category: "基础"
---
# 快速入门
本指南帮助你快速上手...
<!-- _layouts/docs.html -->
---
layout: default
---
<div class="docs-layout">
<aside>
{% assign docs = site.docs | sort: "order" %}
{% assign current_category = "" %}
{% for doc in docs %}
{% if doc.category != current_category %}
<h4>{{ doc.category }}</h4>
{% assign current_category = doc.category %}
{% endif %}
<a href="{{ doc.url | relative_url }}"
{% if page.url == doc.url %}class="active"{% endif %}>
{{ doc.title }}
</a>
{% endfor %}
</aside>
<main>
{{ content }}
</main>
</div>
8.9 扩展阅读
本章小结
| 要点 | 说明 |
|---|---|
| 页面 | 任意位置的独立内容文件 |
| 集合 | 自定义内容类型,支持独立 URL 输出 |
| 数据文件 | _data/ 中的 YAML/JSON/CSV 结构化数据 |
| 静态文件 | 不经处理直接复制到输出目录 |
| 导航 | 通过数据文件 + Liquid 模板实现 |
| 多语言 | URL 前缀方案或 polyglot 插件 |
下一章:插件开发