github提供给每个用户一个网址,用户可以建立自己的静态网站。
一、Hugo
hugo是一个快速搭建网站的工具,由go语言编写。
1.安装hugo
到hugo的github标签页Tags · gohugoio/hugo选择一个版本,下载对应的安装包。比如hugo_extended_withdeploy_0.147.0_windows-amd64.zip
。
解压后,在根目录打开cmd,输入
hugo new site YourSiteName
为你的网站建立文件夹。YourSiteName
更改为你的网站的名字。
根目录会出现YourSiteName文件夹。
3.将根目录的hugo.exe复制到YourSiteName里。 在YourSiteName文件夹里打开cmd,输入
hugo server -D
会返回如下信息:
| EN
-------------------+-----
Pages | 11
Paginator pages | 0
Non-page files | 0
Static files | 0
Processed images | 0
Aliases | 2
Cleaned | 0
Built in 79 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop
在浏览器中输入http://localhost:1313/
,显示Page Not Found,说明服务器正常运行,但是此时网站还没有页面。
2.选择网站主题
在Hugo Themes选择你想要的theme,然后根据theme的安装说明操作就行了。 在此以PaperMod为例。官方安装教程界面:Installation · adityatelange/hugo-PaperMod Wiki
安装PaperMod,可以:
在你的网站的theme文件夹使用:
git clone https://github.com/adityatelange/hugo-PaperMod themes/PaperMod --depth=1
或者,在Tags · adityatelange/hugo-PaperMod选择版本,下载zip并解压到theme文件夹。
在你的网站的根文件夹里的hugo.yml
文件里添加
theme: ["PaperMod"]
3.新建一个笔记
在你的网站的根页面下使用cmd:
hugo new posts/first.md
YourSiteName/content/posts/first.md
就会建立,打开后,内容为:
---
date: '2025-05-01T18:41:05+08:00'
draft: true
title: 'first'
---
这三条短线围起来的是该笔记的属性。第一行是创建时间;第二行为false时表示草稿状态,改为true才会显示在网站中;第三行为该笔记的标题。之后还可以添加其他的属性。
打开http://localhost:1313/
,刷新后就能看到刚才创建的笔记了。如果没有就重新hugo server -D
。
你可以通过cmd,或者直接新建md文件来添加笔记。
4.定制个人博客
4.1添加菜单
在hugo.yml
文件中添加:
menu:
main:
- identifier: categories
name: categories
url: /categories/
weight: 10
- identifier: tags
name: tags
url: /tags/
weight: 20
- identifier: example
name: example.org
url: https://example.org
weight: 30
在网站的右上角就能看到菜单了
4.2置顶帖子
在笔记的md
文件里添加:
---
...
weight: 1
---
weight为正整数,表示笔记顺序。放到最顶上就设为1。
4.3hugo.yaml的可选项
hugo.yaml
是网站根目录的配置文件
# 基础配置
baseURL: https://Rook1eChan.github.io # 网站部署的根URL(GitHub Pages地址)
languageCode: zh-cn # 网站语言代码(简体中文)
title: Chan's Blog # 网站标题(
theme: ["PaperMod"] # 使用的主题(Hugo PaperMod主题)
buildDrafts: false # 构建时是否包含草稿(false表示不构建草稿)
# 主题参数配置
params:
# 布局控制
ShowBreadCrumbs: true # 显示面包屑导航
ShowReadingTime: false # 隐藏文章阅读时间
ShowShareButtons: false # 隐藏分享按钮
ShowCodeCopyButtons: true # 显示代码复制按钮
# 搜索功能配置(使用Fuse.js)
fuseOpts:
isCaseSensitive: false # 搜索不区分大小写
shouldSort: true # 对搜索结果排序
location: 0 # 匹配位置权重
distance: 1000 # 匹配距离阈值
threshold: 0.4 # 匹配相似度阈值
minMatchCharLength: 0 # 最小匹配字符长度
keys: ["title", "permalink", "summary", "content"] # 搜索的字段范围
# 首页欢迎信息
homeInfoParams:
Title: "你好,欢迎来到我的博客 \U0001F44B" # 标题
Content: "welcome!" # 内容
# 社交媒体图标
socialIcons:
- name: github # GitHub图标
url: "https://github.com/Rook1eChan" # GitHub个人主页
# 其他社交平台(已注释掉)
# - name: twitter
# url: "twitter.com"
# 网站图标配置
assets:
favicon: "/apple-touch-icon.png" # favicon路径
# 导航栏图标
label:
icon: /apple-touch-icon.png # 导航栏图标路径
iconHeight: 35 # 图标高度(像素)
# 输出格式配置
outputs:
home:
- HTML # 生成HTML页面
- RSS # 生成RSS订阅
- JSON # 生成JSON数据(可能用于搜索)
# 内容标记配置
markup:
highlight:
codeFences: true # 启用代码块高亮
guessSyntax: true # 自动检测代码语言
hl_Lines: '' # 高亮指定行(未设置)
lineNos: false # 不显示行号(与下面配置冲突)
lineNumbersInTable: true # 用表格布局行号(避免复制时带行号)
noClasses: false # 使用CSS类(必须为false)
style: github # 代码高亮主题(github、monokai、solarized-dark、dracula)
tabWidth: 4 # 代码缩进空格数
goldmark:
renderer:
unsafe: true # 允许渲染原始HTML/LaTeX
math: true # 支持数学公式
# 导航菜单配置
menu:
main:
# 已注释的分类和标签菜单
# - identifier: categories
# name: categories
# url: /categories/
# weight: 10
- identifier: search # 搜索菜单项
name: search # 菜单显示名称(英文,与标识不一致)
url: /search/ # 搜索页面路径
weight: 25 # 菜单项排序权重
baseURL
: 网站的基础URL,这里是 “https://Rook1eChan.github.io”,必须要写,不然导航出现错误。不要写example.comlanguageCode
: 网站语言代码,“zh-cn” 表示简体中文title
: 网站标题theme
: 使用的主题buildDrafts
: false 表示不设置草稿文章,所有文章都会被展示显示相关:
ShowBreadCrumbs
: true 显示面包屑导航ShowReadingTime
: false 不显示文章阅读时间ShowShareButtons
: false 不显示分享按钮ShowCodeCopyButtons
: true 显示代码块的复制按钮
搜索功能 (
fuseOpts
):- 配置了基于 Fuse.js 的搜索功能参数,包括不区分大小写、排序方式等
主页信息 (
homeInfoParams
):Title
:标题Content
:内容
主页社交媒体图标 (
socialIcons
):- 只启用了 GitHub 链接,指向 “https://github.com/Rook1eChan"
- Twitter 和 Facebook 链接
资源 (
assets
):- 设置了网站图标 (favicon) 为 “/apple-touch-icon.png”
标签 (
label
):- 设置了图标及其高度
指定了主页的输出格式为 HTML、RSS 和 JSON
主菜单 (
main
) 中只配置了一个 “搜索” 项:- 标识符: “搜索”
- 名称: “search”
- URL: “/search/”
- 权重: 25 (用于菜单项排序)
分类和标签菜单项自选
4.4更改字体
在 Hugo 项目下新建 assets/css/extended/custom.css
,写入
/* 全局正文字体 - Noto Sans Simplified Chinese */
body, article {
font-family: "Noto Sans SC", sans-serif;
}
/* 代码块字体 - 保持等宽字体(如 JetBrains Mono 或系统默认) */
pre, code {
font-family: "JetBrains Mono", monospace;
font-size: 0.9em; /* 可选:调整代码字体大小 */
}
在 themes/PaperMod/layouts/partials/head.html
中添加 Google Fonts 的链接
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&family=JetBrains+Mono&display=swap" rel="stylesheet">
可以去Browse Fonts - Google Fonts选字体。代码怎么写问AI。
4.5启用数学公式
以下方法可以使用$...$
表示行内公式,$$...$$
表示行间公式
在themes\PaperMod\layouts\partials\math.html"
里加上:
{{ if .Page.Params.math }}
<script>
MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']], // 行内公式:$...$ 或 \(...\)
displayMath: [['$$', '$$'], ['\\[', '\\]']] // 块级公式:$$...$$ 或 \[...\]
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
{{ end }}
在themes\PaperMod\layouts\_default\baseof.html"
里加上:
<head>
...
{{ if .Param "math" }}
{{ partialCached "math.html" . }}
{{ end }}
...
</head>
在hugo.yaml
里加上:
# 内容标记配置
markup:
goldmark:
renderer:
unsafe: true # 允许原始 HTML(某些数学公式需要)
extensions:
passthrough:
enable: true
delimiters:
block:
- ['$$', '$$'] # 块级公式:$$...$$
- ['\\[', '\\]'] # 或者 \[...\]
inline:
- ['$', '$'] # 行内公式:$...$
- ['\\(', '\\)'] # 或者 \(...\)
hugo.yaml
不要加math: true
,在每篇文章开头的参数里加上:
params:
math: true
4.6侧边目录
在config.yml
中,添加或修改params
对应的配置为以下内容:
params:
ShowToc: true
TocOpen: true
虽然PaperMod
原生就有目录,但是却是在顶部,便捷性几乎为0,放在侧边就会方便许多。
在项目目录
layouts/partials
下添加toc.html
文件,内容如下:{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}} {{- $has_headers := ge (len $headers) 1 -}} {{- if $has_headers -}} <aside id="toc-container" class="toc-container wide"> <div class="toc"> <details {{if (.Param "TocOpen") }} open{{ end }}> <summary accesskey="c" title="(Alt + C)"> <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span> </summary> <div class="inner"> {{- $largest := 6 -}} {{- range $headers -}} {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{- if lt $headerLevel $largest -}} {{- $largest = $headerLevel -}} {{- end -}} {{- end -}} {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} {{- $.Scratch.Set "bareul" slice -}} <ul> {{- range seq (sub $firstHeaderLevel $largest) -}} <ul> {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}} {{- end -}} {{- range $i, $header := $headers -}} {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{/* get id="xyz" */}} {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }} {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}} {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }} {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}} {{- if ne $i 0 -}} {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}} {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}} {{- if gt $headerLevel $prevHeaderLevel -}} {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}} <ul> {{/* the first should not be recorded */}} {{- if ne $prevHeaderLevel . -}} {{- $.Scratch.Add "bareul" . -}} {{- end -}} {{- end -}} {{- else -}} </li> {{- if lt $headerLevel $prevHeaderLevel -}} {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}} {{- if in ($.Scratch.Get "bareul") . -}} </ul> {{/* manually do pop item */}} {{- $tmp := $.Scratch.Get "bareul" -}} {{- $.Scratch.Delete "bareul" -}} {{- $.Scratch.Set "bareul" slice}} {{- range seq (sub (len $tmp) 1) -}} {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}} {{- end -}} {{- else -}} </ul> </li> {{- end -}} {{- end -}} {{- end -}} {{- end }} <li> <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{- else }} <li> <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a> {{- end -}} {{- end -}} <!-- {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}} --> {{- $firstHeaderLevel := $largest }} {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }} </li> {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}} {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }} </ul> {{- else }} </ul> </li> {{- end -}} {{- end }} </ul> </div> </details> </div> </aside> <script> let activeElement; let elements; window.addEventListener('DOMContentLoaded', function (event) { checkTocPosition(); elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]'); // Make the first header active activeElement = elements[0]; const id = encodeURI(activeElement.getAttribute('id')).toLowerCase(); document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active'); }, false); window.addEventListener('resize', function(event) { checkTocPosition(); }, false); window.addEventListener('scroll', () => { // Check if there is an object in the top half of the screen or keep the last item active activeElement = Array.from(elements).find((element) => { if ((getOffsetTop(element) - window.pageYOffset) > 0 && (getOffsetTop(element) - window.pageYOffset) < window.innerHeight/2) { return element; } }) || activeElement elements.forEach(element => { const id = encodeURI(element.getAttribute('id')).toLowerCase(); if (element === activeElement){ document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active'); } else { document.querySelector(`.inner ul li a[href="#${id}"]`).classList.remove('active'); } }) }, false); const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10); const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10); const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10); function checkTocPosition() { const width = document.body.scrollWidth; if (width - main - (toc * 2) - (gap * 4) > 0) { document.getElementById("toc-container").classList.add("wide"); } else { document.getElementById("toc-container").classList.remove("wide"); } } function getOffsetTop(element) { if (!element.getClientRects().length) { return 0; } let rect = element.getBoundingClientRect(); let win = element.ownerDocument.defaultView; return rect.top + win.pageYOffset; } </script> {{- end }}
在项目目录
assets/css/extended
下添加blank.css
文件,内容如下::root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } .toc { margin: 0 2px 40px 2px; border: 1px solid var(--border); background: var(--entry); border-radius: var(--radius); padding: 0.4em; } .toc-container.wide { position: absolute; height: 100%; border-right: 1px solid var(--border); left: calc((var(--toc-width) + var(--gap)) * -1); top: calc(var(--gap) * 2); width: var(--toc-width); } .wide .toc { position: sticky; top: var(--gap); border: unset; background: unset; border-radius: unset; width: 100%; margin: 0 2px 40px 2px; } .toc details summary { cursor: zoom-in; margin-inline-start: 20px; padding: 12px 0; } .toc details[open] summary { font-weight: 500; } .toc-container.wide .toc .inner { margin: 0; } .active { font-size: 110%; font-weight: 600; } .toc ul { list-style-type: circle; } .toc .inner { margin: 0 0 0 20px; padding: 0px 15px 15px 20px; font-size: 16px; /*目录显示高度*/ max-height: 83vh; overflow-y: auto; } .toc .inner::-webkit-scrollbar-thumb { /*滚动条*/ background: var(--border); border: 7px solid var(--theme); border-radius: var(--radius); } .toc li ul { margin-inline-start: calc(var(--gap) * 0.5); list-style-type: none; } .toc li { list-style: none; font-size: 0.95rem; padding-bottom: 5px; } .toc li a:hover { color: var(--secondary); }
二、在GitHubPage部署网站
基本思路:建立两个仓库,一个网站仓库负责展示页面,另一个源仓库负责存储源码、更新内容并自动更新同步到网站仓库。
1.建立网站仓库
在Github页面点击最上面的加号-New repository
Repository name
填写 你的GitHub用户名.github.io
,这样GitHub才会把它识别为网站仓库
选择Public
点击绿色的Create repository
2.建立源仓库
同上建立仓库,随便命名,Public或Private都行
这里我命名为mywebsite
3.GitHub 个人访问令牌 (Token) 生成&配置
- 点击右上角 头像
- 选择
Settings
- 左侧菜单选择
Developer settings
- 选择
Personal access tokens
→Tokens (classic)
- 点击
Generate new token
→Generate new token (classic)
- 设置 Token 信息:
- Token name:输入名称(如
mywebsite
) - Expiration:选择
No expiration
(永不过期) - 权限勾选:
- ✅
repo
(全仓库权限) - ✅
admin:repo_hook
(仓库管理权限)
- ✅
- Token name:输入名称(如
- 点击绿色按钮
Generate token
- 重要:立即复制生成的密钥并妥善保存(离开页面后将无法再次查看)
- 进入源仓库
- 点击
settings
- 左侧
Secrets and variables-Actions
New repository secret
- 填写刚才的名称和密钥
- Add sercet
- 点击
4.配置workflow脚本
在本地网站根目录新建文件夹及文件.github/workflows/hugo.yaml
写入:
name: github pages # 名字自取
on:
push:
branches:
- main
jobs:
deploy: # 任务名自取
runs-on: ubuntu-latest # 在什么环境运行任务
steps:
- uses: actions/checkout@v2 # 引用actions/checkout这个action,与所在的github仓库同名
with:
submodules: true # Fetch Hugo themes (true OR recursive) 获取submodule主题
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo # 步骤名自取
uses: peaceiris/actions-hugo@v2 # hugo官方提供的action,用于在任务环境中获取hugo
with:
hugo-version: 'latest' # 获取最新版本的hugo
extended: true
- name: Build
run: hugo --minify # 使用hugo构建静态网页
- name: Deploy
uses: peaceiris/actions-gh-pages@v3 # 一个自动发布github pages的action
with:
# github_token: ${{ secrets.GITHUB_TOKEN }} 该项适用于发布到源码相同repo的情况,不能用于发布到其他repo
external_repository: Rook1eChan/Rook1eChan.github.io # 发布到哪个repo
personal_token: ${{ secrets.MYWEBSITE2 }} # 发布到其他repo需要提供上面生成的personal access token
publish_dir: ./public # 注意这里指的是要发布哪个文件夹的内容,而不是指发布到目的仓库的什么位置,因为hugo默认生成静态网页到public文件夹,所以这里发布public文件夹里的内容
publish_branch: main # 发布到哪个branch
只需要更改personal_token
和external_repository
5. SSH 密钥配置
检查是否已有 SSH Key
Windows: 进入
C:\Users\你的用户名\.ssh
,查看是否存在id_rsa
(私钥)和id_rsa.pub
(公钥)文件。- 若有,说明已生成过 SSH Key,可直接使用。
- 若无,需重新生成。
Linux:
cd ~/.ssh ls
检查是否存在
id_rsa
和id_rsa.pub
文件。
生成 SSH Key(若无) 运行以下命令(替换
xxx@xxx.com
为你的 GitHub 注册邮箱):
ssh-keygen -t rsa -C "xxx@xxx.com"
- 连续按 3 次回车(使用默认路径,不设密码)。
- 生成的文件:
id_rsa
:私钥(切勿泄露)。id_rsa.pub
:公钥(需添加到 GitHub)。
- 将公钥添加到 GitHub
- 复制公钥内容(
id_rsa.pub
): - 登录 GitHub → 点击头像 → Settings → SSH and GPG Keys → New SSH Key。
- 测试 SSH 连接 在终端运行:
ssh -T git@github.com
- 若显示
Hi 你的用户名!
,说明配置成功。
之后clone或push时都选择SSH地址,而不是https地址。
6.上传
本地网站根目录使用cmd,git@github.com:XXX/mywebsite.git
改为源仓库地址:
git init
git add .
git remote add origin git@github.com:XXX/mywebsite.git
git commit -m "Update"
git push -u origin main
然后在源仓库的Action下,能看到正在Deploy,变绿色说明成功。此时网站仓库已自动更新了内容。
进入网站仓库-settings-Pages
-Build and deployment
选择Deploy from a branch
刚才workflow
脚本里写的是main
,这里就选择main
然后进入xxx.github.io,就可以看到你的网站了!🎉
三、如何更新网站内容
不要在github和本地同时更改内容!不然会导致内容不同步,无法push。
最好是一直都在本地更改,然后push到源仓库。
1.本地更改后,比如新建了笔记,在网站根目录使用cmd: 不要复制注释!
git init //初始化git文件夹
git add . //添加变更内容
git remote add origin git@github.com:XXX/mywebsite.git //最后一项改为源仓库的地址,如果使用ssh连接的就复制ssh地址
git commit -m "new" //设置本次操作的名称,new可以随便改
git push -u origin main //把本地文件push到github上,增量更新
常见问题:
git push -u origin main的时候报错: error: src refspec master does not match any error: failed to push some refs to ‘github.com:Rook1eChan/mywebsite.git’
使用git branch -a,查看branch的名称是不是main。如果是master,就把main改为master。
To github.com:Rook1eChan/mywebsite.git ! [rejected] main -> main (fetch first) error: failed to push some refs to ‘github.com:Rook1eChan/mywebsite.git’ hint: Updates were rejected because the remote contains work that you do not
说明你的GitHub和本地不同步。不建议强制合并,可以把GitHub整个repo clone到本地另一个文件,把变化的文件手动更改,再把新文件夹push上去。
感谢GitHub、Hugo和Deekseek。