从 Jekyll 迁移到 Hugo:一个循序渐进的指南

引言

对于许多曾经使用 Jekyll 作为静态网站生成器的开发者来说,迁移到 Hugo 可能是出于对性能提升、更简化的配置或更强大的功能集的需求。Jekyll 是一款成熟且广泛使用的 SSG,但随着技术的发展,Hugo 凭借其极快的构建速度和现代化的特性,吸引了越来越多的用户。本文旨在提供一个详细的指南,帮助您逐步完成从 Jekyll 到 Hugo 的迁移过程,最大程度地减少数据丢失和工作量的同时,充分享受 Hugo 带来的优势。

1. 理解迁移的挑战

迁移过程的主要挑战在于:

  • 模板引擎差异: Jekyll 使用 Liquid 模板语言,而 Hugo 使用 Go 的 text/templatehtml/template
  • Front Matter 格式: 虽然两者都支持 YAML, JSON, TOML,但具体的使用方式和内置变量可能不同。
  • 文件结构: Hugo 和 Jekyll 的项目结构有所区别。
  • 插件与扩展: Jekyll 的插件生态与 Hugo 的函数和短代码系统需要进行适配。

2. 准备工作:备份与分析

在开始任何迁移之前,请务必:

  1. 完整备份您的 Jekyll 网站: 包括所有内容文件(Markdown)、图片、CSS/JS 文件以及配置文件。
  2. 分析您的 Jekyll 网站:
    • 内容数量: 估算您的文章、页面数量。
    • 主题复杂度: 您的 Jekyll 主题有多复杂?是否大量使用了 Liquid 过滤器或自定义标签?
    • 插件依赖: 您使用了哪些 Jekyll 插件?它们在 Hugo 中是否有直接的替代品?
    • 文件结构: 您的文件是如何组织的?例如,文章是否在 _posts 目录下?页面在根目录还是 _pages 目录下?

3. 初始化 Hugo 项目

首先,您需要安装 Hugo。然后,在您想要存放新网站的目录下,运行以下命令创建一个新的 Hugo 项目:

hugo new site my-new-hugo-site
cd my-new-hugo-site

这将创建一个基本的 Hugo 项目结构。

4. 迁移内容文件

这是迁移的核心步骤。

4.1. 迁移文章 (Posts)

Jekyll 的文章通常存储在 _posts 目录下,文件名格式为 YYYY-MM-DD-post-title.md。Hugo 的文章默认存放在 content/post/ 目录下,文件名可以是 post-title.md,日期信息放在 Front Matter 中。

您可以编写一个脚本来批量转换:

  • 读取 Jekyll 的 _posts 目录下的所有 .md 文件。
  • 解析每个文件的 Front Matter (YAML)。
    • 提取 date,将其格式化为 YYYY-MM-DD
    • 提取 title
    • 保留其他 Front Matter 字段,但注意 Hugo 的内置变量(例如 layout 在 Hugo 中有不同含义)。
  • 提取文件主体内容(Markdown 部分)。
  • 创建 Hugo 的 Front Matter:
    • title = "您的文章标题"
    • date = 2023-10-27T10:00:00+08:00 (Hugo 推荐 ISO 8601 格式)
    • draft = false (如果文章不是草稿)
    • tags = ["tag1", "tag2"] (如果 Jekyll 中有 tags)
    • categories = ["category1"] (如果 Jekyll 中有 categories)
  • 将转换后的内容保存到 content/post/ 目录下,文件名为 您的文章标题.md(Hugo 会自动处理 slug)。

示例脚本思路(Python):

import os
import glob
import frontmatter
import datetime

jekyll_posts_dir = '../jekyll-site/_posts' # 假设 Jekyll 网站在上一级目录
hugo_posts_dir = './content/post'

if not os.path.exists(hugo_posts_dir):
    os.makedirs(hugo_posts_dir)

for jekyll_file in glob.glob(os.path.join(jekyll_posts_dir, '*.md')):
    post = frontmatter.load(jekyll_file)

    # 提取日期和标题
    date_str = os.path.basename(jekyll_file)[:10]
    try:
        post_date = datetime.datetime.strptime(date_str, '%Y-%m-%d').isoformat() + "Z" # ISO 8601 for Hugo
    except ValueError:
        print(f"Skipping file with invalid date format: {jekyll_file}")
        continue

    # 确保 title 存在
    if not post.metadata.get('title'):
        print(f"Skipping file with no title: {jekyll_file}")
        continue
    post_title = post.metadata['title']

    # 创建 Hugo Front Matter
    hugo_meta = {
        'title': post_title,
        'date': post_date,
        'draft': False, # 默认非草稿
    }

    # 复制 Jekyll 的其他 Front Matter 字段
    for key, value in post.metadata.items():
        if key not in ['date', 'title', 'layout', 'permalink']: # 避免覆盖或复制不必要的字段
            hugo_meta[key] = value

    # 创建 Hugo 内容文件
    hugo_content = frontmatter.dumps(post.content, hugo_meta)
    hugo_filename = f"{post_title.replace(' ', '-')}.md" # Hugo 会自动处理 slug
    hugo_filepath = os.path.join(hugo_posts_dir, hugo_filename)

    with open(hugo_filepath, 'w', encoding='utf-8') as f:
        f.write(hugo_content)

    print(f"Migrated: {jekyll_file} -> {hugo_filepath}")

4.2. 迁移页面 (Pages)

Jekyll 的页面可能在根目录或 _pages 目录下。Hugo 将页面存放在 content/ 目录下的不同子目录中,以区分内容类型(例如 content/about.md, content/contact.md)。

  • 将 Jekyll 的页面文件(除了 index.mdabout.md 等特殊页面)复制到 Hugo 的 content/ 目录下,并确保 Front Matter 包含 titledate

4.3. 迁移图片、CSS 和 JS

  • 将 Jekyll 的 images/ (或类似目录) 中的图片复制到 Hugo 的 static/images/ 目录下。
  • 将 Jekyll 的 css/js/ 目录中的文件复制到 Hugo 的 static/css/static/js/ 目录下。

5. 重建主题

这是迁移中最具挑战性的部分,因为模板语言不同。

  1. 创建 Hugo 主题骨架: 在 themes/ 目录下创建一个新主题文件夹(例如 mytheme),并创建必要的目录结构 (layouts/, static/, assets/, i18n/ 等)。
  2. 逐步转换 Liquid 模板到 Go 模板:
    • 基本布局: 将 Jekyll 的