强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 的高级内容类型系统,允许定义除 postspages 之外的自定义内容类型。

定义集合

# _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 插件

下一章:插件开发