Jekyll 静态站点完全教程 / 第7章:文章管理
第7章:文章管理
7.1 文章基础
Jekyll 的文章(Posts)是其博客功能的核心。文章存放在 _posts/ 目录下,文件名必须遵循特定的日期格式。
文件命名规则
YYYY-MM-DD-title.ext
│ │ │ │ │
│ │ │ │ └── 扩展名:.md / .markdown / .html / .textile
│ │ │ └──────── 标题 slug(URL 友好格式)
│ │ └─────────── 日
│ └────────────── 月
└─────────────────── 年
# 正确的文件名
_posts/2025-01-15-hello-world.md
_posts/2025-06-01-jekyll-tips.md
_posts/2024-12-25-merry-christmas.markdown
# 错误的文件名(不会被识别为文章)
_posts/hello-world.md # 缺少日期
_posts/2025_01_15_hello.md # 使用下划线而非连字符
_posts/15-01-2025-hello.md # 日期格式错误
注意事项:
- 文件名中的日期是文章日期的后备值
- 如果 Front Matter 中有
date,优先使用 Front Matter 的日期 - slug 部分会用于生成 URL
文章文件结构
---
title: "我的第一篇文章"
date: 2025-01-15 10:30:00 +0800
layout: post
categories: [技术, Web开发]
tags: [jekyll, ruby, tutorial]
description: "这篇文章介绍 Jekyll 的基础知识"
author: "张三"
excerpt_separator:
---
这是文章的摘要部分。
<!--more-->
这是文章的完整内容。
7.2 日期处理
日期变量
| 变量 | 来源 | 说明 |
|---|
page.date | Front Matter date 或文件名 | 文章发布日期 |
site.time | 构建时间 | 每次构建时更新 |
page.last_modified_at | Front Matter | 最后修改时间(需插件) |
日期格式化
<!-- 常用日期格式 -->
{{ page.date | date: "%Y-%m-%d" }} <!-- 2025-01-15 -->
{{ page.date | date: "%Y年%m月%d日" }} <!-- 2025年01月15日 -->
{{ page.date | date: "%B %d, %Y" }} <!-- January 15, 2025 -->
{{ page.date | date: "%Y-%m-%dT%H:%M:%S%z" }} <!-- ISO 8601 -->
{{ page.date | date: "%A" }} <!-- Wednesday -->
<!-- 相对时间(需自定义过滤器或插件) -->
{{ page.date | timeago }} <!-- 3 天前 -->
时区处理
# _config.yml
timezone: Asia/Shanghai
# 时区影响:
# 1. 文件名中的日期按此 timezone 解析
# 2. Front Matter 中的日期按此 timezone 解析
# 3. site.time 按此 timezone 生成
按年月归档
<!-- 按年分组 -->
{% assign posts_by_year = site.posts | group_by_exp: "post", "post.date | date: '%Y'" %}
{% for year in posts_by_year %}
<h2>{{ year.name }} 年</h2>
<ul>
{% for post in year.items %}
<li>{{ post.date | date: "%m月%d日" }} - {{ post.title }}</li>
{% endfor %}
</ul>
{% endfor %}
<!-- 按年月分组 -->
{% assign posts_by_month = site.posts | group_by_exp: "post", "post.date | date: '%Y-%m'" %}
7.3 永久链接(Permalink)
永久链接定义了文章的 URL 结构。
预设样式
| 样式 | URL 模板 | 示例 |
|---|
date | /:categories/:year/:month/:day/:title.html | /2025/01/15/hello.html |
pretty | /:categories/:year/:month/:day/:title/ | /2025/01/15/hello/ |
none | /:categories/:title.html | /hello.html |
date_no_time | /:year-:month-:day/:title | /2025-01-15/hello |
自定义 Permalink
# _config.yml 全局设置
permalink: /posts/:year/:month/:title/
# 文章级覆盖
---
permalink: /blog/my-custom-url/
---
Permalink 变量
| 变量 | 说明 | 示例 |
|---|
:year | 4位年份 | 2025 |
:month | 2位月份 | 01 |
:day | 2位日期 | 15 |
:short_year | 2位年份 | 25 |
:title | 文件名中的标题部分 | hello-world |
:slug | 标题的小写连字符格式 | hello-world |
:categories | 第一个分类 | tech |
:i_day | 无前导零的日期 | 15 |
:i_month | 无前导零的月份 | 1 |
常见 Permalink 方案
# 方案1:扁平结构(简单博客)
permalink: /:title/
# 方案2:按年分类
permalink: /:year/:title/
# 方案3:按年月日(传统博客)
permalink: /:year/:month/:day/:title/
# 方案4:按分类
permalink: /:categories/:title/
# 方案5:按年月 + 无斜杠
permalink: /:year-:month/:title/
7.4 分类(Categories)
在 Front Matter 中设置分类
---
title: "Jekyll 入门"
categories: [技术, Web开发]
---
多级分类
# Jekyll 支持层级分类
categories:
- 技术
- Web开发
- 静态站点
# 生成的 URL(如果 permalink 包含 :categories)
# /技术/Web开发/静态站点/hello/
创建分类页面
# _plugins/category_pages.rb
module Jekyll
class CategoryPageGenerator < Generator
safe true
def generate(site)
site.categories.each do |category, posts|
site.pages << CategoryPage.new(site, category, posts)
end
end
end
class CategoryPage < Page
def initialize(site, category, posts)
@site = site
@base = site.source
@dir = File.join('categories', Jekyll::Utils.slugify(category))
@name = 'index.html'
self.process(@name)
self.read_yaml(File.join(site.source, '_layouts'), 'category.html')
self.data['category'] = category
self.data['posts'] = posts
self.data['title'] = "分类:#{category}"
end
end
end
<!-- _layouts/category.html -->
---
layout: default
---
<h1>{{ page.title }}</h1>
<ul>
{% for post in page.posts %}
<li>
<a href="{{ post.url | relative_url }}">{{ post.title }}</a>
<time>{{ post.date | date: "%Y-%m-%d" }}</time>
</li>
{% endfor %}
</ul>
分类存档页面
<!-- categories.md -->
---
layout: page
title: 所有分类
permalink: /categories/
---
{% assign sorted_categories = site.categories | sort %}
{% for category in sorted_categories %}
{% assign cat_name = category[0] %}
{% assign cat_posts = category[1] %}
<h2>{{ cat_name }} ({{ cat_posts.size }})</h2>
<ul>
{% for post in cat_posts %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
{% endfor %}
在 Front Matter 中设置标签
---
tags: [jekyll, ruby, tutorial, beginner]
---
生成标签页面
# _plugins/tag_pages.rb
module Jekyll
class TagPageGenerator < Generator
safe true
def generate(site)
site.tags.each do |tag, posts|
site.pages << TagPage.new(site, tag, posts)
end
end
end
class TagPage < Page
def initialize(site, tag, posts)
@site = site
@base = site.source
@dir = File.join('tags', Jekyll::Utils.slugify(tag))
@name = 'index.html'
self.process(@name)
self.read_yaml(File.join(site.source, '_layouts'), 'tag.html')
self.data['tag'] = tag
self.data['posts'] = posts
self.data['title'] = "标签:#{tag}"
end
end
end
标签云
<!-- _includes/tag-cloud.html -->
<div class="tag-cloud">
{% assign sorted_tags = site.tags | sort %}
{% for tag in sorted_tags %}
{% assign tag_name = tag[0] %}
{% assign tag_count = tag[1].size %}
{% assign font_size = tag_count | times: 0.3 | plus: 0.8 %}
<a href="{{ '/tags/' | append: tag_name | relative_url }}"
style="font-size: {{ font_size }}em"
class="tag-cloud-item">
{{ tag_name }} ({{ tag_count }})
</a>
{% endfor %}
</div>
7.6 草稿(Drafts)
使用 _drafts 目录
# 草稿放在 _drafts/ 目录,文件名不需要日期
_drafts/my-new-post.md
# 启动时包含草稿
bundle exec jekyll serve --drafts
# 草稿使用文件的修改时间作为日期
草稿 Front Matter
<!-- _drafts/work-in-progress.md -->
---
title: "正在写作的文章"
layout: post
categories: [技术]
tags: [draft]
---
这篇文章尚未完成...
published: false 替代方案
# 在 _posts/ 中隐藏文章(不生成、不出现在 site.posts)
---
title: "隐藏文章"
published: false
---
| 方式 | 文件位置 | 构建输出 | site.posts |
|---|
_drafts/ + --drafts | _drafts/ | ✅ 本地预览 | ✅ |
_drafts/ 无 --drafts | _drafts/ | ❌ | ❌ |
published: false | _posts/ | ❌ | ❌ |
安装分页插件
# Gemfile
gem "jekyll-paginate"
# _config.yml
plugins:
- jekyll-paginate
paginate: 5 # 每页文章数
paginate_path: "/page:num/" # 分页 URL 模板
# 输出: /, /page2/, /page3/, ...
分页模板
<!-- index.html -->
---
layout: home
---
{% for post in paginator.posts %}
<article class="post-summary">
<h2><a href="{{ post.url | relative_url }}">{{ post.title }}</a></h2>
<time>{{ post.date | date: "%Y-%m-%d" }}</time>
<p>{{ post.excerpt | strip_html | truncatewords: 30 }}</p>
</article>
{% endfor %}
<!-- 分页导航 -->
{% if paginator.total_pages > 1 %}
<nav class="pagination">
{% if paginator.previous_page %}
<a href="{{ paginator.previous_page_path | relative_url }}" class="prev">← 上一页</a>
{% else %}
<span class="prev disabled">← 上一页</span>
{% endif %}
{% for page in (1..paginator.total_pages) %}
{% if page == paginator.page %}
<span class="current">{{ page }}</span>
{% elsif page == 1 %}
<a href="{{ '/' | relative_url }}">{{ page }}</a>
{% else %}
<a href="{{ site.paginate_path | replace: ':num', page | relative_url }}">{{ page }}</a>
{% endif %}
{% endfor %}
{% if paginator.next_page %}
<a href="{{ paginator.next_page_path | relative_url }}" class="next">下一页 →</a>
{% else %}
<span class="next disabled">下一页 →</span>
{% endif %}
</nav>
{% endif %}
分页对象
| 属性 | 说明 |
|---|
paginator.page | 当前页码 |
paginator.per_page | 每页文章数 |
paginator.posts | 当前页文章列表 |
paginator.total_posts | 文章总数 |
paginator.total_pages | 总页数 |
paginator.previous_page | 上一页页码(nil if 第一页) |
paginator.previous_page_path | 上一页路径 |
paginator.next_page | 下一页页码(nil if 最后一页) |
paginator.next_page_path | 下一页路径 |
注意事项:
jekyll-paginate 只支持首页分页,不能为分类/标签分页- 如需多处分页,使用
jekyll-paginate-v2(不兼容 GitHub Pages) - 分页只对
_posts/ 中的文章有效
7.8 文章摘要
excerpt_separator
# _config.yml 全局设置
excerpt_separator: <!--more-->
---
title: "文章标题"
---
这是摘要部分,会显示在列表中。
<!--more-->
这是正文的其余部分,只在文章详情页显示。
在模板中使用摘要
<!-- 自动摘要(使用 excerpt_separator) -->
{{ post.excerpt }}
<!-- 手动截断 -->
{{ post.content | strip_html | truncatewords: 50 }}
<!-- 自定义摘要 -->
{{ post.description }}
<!-- 移除摘要中的链接 -->
{{ post.excerpt | strip_html }}
7.9 文章排序
<!-- 按日期排序(默认:最新在前) -->
{% for post in site.posts %}
<!-- 按日期正序 -->
{% assign sorted = site.posts | sort: "date" %}
{% for post in sorted %}
<!-- 按自定义 weight 排序 -->
{% assign sorted = site.posts | sort: "weight" %}
{% for post in sorted %}
<!-- 多条件排序 -->
{% assign sorted = site.posts | sort_natural: "category" | sort: "date" | reverse %}
7.10 上下篇导航
<!-- _includes/post-navigation.html -->
<nav class="post-navigation">
{% if page.previous.url %}
<a href="{{ page.previous.url | relative_url }}" class="prev">
<span class="nav-label">← 上一篇</span>
<span class="nav-title">{{ page.previous.title }}</span>
</a>
{% endif %}
{% if page.next.url %}
<a href="{{ page.next.url | relative_url }}" class="next">
<span class="nav-label">下一篇 →</span>
<span class="nav-title">{{ page.next.title }}</span>
</a>
{% endif %}
</nav>
7.11 业务场景:技术博客
完整文章模板
---
title: "深入理解 Jekyll 永久链接"
date: 2025-01-15 10:30:00 +0800
last_modified_at: 2025-02-01
layout: post
permalink: /blog/jekyll-permalink-deep-dive/
categories: [技术, Jekyll]
tags: [permalink, url, routing]
description: "详细解析 Jekyll 永久链接系统的配置与自定义"
author: "张三"
excerpt_separator: <!--more-->
comments: true
math: false
mermaid: true
toc: true
featured: true
image: /images/posts/jekyll-permalink.jpg
---
7.12 扩展阅读
本章小结
| 要点 | 说明 |
|---|
| 文件命名 | YYYY-MM-DD-slug.md 格式必需 |
| 日期 | Front Matter 优先,文件名其次 |
| Permalink | 全局 _config.yml + 文章级覆盖 |
| 分类/标签 | 通过插件自动生成列表页面 |
| 草稿 | _drafts/ 或 published: false |
| 分页 | jekyll-paginate 插件实现 |
下一章:页面与集合