在搭建个人博客时,很多人会将博客的框架代码(如 Hugo/Hexo)、主题文件和文章的 Markdown 文件混在一个 GitHub 仓库里。这虽然简单,但随着文章越来越多,你的写作环境会被各种前端配置文件干扰;而且,如果你想写一些不公开的私密草稿,公开的仓库也无法满足需求。
为了获得最纯粹的写作体验,我采用了 「代码与内容分离」 的架构。今天这篇文章,就来详细聊聊如何基于 Hugo + PaperMod 主题,配合 Cloudflare Pages 部署这样一个现代化的博客。
🏗️ 架构概览:双仓库联动
我的博客由两个 GitHub 仓库组成:
- 引擎仓库 (公开):
PaperMod-Blog-Cloudflare- 角色:博客的“打印机”与“骨架”。
- 内容:包含 Hugo 的配置文件、PaperMod 主题、自定义 CSS 等。没有实质性的文章内容。
- 内容仓库 (私有):
blog-content- 角色:博客的“燃料”与“血肉”。
- 内容:纯净的 Markdown 文件和图片资源。
- 优势:设置为 Private (私有),确保草稿和私密笔记的安全。写作时只需在本地使用 Obsidian 或 Typora 即可。
这两个仓库通过 Hugo Modules(Go Modules 底层)连接。当云端开始构建时,引擎会自动把内容仓库拉取过来,合并渲染成最终的网页。
在 config.yml 中添加如下配置:
module:
imports:
- path: github.com/adityatelange/hugo-PaperMod
- path: github.com/monstercjz/blog-content
mounts:
- source: blog-paper # 内容仓库的blog-paper目录
target: content/posts # 挂载到 content/posts
🛠️ 部署痛点一:Cloudflare 如何拉取私有内容?
将公开的引擎仓库绑定到 Cloudflare Pages 非常简单,但在执行 hugo 构建命令时,如何拉取私有的 blog-content 仓库是核心难点。
若不做特殊处理,部署时会遇到权限报错:
fatal: could not read Username for 'https://github.com': terminal prompts disabled
Error: failed to load modules: failed to get ["github.com/monstercjz/blog-content@upgrade"]
这是因为 Cloudflare 的 CI/CD 环境没有图形界面,无法弹窗让你输入 GitHub 密码。
终极部署方案
第一步:获取 GitHub 个人令牌 (Personal Access Token)
我们需要一个通行证来赋予 Cloudflare 访问私有库的权限:
- 前往 GitHub 的 Settings -> Developer settings -> Personal access tokens (classic)。
- 点击 Generate new token。
- 勾选
repo权限(Full control of private repositories)。 - 生成后,立即复制并妥善保存这段以
ghp_开头的 Token(严禁提交到公开仓库!)。
第二步:配置 Cloudflare 环境变量
进入 Cloudflare Pages 项目设置,找到 Settings -> Environment variables。添加以下两个变量:
- 变量 1:
GOPRIVATE(告诉 Go 不要通过公共代理拉取私有库)- Value:
github.com/你的用户名/*
- Value:
- 变量 2:
GITHUB_TOKEN(存储你的通行证)- Value:
ghp_xxxxxxxxxxxxxxxxxxx(刚才生成的 Token)
- Value:
第三步:修改构建命令 (Build Command)
进入 Settings -> Builds & deployments,将 Build command 修改为:
git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" && hugo --gc --minify
原理分析:利用
git config将环境变量${GITHUB_TOKEN}动态注入。告诉 Git:凡是遇到https://github.com/的链接,自动替换为带有 Token 的认证链接。
🔄 部署痛点二:内容仓库更新如何触发构建?
由于 Cloudflare 绑定的是引擎仓库,当你只向内容仓库推送文章时,Cloudflare 不会自动触发更新。我们需要配置 Webhook 实现联动。
实现步骤
1. 在 Cloudflare 获取部署钩子 (Deploy Webhook)
- 登录你的 Cloudflare 账号,进入你的 Pages 项目(
PaperMod-Blog-Cloudflare)。 - 点击顶部的 Settings (设置) 选项卡。
- 在左侧菜单中找到 Builds & deployments (构建和部署)。
- 向下滚动,找到 Deploy hooks (部署挂钩) 区域。
- 点击 Add deploy hook (添加部署挂钩) 按钮。
- 给这个 Hook 起个名字,比如叫:
Trigger from blog-content。 - 选择你要触发部署的分支(通常是
main或master)。 - 点击保存后,你会得到一串很长的 URL(例如:
https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/xxxxxx)。 - 复制这串 URL,妥善保存。
2. 在 GitHub 内容仓库配置 Webhook
- 登录 GitHub,进入你的私有内容仓库 (
blog-content)。 - 点击仓库右上角的 Settings (设置)。
- 在左侧菜单中点击 Webhooks。
- 点击右上角的 Add webhook 按钮(可能会要求你输入 GitHub 密码确认身份)。
- 填写 Webhook 详情:
- Payload URL:粘贴你刚才在 Cloudflare 复制的那串 Deploy hook URL。
- Content type:选择
application/json。 - Secret:留空即可。
- Which events would you like to trigger this webhook?:保持默认的
Just the push event.(仅在推送时触发)。 - 确保 Active 是勾选状态。
- 点击底部的 Add webhook 按钮保存。
3. 测试自动化流水线
- 在本地
blog-content提交代码并git push。 - 检查 GitHub Webhooks 页面是否有绿色对勾(✅)。
- 观察 Cloudflare Pages 是否自动启动了新的构建任务。
✨ 总结:极客般的写作体验
通过上述配置,你完美实现了内容与引擎的解耦:
- 纯粹写作:只需在
blog-content中专注码字。 - 安全隐秘:内容仓私有,无需担心草稿泄露。
- 全自动部署:
git push后几十秒内,新文章自动上线。
这,就是代码与内容分离的魅力所在。现在,端起咖啡,享受你的极客创作之旅吧!
yml配置文件
# ============================================================
# Hugo Modules 配置
# ============================================================
module:
imports:
- path: github.com/adityatelange/hugo-PaperMod
- path: github.com/monstercjz/blog-content
mounts:
- source: blog-paper # 内容仓库的blog-paper目录
target: content/blog-paper # 挂载到 content/blog-paper
# ============================================================
# Hugo 网站基础配置
# ============================================================
# 如果你的网站使用 'https',请确保 base url 不是 'http' 开头,
# 否则站点地图会包含 http(而非 https)URL,这会影响 Google 索引。
baseURL: "https://paper.nuaa.dpdns.org/"
title: 冇记 # 网站标题
copyright: "© 冇文化 · [冇记](https://paper.nuaa.dpdns.org/)" # 页脚版权信息
# ============================================================
# Hugo 构建配置
# ============================================================
enableInlineShortcodes: true # 启用内联短代码
enableRobotsTXT: true # 自动生成 robots.txt
buildDrafts: false # 是否构建草稿文章
buildFuture: false # 是否构建未来日期的文章
buildExpired: false # 是否构建已过期的文章
enableEmoji: true # 启用 Emoji 支持
pygmentsUseClasses: true # 使用 CSS 类进行代码高亮
mainsections: ["blog-paper"] # 主内容分区,用于首页显示
# ============================================================
# 压缩配置
# ============================================================
minify:
disableXML: true # 禁用 XML 压缩
# minifyOutput: true # 是否压缩输出
# ============================================================
# 分页配置
# ============================================================
pagination:
disableAliases: false
pagerSize: 5 # 每页显示文章数量
# ============================================================
# 多语言配置
# ============================================================
defaultContentLanguage: zh # 默认内容语言
languages:
# ---------- 中文(默认语言)----------
zh:
languageName: "中文"
weight: 1 # 权重越小,优先级越高
taxonomies: # 分类法配置
category: categories
tag: tags
series: series
menu: # 导航菜单配置
main:
- name: 📌 全部文章 # 显示名称
url: /blog-paper/ # 链接地址 (建议前面加个 / 变成绝对路径更稳妥)
weight: 1 # 排序权重
title: "浏览博客的所有文章列表" # 👈 新增:鼠标悬停时显示的文字
- name: 📂 归档
url: /archives/
weight: 5
title: "按时间线查看历史文章" # 👈 新增
- name: 🔍 搜索
url: /search/
weight: 10
title: "快速查找你感兴趣的内容" # 👈 新增
- name: 🏷️ 标签
url: /tags/
weight: 15
title: "按标签聚合筛选文章" # 👈 新增
- name: 📁 分类
url: /categories/
weight: 18
title: "按分类聚合筛选文章" # 👈 新增
- identifier: github-menu # 留空 name 时,最好加一个标识符
name: "" # 👈 关键修改:把引号里的空格删掉,变成绝对的空字符串
pre: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>'
url: https://github.com/monstercjz/PaperMod-Blog-Cloudflare
weight: 20
title: "访问本站点的开源代码仓库"
# ---------- 英语 ----------
en:
languageName: "English"
weight: 2
taxonomies:
category: categories
tag: tags
series: series
menu:
main:
- name: 📌 All blog-paper # Display name
url: blog-paper/ # URL
weight: 1 # Weight (smaller = earlier)
- name: 📂 Archive
url: archives
weight: 5
- name: 🔍 Search
url: search/
weight: 10
- name: 🏷️ Tags
url: tags/
weight: 15
- name: 📁 Categories
url: categories/
weight: 18
- name: GitHub
url: https://github.com/monstercjz/PaperMod-Blog-Cloudflare
weight: 20
# ============================================================
# 输出格式配置
# ============================================================
outputFormats:
llms:
mediaType: "text/plain"
baseName: "llms"
isPlainText: true
notAlternative: true
# ============================================================
# 输出配置(定义首页生成哪些格式)
# ============================================================
outputs:
home:
- HTML # HTML 页面
- RSS # RSS 订阅
- JSON # JSON 格式(用于搜索)
- llms # AI/LLM 可读格式
# ============================================================
# 主题参数配置(PaperMod 主题特有)
# ============================================================
params:
# ---------- 基础参数 ----------
env: production # 环境:production 启用 Google Analytics、OpenGraph、Twitter Cards 和 Schema
description: "冇记 - 冇文化的日常记录博客" # 网站描述(SEO)
author: 冇文化 # 作者名称
# author: ["Me", "You"] # 多作者模式
# ---------- 主题外观 ----------
defaultTheme: auto # 默认主题:auto(跟随系统)、light(浅色)、dark(深色)
# disableThemeToggle: true # 禁用主题切换按钮
# ---------- 功能开关 ----------
ShowShareButtons: false # 显示分享按钮
ShowReadingTime: true # 显示阅读时间
# disableSpecial1stPost: true # 禁用首篇文章特殊样式
displayFullLangName: true # 显示完整语言名称
ShowPostNavLinks: true # 显示上一篇/下一篇导航
ShowBreadCrumbs: true # 显示面包屑导航
ShowCodeCopyButtons: true # 显示代码复制按钮
ShowRssButtonInSectionTermList: true # 在分类列表显示 RSS 按钮
ShowAllPagesInArchive: true # 在归档页显示所有页面
ShowPageNums: true # 显示页码
ShowToc: true # 显示文章目录(Table of Contents)
# comments: false # 评论功能
images: ["images/cover.png"] # 社交分享时的默认封面图
# ---------- ProfileMode:首页个人资料模式 ----------
# 与 homeInfoParams 二选一,enabled 为 true 时生效
profileMode:
enabled: false # 是否启用 ProfileMode
title: 冇记 · 记录 · 分享 - 冇文化 # 大标题
imageUrl: "/android-chrome-512x512.png" # 头像图片地址
imageTitle: 冇文化的头像 # 图片 alt 文本
# imageWidth: 120 # 头像宽度
# imageHeight: 120 # 头像高度
# buttons: # 首页快捷按钮
# - name: Archives
# url: archives
# - name: Tags
# url: tags
# ---------- HomeInfoParams:首页欢迎信息模式 ----------
# 与 profileMode 二选一,profileMode.enabled 为 false 时生效
homeInfoParams:
Title: "📖 学习 ✍️ 记录 🌐 分享 "
Content: >
👋欢迎来到冇记!🎉 🎉
- 本博客主人叫冇文化,故简称 冇记。
- 这里没有高深理论,只有点滴的记录。
- 愿路过的你,能在这里找到一点你需要的。
<br>
🏡🌈🍉🌟📍🎆🎇🧨🧨👋✨ ✨
# ---------- 社交媒体图标 ----------
# 显示在页面底部的社交链接图标
socialIcons:
- name: github
title: GitHub
url: "https://github.com/monstercjz/PaperMod-Blog-Cloudflare"
- name: QQ
title: QQ
url: "https://qm.qq.com/cgi-bin/qm/qr?k=YOUR_QQ_KEY"
- name: telegram
title: Telegram
url: "https://t.me/YOUR_TELEGRAM"
# ---------- 文章编辑链接 ----------
editPost:
URL: "https://github.com/monstercjz/blog-content/edit/main/" # 编辑链接基础地址(指向内容仓库)
Text: "📝 编辑此页" # 编辑按钮文字
appendFilePath: true # 是否在编辑链接后附加文件路径
# ---------- 网站标签配置 ----------
# label:
# iconSVG: '<svg xmlns="http://www.w3.org/2000/svg" height="25" viewBox="0 -960 960 960" fill="currentColor"><path d="M320-240h320v-80H320v80Zm0-160h320v-80H320v80ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>'
# text: "Home" # 标签文字
# icon: icon.png # 标签图标
# iconHeight: 35 # 图标高度
# ---------- Google Analytics 配置 ----------
# analytics:
# google:
# SiteVerificationTag: "XYZabc"
# ---------- 静态资源配置 ----------
assets:
disableHLJS: true # 禁用 Hugo 内置代码高亮,使用主题自带
favicon: "favicon.ico"
favicon16x16: "favicon.ico"
favicon32x32: "favicon.ico"
apple_touch_icon: "apple-touch-icon.png"
# favicon: "<link / abs url>" # 网站图标
# favicon16x16: "<link / abs url>" # 16x16 图标
# favicon32x32: "<link / abs url>" # 32x32 图标
# apple_touch_icon: "<link / abs url>" # iOS 桌面图标
# safari_pinned_tab: "<link / abs url>" # Safari 固定标签图标
# ---------- 文章封面图配置 ----------
# cover:
# hidden: true # 在所有地方隐藏,但结构数据中保留
# hiddenInList: true # 在列表页和首页隐藏
# hiddenInSingle: true # 在文章页隐藏
# ---------- 搜索功能配置(Fuse.js 选项)----------
# fuseOpts:
# isCaseSensitive: false # 是否区分大小写
# shouldSort: true # 是否排序
# location: 0
# distance: 1000
# threshold: 0.4 # 匹配阈值
# minMatchCharLength: 0 # 最小匹配字符数
# keys: ["title", "permalink", "summary", "content"] # 搜索字段
# ============================================================
# Markdown 渲染配置
# ============================================================
markup:
goldmark:
renderer:
unsafe: true # 允许渲染 HTML 标签
highlight:
noClasses: false # 使用 CSS 类进行代码高亮(支持自定义样式)
# anchorLineNos: true # 行号锚点
# codeFences: true # 代码围栏
# guessSyntax: true # 自动猜测语法
# lineNos: true # 显示行号
# style: monokai # 高亮主题
# ============================================================
# 隐私配置(增强第三方嵌入的隐私保护)
# ============================================================
# privacy:
# vimeo:
# disabled: false
# simple: true
#
# twitter:
# disabled: false
# enableDNT: true # 启用 Do Not Track
# simple: true
#
# instagram:
# disabled: false
# simple: true
#
# youtube:
# disabled: false
# privacyEnhanced: true # 隐私增强模式
# ============================================================
# 第三方服务配置
# ============================================================
services:
instagram:
disableInlineCSS: true # 禁用内联 CSS
x:
disableInlineCSS: true # 禁用内联 CSS