在搭建个人博客时,很多人会将博客的框架代码(如 Hugo/Hexo)、主题文件和文章的 Markdown 文件混在一个 GitHub 仓库里。这虽然简单,但随着文章越来越多,你的写作环境会被各种前端配置文件干扰;而且,如果你想写一些不公开的私密草稿,公开的仓库也无法满足需求。

为了获得最纯粹的写作体验,我采用了 「代码与内容分离」 的架构。今天这篇文章,就来详细聊聊如何基于 Hugo + PaperMod 主题,配合 Cloudflare Pages 部署这样一个现代化的博客。


🏗️ 架构概览:双仓库联动

我的博客由两个 GitHub 仓库组成:

  1. 引擎仓库 (公开)PaperMod-Blog-Cloudflare
    • 角色:博客的“打印机”与“骨架”。
    • 内容:包含 Hugo 的配置文件、PaperMod 主题、自定义 CSS 等。没有实质性的文章内容。
  2. 内容仓库 (私有)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 访问私有库的权限:

  1. 前往 GitHub 的 Settings -> Developer settings -> Personal access tokens (classic)
  2. 点击 Generate new token
  3. 勾选 repo 权限(Full control of private repositories)。
  4. 生成后,立即复制并妥善保存这段以 ghp_ 开头的 Token(严禁提交到公开仓库!)。

第二步:配置 Cloudflare 环境变量

进入 Cloudflare Pages 项目设置,找到 Settings -> Environment variables。添加以下两个变量:

  • 变量 1:GOPRIVATE (告诉 Go 不要通过公共代理拉取私有库)
    • Value: github.com/你的用户名/*
  • 变量 2:GITHUB_TOKEN (存储你的通行证)
    • Value: ghp_xxxxxxxxxxxxxxxxxxx (刚才生成的 Token)

第三步:修改构建命令 (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)

  1. 登录你的 Cloudflare 账号,进入你的 Pages 项目(PaperMod-Blog-Cloudflare)。
  2. 点击顶部的 Settings (设置) 选项卡。
  3. 在左侧菜单中找到 Builds & deployments (构建和部署)
  4. 向下滚动,找到 Deploy hooks (部署挂钩) 区域。
  5. 点击 Add deploy hook (添加部署挂钩) 按钮。
  6. 给这个 Hook 起个名字,比如叫:Trigger from blog-content
  7. 选择你要触发部署的分支(通常是 mainmaster)。
  8. 点击保存后,你会得到一串很长的 URL(例如:https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/xxxxxx)。
  9. 复制这串 URL,妥善保存。

2. 在 GitHub 内容仓库配置 Webhook

  1. 登录 GitHub,进入你的私有内容仓库 (blog-content)。
  2. 点击仓库右上角的 Settings (设置)
  3. 在左侧菜单中点击 Webhooks
  4. 点击右上角的 Add webhook 按钮(可能会要求你输入 GitHub 密码确认身份)。
  5. 填写 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 是勾选状态。
  6. 点击底部的 Add webhook 按钮保存。

3. 测试自动化流水线

  1. 在本地 blog-content 提交代码并 git push
  2. 检查 GitHub Webhooks 页面是否有绿色对勾(✅)。
  3. 观察 Cloudflare Pages 是否自动启动了新的构建任务。

✨ 总结:极客般的写作体验

通过上述配置,你完美实现了内容与引擎的解耦:

  1. 纯粹写作:只需在 blog-content 中专注码字。
  2. 安全隐秘:内容仓私有,无需担心草稿泄露。
  3. 全自动部署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