编辑
2026-10-25
项目分享
00

目录

简介
部署
有服务器、域名
1. 前置条件
2. 本地建站
3. 写第一篇文章
4. 本地构建自检
5. 服务器初始化
6. Nginx 站点配置
7. 上 HTTPS
8. DNS 解析(三家对比)
9. 发布:手动与自动两种
10. 本方案检查清单
github免费部署
1. 准备仓库
2. 配置 Actions
3. 开启 Pages
4. 自定义域名(可选但推荐)
5. 检查清单与排错
常见故障对照(两条路线通用)
多端写作实现
核心模型
仓库结构约定
统一 front matter
手机端方案对比
多端协作纪律
一键发布

简介

Hugo 是一个用 Go 语言编写的开源静态网站生成器。它的核心作用是把你用 Markdown 写的文章内容、模板文件和配置设置,转换成完整的、由纯 HTML、CSS 和 JavaScript 构成的静态网站。

核心特点与优势:

  • 极速生成: 这是 Hugo 最著名的特点。得益于 Go 语言的高效,Hugo 生成网站的速度极其快,通常只需要毫秒级的时间,即使是拥有成千上万页面的超大型网站。这使得开发和内容更新体验非常流畅。

  • 简单易用:

    • 安装简单:通常只需下载一个二进制文件即可运行,无需复杂的运行环境(如 PHP、Ruby、Node.js)。

    • 内容管理简单:内容通常用 Markdown 格式编写(.md 文件),结构清晰。

    • 配置清晰:主要配置文件是 config.toml/config.yaml/config.json,易于理解和修改。

  • 跨平台: 作为一个独立的二进制文件,Hugo 可以在 Windows, macOS, Linux 等主流操作系统上完美运行。

  • 强大的灵活性:

    • 模板引擎: 使用 Go 的 html/template 库,功能强大且灵活,可以创建复杂的布局和功能。

    • 内容组织: 通过灵活的目录结构和 Front Matter(文件顶部的元数据块,如 YAML, TOML, JSON)来组织内容、定义变量(标题、日期、标签、分类等)。

    • 主题系统: 拥有丰富的主题生态系统,用户可以直接使用或自定义主题来快速改变网站外观。

    • 短代码: 允许在 Markdown 内容中嵌入可重用的自定义 HTML 片段或复杂功能。

  • 安全性高: 生成的网站是纯静态文件,没有数据库或服务器端脚本执行,因此不易受到常见的动态网站攻击(如 SQL 注入、PHP 漏洞等)。

  • 部署简单: 生成的静态文件可以轻松部署到任何支持托管静态文件的服务器或服务上,例如 Netlify, Vercel, GitHub Pages, GitLab Pages, AWS S3, Cloudflare Pages 等,通常免费或成本极低。

  • 活跃的社区: 拥有一个庞大且活跃的社区,提供丰富的文档、主题、教程和支持。

部署

Hugo 的部署本质只有一句话:本地(或 CI)执行 hugo 生成 public/ 目录,再把这一堆纯静态文件交给任意 Web 服务器托管。剩下所有差异都来自「谁来构建、谁来托管」。

两条主流路线先做个对比,按自己的情况选一条为主、另一条备用即可:

维度服务器 + 域名GitHub Pages
费用轻量云服务器活动价几十到一百多一年,续费会上涨;域名几十一年完全免费(公开仓库)
国内访问备案后稳定、速度快*.github.io 时快时慢,部分网络不可达
可控性高:任意 Nginx 配置、重定向、缓存、Headers低:只能改 CNAME 与少量配置
运维成本需要管证书续期、系统更新、备份几乎没有
适合场景长期主力站、需要国内可用的正式博客试水、写给自己看、读者主要在海外

下面两条路线都从「本地建一个 Hugo 站点」开始,第 1~3 步是共用的。

有服务器、域名

按「云服务器 + Nginx + Let's Encrypt + 国内云 DNS / Cloudflare」这套最常见的组合写。

1. 前置条件

  • 一台云服务器:1 核 1G 足够跑纯静态站,系统用 Ubuntu 22.04 / 24.04。
  • 一个域名:服务器在中国大陆境内必须完成 ICP 备案,否则 80/443 会被云厂商拦截;服务器在境外或香港则不需要备案。
  • 本地已安装 Hugo(Windows 可用 scoop install hugo-extended,macOS 用 brew install hugo),执行 hugo version 能看到版本号,建议 ≥ 0.146。
  • 服务器的 22 / 80 / 443 端口在安全组和系统防火墙里都放行。

2. 本地建站

bash
# 生成站点,配置文件用 yaml hugo new site myblog --format yaml cd myblog git init

装主题(以 PaperMod 为例,用 submodule 方便后续升级):

bash
git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod

编辑 hugo.yaml,这是一份能直接跑起来的最小配置:

yaml
baseURL: "https://example.com/" languageCode: "zh-cn" defaultContentLanguage: "zh-cn" title: "我的博客" theme: "PaperMod" enableRobotsTXT: true hasCJKLanguage: true pagination: pagerSize: 20 markup: goldmark: renderer: unsafe: true # 允许正文里写裸 HTML,按需开启 params: env: production description: "记录与分享" author: "你的名字" ShowReadingTime: true ShowCodeCopyButtons: true ShowToc: true

baseURL 必须以 / 结尾,且要和最终访问的协议、域名完全一致。这是后面「页面能打开但样式全丢」的头号原因。

3. 写第一篇文章

bash
hugo new content posts/hello-world/index.md

生成的文章是草稿(draft: true),改成 false 才会出现在构建结果里:

yaml
--- title: "你好,世界" date: 2026-10-25T10:00:00+08:00 draft: false tags: ["随笔"] categories: ["生活"] --- 这里开始写正文。

本地预览(-D 让草稿也显示):

bash
hugo server -D --bind 0.0.0.0 --port 1313

4. 本地构建自检

bash
hugo --minify --gc --cleanDestinationDir

产物在 public/。发布前确认三件事:

  • public/index.html 存在;
  • 用浏览器直接打开 public/index.html,或者扫一眼 HTML 里的链接是 https://example.com/... 而不是 http://localhost:1313/...;
  • du -sh public 是几 MB 级别,而不是把 content/、.git 打进去了。

5. 服务器初始化

bash
ssh ubuntu@1.2.3.4 sudo apt update && sudo apt install -y nginx sudo mkdir -p /var/www/myblog sudo chown -R $USER:$USER /var/www/myblog sudo ufw allow 'Nginx Full' # 用 ufw 的话;云厂商安全组也要放行 80/443

部署目录用 /var/www/myblog,后续所有发布动作都只是往这个目录里写文件。建议用普通用户 + SSH 密钥登录,不要直接拿 root 跑 rsync。

6. Nginx 站点配置

新建 /etc/nginx/sites-available/myblog.conf:

nginx
server { listen 80; listen [::]:80; server_name example.com www.example.com; root /var/www/myblog; index index.html; location / { try_files $uri $uri/ =404; } # HTML 不缓存,改了立刻生效 location ~* \.html$ { add_header Cache-Control "no-cache"; } # 带哈希指纹的静态资源可以长期缓存;没有指纹的话把 30d 改小 location ~* \.(css|js|woff2?|png|jpe?g|webp|svg|ico)$ { expires 30d; add_header Cache-Control "public, max-age=2592000"; } error_page 404 /404.html; add_header X-Content-Type-Options "nosniff" always; gzip on; gzip_types text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; }

启用并检查:

bash
sudo ln -s /etc/nginx/sites-available/myblog.conf /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t && sudo systemctl reload nginx

此时用 IP 或 http://域名 应该已经能看到站点;看不到就先查安全组和 sudo systemctl status nginx,别急着弄证书。

7. 上 HTTPS

bash
sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d example.com -d www.example.com

certbot 会自动改写 Nginx 配置、追加 443 段并配置 HTTP 跳转。证书 90 天有效,系统自带 systemd timer 会自动续期,验证一次即可:

bash
sudo certbot renew --dry-run sudo systemctl list-timers | grep certbot

8. DNS 解析(三家对比)

服务器 IP 假设为 1.2.3.4,最少需要两条记录:根域名 @ 和 www。

DNS 服务商配置入口记录注意点
Cloudflare添加站点后把 NS 改到 Cloudflare,进入 DNS → RecordsA @ → 1.2.3.4、A www → 1.2.3.4橙色云朵 = 走 CDN 代理,源站必须持有效证书并把 SSL/TLS 模式设为 Full (strict);用 Flexible 配合源站强制跳转 HTTPS 会出现重定向循环。签证书期间建议先把记录设为灰云(DNS only),签发成功再开代理
阿里云 / 腾讯云控制台 → 云解析 DNS → 解析设置 → 添加记录A @ → 1.2.3.4、A www → 1.2.3.4,TTL 600国内服务器必须先完成备案,否则解析生效也访问不了;同一域名不要在两家同时改,先改 NS 再操作
DNSPod域名解析 → 添加记录 → 主机记录填 @ / www,线路「默认」同上,TTL 600免费版够用;改完等 TTL 过期再验证,别连按刷新

验证(Windows 用 nslookup example.com):

bash
dig +short example.com dig +short www.example.com curl -I http://example.com curl -I https://example.com

期望 curl -I https:// 返回 200,并且响应里有 server: nginx(Cloudflare 代理时会显示 cloudflare,属于正常)。

9. 发布:手动与自动两种

方案 A:本地构建后上传(推荐,服务器不用装 Hugo)

bash
hugo --minify --gc --cleanDestinationDir rsync -avz --delete public/ deploy@1.2.3.4:/var/www/myblog/

--delete 很关键,否则删掉的文章在服务器上会一直残留。

两个 Windows 上的坑:

  • Git Bash 默认不带 rsync。用 scoop install rsync 装一个,或者改在 WSL 里执行。
  • 没有 rsync 时的等效写法是 tar 管道:
bash
tar -czf - -C public . | ssh deploy@1.2.3.4 'find /var/www/myblog -mindepth 1 -delete && tar -xzf - -C /var/www/myblog'

方案 B:服务器直接拉代码构建(适合写作机器不固定)

服务器安装 Hugo,git clone 到 /srv/myblog,发布时:

bash
cd /srv/myblog git pull git submodule update --init --recursive hugo --minify --destination /var/www/myblog

代价是服务器要装 Hugo 并保留一份源码,好处是任何一台设备只要 git push,在服务器上跑一条命令就能上线。

方案 C:GitHub Actions 构建后推服务器

在下一节的 Actions 流程里,把「上传到 Pages」那一步换成 rsync action,把服务器 SSH 私钥放进仓库 Secrets 即可。适合既想要 git push 自动发布、又想要自己服务器速度的场景。

10. 本方案检查清单

  • baseURL 与实际域名、协议一致,且以 / 结尾
  • hugo --minify 构建无报错,public/ 里有 index.html
  • 服务器 /var/www/myblog 属主是部署用户,Nginx 可读
  • 80 / 443 在安全组和 ufw 都已放行
  • sudo nginx -t 通过,certbot renew --dry-run 通过
  • https://example.com 与 https://www.example.com 都能打开,且都跳 HTTPS
  • 随便点开一篇文章、一张图片,确认 404 已消除
  • 发布脚本里保留了 --delete,删文章后服务器不残留

github免费部署

全程零成本:仓库即源码,GitHub Actions 构建,GitHub Pages 托管。

1. 准备仓库

仓库名有两种,直接影响 baseURL:

  • 用户站:仓库名必须是 <用户名>.github.io,访问地址是 https://<用户名>.github.io/
  • 项目站:仓库名随意(比如 blog),访问地址是 https://<用户名>.github.io/blog/

把本地站点(含 hugo.yaml、content/、themes/ submodule)推上去。主题是 submodule,所以 checkout 时必须递归拉取,这一步漏了就会报「theme not found」。

2. 配置 Actions

新建 .github/workflows/hugo.yaml:

yaml
name: Deploy Hugo site to Pages on: push: branches: ["main"] workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: "pages" cancel-in-progress: false defaults: run: shell: bash jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - name: Setup Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: "0.147.0" # 建议锁一个具体版本,避免某天构建突然变样 extended: true - name: Setup Pages id: pages uses: actions/configure-pages@v5 - name: Build env: HUGO_ENVIRONMENT: production run: hugo --minify --baseURL "${{ steps.pages.outputs.base_url }}/" - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./public deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy id: deployment uses: actions/deploy-pages@v4

要点:

  • 构建时用 configure-pages 输出的 base_url 覆盖配置里的 baseURL,用户站和项目站都能自动适配,不必手改。
  • hugo-version 锁版本;主题要求的 Hugo 版本如果更高,按报错提示往上调。
  • fetch-depth: 0 让 Hugo 拿到完整 Git 历史,.Lastmod、更新时间排序才准。

3. 开启 Pages

仓库 Settings → Pages → Build and deployment → Source 选 GitHub Actions。

推送一次到 main,Actions 里应出现一次构建;成功后 Pages 面板会给出版址。之前必须先选 Source,否则 deploy-pages 会报环境不存在。

4. 自定义域名(可选但推荐)

  1. Settings → Pages → Custom domain 填 blog.example.com,保存(会在仓库里生成 CNAME)。

  2. DNS 加记录:

    • 子域名:CNAME blog → <用户名>.github.io
    • 根域名:A @ → 185.199.108.153、.109.153、.110.153、.111.153(四条都要加)
  3. 等解析生效后回到 Pages 勾选 Enforce HTTPS,证书由 GitHub 自动签发,通常几分钟到几小时。

*.github.io 在国内访问不稳定,挂自己的域名(尤其再套一层 Cloudflare)能改善,但要接受「免费 CDN 国内节点质量一般」这个现实。如果博客要长期给国内读者看,服务器方案仍是主力。

5. 检查清单与排错

现象原因处理
构建报 theme 找不到没拉 submoduleActions 里确认 submodules: recursive,仓库里有 .gitmodules
部署步骤报 Get Pages site failedPages 的 Source 还不是 GitHub Actions去 Settings → Pages 改 Source 后重跑
页面能开但样式全丢baseURL 少了末尾 /,或项目站路径不对交给 configure-pages 的 base_url 覆盖,别手写死
自定义域名一直 pendingDNS 未生效或被代理dig 确认记录,Cloudflare 代理先关掉再验证
文章顺序不对浅克隆导致无 Git 历史fetch-depth: 0

常见故障对照(两条路线通用)

现象优先检查
访问 404baseURL、public/ 是否上传到正确目录、Nginx root 路径
访问 403目录权限、SELinux、备案状态
样式 / 图片 404控制台看请求路径;检查 baseURL 与资源指纹
证书签发失败80 端口是否可达、域名是否解析到本机、Cloudflare 是否开橙云
改了文章不生效HTML 被缓存;Ctrl + F5,确认 Nginx 对 .html 设了 no-cache
中文显示异常配置 languageCode: zh-cn,文件本身用 UTF-8
日期错乱 / 文章排序怪front matter 的 date 带时区(如 +08:00)

多端写作实现

目标:在 PC、手机、任何浏览器上都能写,写完立刻发布,不出现「稿子散落在三个平台」的情况。

核心模型

Git 仓库是唯一的内容源,写作 = 提交,发布 = 推送。

任何设备改 content/**/*.md → commit → push → GitHub Actions(或本地脚本)执行 hugo → Pages / 服务器目录更新 → 几十秒后线上可见

只要守住这一点,多少端都只是「客户端」的差异。

仓库结构约定

myblog/ ├── archetypes/default.md # 新文章模板 ├── content/ │ └── posts/ │ └── hugo-deploy/ │ ├── index.md # 一篇文章一个目录(page bundle) │ └── cover.jpg # 图片跟着文章走 ├── static/ # 全站共用文件(favicon 等) ├── themes/PaperMod/ # submodule ├── hugo.yaml └── .github/workflows/hugo.yaml

三条约定,能省掉后面 90% 的图片路径问题:

  1. 一篇文章一个目录,正文叫 index.md,图片放同目录,引用写 ![](cover.jpg)。删文章时图片跟着目录一起删,手机上也不用记 static/images/xxx 这种长路径。
  2. 目录名用小写英文 slug(hugo-deploy),日期只写在 front matter 里。这样改日期不会改链接。
  3. 文章 URL 由目录名决定(/posts/hugo-deploy/),起名时想好,别上线后再改。

统一 front matter

编辑 archetypes/default.md,让所有端 hugo new 出来的骨架一致:

yaml
--- title: "{{ replace .File.ContentBaseName "-" " " | title }}" date: {{ .Date }} draft: true tags: [] categories: [] ---
  • draft: true 是安全阀:草稿不会被构建(hugo server -D 除外),可以先 push 再慢慢写,最后改成 false 才算发布。
  • date 统一带时区(+08:00),跨设备、跨时区也不会算错。
  • 手机端没有 hugo new 命令时,直接复制一篇已有文章的 front matter 改标题和日期。

手机端方案对比

方案平台优点代价
GitHub 网页 / GitHub Mobile全平台零安装;改错字、发短文最快;上传图片直接选相册长文编辑体验弱,Markdown 预览有限
Obsidian + Git 插件iOS / Android / 桌面本地 Markdown 库、双链、离线可写,插件可直接 commit & push手机端合并冲突要手动处理;附件路径需和 page bundle 约定对齐
Working CopyiOS完整 Git 客户端,clone / pull / push / diff 都有推送功能需付费解锁,Android 没有对应体验
Termux + gitAndroid可以本地 hugo server 预览,能力最全门槛最高,要自己配环境
IntelliJ IDEA / VS Code桌面编辑体验最好,内置 Git,可本地实时预览只在电脑前可用

实际最省心的组合:

  • 桌面:IntelliJ 或 VS Code 打开仓库,hugo server -D 实时预览,写完在 IDE 里提交推送。
  • 手机应急改字/发短记:GitHub 网页直接编辑 content/posts/xxx/index.md,Commit 即发布。
  • 手机长文:Obsidian 写,Git 插件推送;图片直接插入同目录,再统一压缩。

多端协作纪律

  • 写前先 pull,写完立刻 push。 两台设备同时挂着未提交的改动,是冲突的唯一来源。
  • 冲突以内容为准手动合并,不要 git push --force,它会把另一端的稿子直接抹掉。
  • 一次提交一件事,提交信息用 post: 文章标题,出问题能一眼定位到哪篇。
  • 手机流量有限,图片先压缩:宽 ≤ 1600px、jpg / webp、单张尽量 < 300KB。
  • 不要在手机上改主题、配置和 hugo.yaml;这类改动放桌面做,出问题好回滚。
  • 草稿阶段保持 draft: true,确定要发再改 false,避免写一半被 RSS 和搜索引擎抓到。

一键发布

桌面端脚本 deploy.sh(GitHub Pages 路线删掉最后一行 rsync):

bash
#!/usr/bin/env bash set -euo pipefail msg="${1:-post: 更新}" git pull --rebase hugo --minify --gc --cleanDestinationDir git add -A git commit -m "$msg" || echo "没有需要提交的改动" git push # 服务器路线才需要 rsync -avz --delete public/ deploy@1.2.3.4:/var/www/myblog/ echo "已发布 $(date '+%F %T')"

日常流程就三句话:桌面写完 → ./deploy.sh "post: 标题" → 手机上打开域名确认。手机端则只需要在 GitHub 页面上点一次 Commit,剩下的交给 Actions。

本文作者:Weee

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!