摘要:记录了一个 17 年老博客从 Netlify 迁移至 Cloudflare Pages 的全过程。涵盖了构建环境配置、Profile Mode 深度定制、以及基于 Python 脚本的 URL 架构重构(中文转 Slug + 301 重定向闭环)。

1. 基础设施:Cloudflare Pages 构建环境

为了解决 Hugo PaperMod 主题 CSS 不生效的问题,必须锁定 Hugo 的 Extended 版本。

Cloudflare 后台设置:

  • 位置:Settings -> Environment variables
  • 变量名HUGO_VERSION
  • 0.147.0 (推荐使用较新的稳定版,务必与本地一致)

2. 视觉系统:文人风骨与极客之魂

针对 PaperMod Profile Mode(极简个人主页模式),我们定制了一套**“宋体标题 + 黑体正文 + 极客蓝交互”**的 CSS 方案。

文件路径assets/css/extended/custom.css 最终代码

/* ========================================================
   ChowRay.org - 全站样式终极定制版
   ======================================================== */

/* 1. 全局变量定义 */
:root {
    /* 字体栈:标题优先宋体(人文感),正文优先系统黑体(易读性) */
    --font-serif: "Noto Serif SC", "Source Han Serif SC", "Songti SC", "SimSun", "Times New Roman", serif;
    --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
    --font-mono: "JetBrains Mono", "Fira Code", Consolas, monospace;

    /* 品牌色:极客蓝 (Geek Blue) */
    --geek-blue: #3b82f6;
    --geek-blue-rgb: 59, 130, 246; 
}

/* 2. 字体与排版 */
h1, h2, h3, h4, h5, h6, .entry-header h2, .post-title {
    font-family: var(--font-serif) !important;
    font-weight: 700 !important;
    letter-spacing: 0.5px !important;
}

/* 文章详情页大标题 (控制大小,建立层级) */
.post-title {
    font-size: 2.5rem !important; /* 约40px,确立视觉中心 */
    line-height: 1.3 !important;
    margin-bottom: 20px !important;
}

/* 文章正文 */
.post-content {
    font-family: var(--font-sans) !important;
    font-size: 17px !important;
    line-height: 1.8 !important;
    text-align: justify;
    color: #333 !important;
}
[data-theme="dark"] .post-content { color: #c4c4c5 !important; }

/* 3. 内容层级修正 (Visual Hierarchy) */
/* 防止正文内的 H1 喧宾夺主 */
.post-content h1 { font-size: 1.8rem !important; color: #111 !important; margin-top: 3rem !important; }
.post-content h2 { font-size: 1.5rem !important; color: #222 !important; margin-top: 2.5rem !important; }
.post-content h3 { font-size: 1.25rem !important; font-weight: 700 !important; }

/* 4. 引用块 (Blockquote) */
.post-content blockquote {
    font-family: var(--font-serif) !important;
    font-style: italic;
    border-left: 3px solid var(--geek-blue) !important; /* 3px 极客蓝边框 */
    background: #f9fafb;
    padding: 10px 20px !important;
    color: #555 !important;
}
[data-theme="dark"] .post-content blockquote {
    background: #2e2e33 !important;
    border-left-color: #444 !important;
    color: #b0b0b0 !important;
}

/* 5. 交互特效 (The Soul) */
/* 头像呼吸 + 歪头杀 */
@keyframes breathing {
    0% { transform: scale(1); box-shadow: 0 0 0 rgba(var(--geek-blue-rgb), 0); }
    50% { transform: scale(1.02); box-shadow: 0 0 20px rgba(var(--geek-blue-rgb), 0.4); }
    100% { transform: scale(1); box-shadow: 0 0 0 rgba(var(--geek-blue-rgb), 0); }
}
.profile img {
    animation: breathing 4s ease-in-out infinite;
    transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1);
    backface-visibility: hidden; 
}
.profile img:hover {
    animation: none; /* 悬停时停止呼吸,触发歪头 */
    transform: scale(1.08) rotate(5deg); 
    box-shadow: 0 0 25px rgba(var(--geek-blue-rgb), 0.6);
    cursor: pointer;
}

/* 按钮与菜单的物理反馈 */
.button, .social-icons a, #menu a {
    transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    border-radius: 8px; 
}
.button:hover, .social-icons a:hover, #menu a:hover {
    transform: translateY(-3px); /* 悬停上浮 */
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
#menu a { background: transparent; padding: 4px 12px; } /* 菜单平时透明 */
#menu a:hover { background: var(--entry); color: var(--geek-blue) !important; }

3. URL 架构重构:告别中文乱码

为了解决旧文章 URL 包含中文、日期前缀冗余的问题,同时保证 SEO 链接不变成 404,我们开发了 refactor_v6_final.py 脚本。

核心逻辑:

  1. 清洗 Slug:自动提取文件夹名,去掉 2011-05-11- 日期前缀,保留纯净名称。
  2. 智能 Aliases:自动计算出当前的中文旧链接,写入 aliases 字段,实现 301 无缝跳转。
  3. 双重保护:如果文章已有 Slug,则基于旧 Slug 生成 Alias,确保万无一失。

Python 脚本 (refactor_v6_final.py):

import os
import frontmatter
import re

TARGET_DIR = os.path.join('content', 'posts')

def sanitize_for_url(text):
    # 模拟 Hugo URL 清洗规则:转小写,去标点
    text = str(text).lower()
    text = re.sub(r'[^\w\u4e00-\u9fa5\-]', '', text)
    return text

def main():
    print(f"🚀 开始全站 URL 重构 (Folder Slug + Auto Aliases)...")
    for root, dirs, files in os.walk(TARGET_DIR):
        for file in files:
            if file.endswith('.md'):
                file_path = os.path.join(root, file)
                
                # 1. 准备基础路径信息
                folder_name = os.path.basename(root)
                if file != 'index.md': folder_name = os.path.splitext(file)[0]

                # 计算旧链接路径 (用于生成 Alias)
                rel_path = os.path.relpath(root, 'content')
                if file != 'index.md': rel_path = os.path.dirname(rel_path)
                else: rel_path = os.path.dirname(rel_path)

                # 路径清洗 (确保 Alias 与浏览器访问的真实链接一致)
                path_parts = rel_path.split(os.sep)
                cleaned_parts = [sanitize_for_url(p) for p in path_parts]
                base_url_prefix = '/'.join(cleaned_parts)

                try:
                    post = frontmatter.load(file_path)
                    is_modified = False
                    aliases_to_add = []

                    # --- A. 文件夹名兜底 Alias ---
                    cleaned_folder = sanitize_for_url(folder_name)
                    folder_alias = f"/{base_url_prefix}/{cleaned_folder}/"
                    aliases_to_add.append(folder_alias)

                    # --- B. 旧 Slug 优先 Alias ---
                    current_slug = post.metadata.get('slug')
                    if current_slug:
                        slug_alias = f"/{base_url_prefix}/{current_slug}/"
                        aliases_to_add.append(slug_alias)
                    else:
                        # --- C. 无 Slug 时,使用文件夹名去日期生成新 Slug ---
                        # 去掉 YYYY-MM-DD- 前缀
                        new_slug = re.sub(r'^\d{4}-?\d{2}-?\d{2}-?', '', folder_name)
                        post.metadata['slug'] = new_slug
                        is_modified = True
                        print(f"📝 生成 Slug: {new_slug}")

                    # --- D. 写入 Aliases ---
                    if 'aliases' not in post.metadata: post.metadata['aliases'] = []
                    for alias in aliases_to_add:
                        if alias not in post.metadata['aliases']:
                            post.metadata['aliases'].append(alias)
                            is_modified = True

                    if is_modified:
                        with open(file_path, 'wb') as f: frontmatter.dump(post, f)
                        print(f"✅ 处理: {folder_name}")
                    
                except Exception as e:
                    print(f"❌ 错误: {file_path} - {e}")

if __name__ == "__main__":
    main()

配合配置文件 (config.yml):

permalinks:
  # 统一为:/年/月/文章名/ (干净,无中文,层级清晰)
  posts: "/:year/:month/:slug/"

4. 自动化工作流:极速预览与发布

告别复杂的命令敲击,将日常操作封装为脚本。

预览脚本 (server.sh - 内存模式防闪退):

#!/bin/bash
# 捕捉 Ctrl+C 防止窗口秒关
trap 'echo -e "\n🛑 已停止"; exit' SIGINT

echo -e "🚀 启动极速预览 (内存模式)..."
# 内存渲染,不伤硬盘,无垃圾文件
hugo server -D --renderToMemory

# 保持 Git Bash 窗口打开
echo -e "\n⚡ 窗口保持打开..."
[[ "$OSTYPE" == "msys" ]] && exec bash --login -i

发布脚本 (deploy.sh - 智能推送):

#!/bin/bash
echo -e "🚀 准备推送到 GitHub..."
git add -A

# 支持交互式输入 Commit 信息,回车默认使用时间戳
if [ $# -eq 0 ]; then
    echo -e "📝 请输入提交信息 (直接回车用默认时间): "
    read input_msg
    if [ -z "$input_msg" ]; then
        msg="rebuilding site $(date +'%Y-%m-%d %H:%M:%S')"
    else
        msg="$input_msg"
    fi
else
    msg="$1"
fi

git commit -m "$msg"
echo -e "📤 推送中..."
git push origin master
echo -e "✅ 完成!"
read -p "按任意键关闭..." -n1 -s

结语: 至此,完成了从“古典博客”到“现代工程化博客”的华丽转身。数据主权在手,排版赏心悦目,架构坚不可摧。

Happy Blogging!