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

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.dateFront Matter date 或文件名文章发布日期
site.time构建时间每次构建时更新
page.last_modified_atFront 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'" %}

永久链接定义了文章的 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
# _config.yml 全局设置
permalink: /posts/:year/:month/:title/

# 文章级覆盖
---
permalink: /blog/my-custom-url/
---
变量说明示例
:year4位年份2025
:month2位月份01
:day2位日期15
:short_year2位年份25
:title文件名中的标题部分hello-world
:slug标题的小写连字符格式hello-world
:categories第一个分类tech
:i_day无前导零的日期15
:i_month无前导零的月份1
# 方案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 %}

7.5 标签(Tags)

在 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/

7.7 分页(Pagination)

安装分页插件

# 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 插件实现

下一章:页面与集合