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,可以:

  1. 在你的网站的theme文件夹使用:

    git clone https://github.com/adityatelange/hugo-PaperMod themes/PaperMod --depth=1
    
  2. 或者,在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.com

  • languageCode: 网站语言代码,“zh-cn” 表示简体中文

  • title: 网站标题

  • theme: 使用的主题

  • buildDrafts: false 表示不设置草稿文章,所有文章都会被展示

  • 显示相关:

    • ShowBreadCrumbs: true 显示面包屑导航
    • ShowReadingTime: false 不显示文章阅读时间
    • ShowShareButtons: false 不显示分享按钮
    • ShowCodeCopyButtons: true 显示代码块的复制按钮
  • 搜索功能 (fuseOpts):

    • 配置了基于 Fuse.js 的搜索功能参数,包括不区分大小写、排序方式等
  • 主页信息 (homeInfoParams):

    • Title:标题
    • Content:内容
  • 主页社交媒体图标 (socialIcons):

  • 资源 (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,放在侧边就会方便许多。

  1. 在项目目录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 }}
    
  2. 在项目目录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) 生成&配置

  1. 点击右上角 头像
  2. 选择 Settings
  3. 左侧菜单选择 Developer settings
  4. 选择 Personal access tokensTokens (classic)
  5. 点击 Generate new tokenGenerate new token (classic)
  6. 设置 Token 信息:
    • Token name:输入名称(如 mywebsite
    • Expiration:选择 No expiration(永不过期)
    • 权限勾选:
      • repo(全仓库权限)
      • admin:repo_hook(仓库管理权限)
  7. 点击绿色按钮 Generate token
  8. 重要:立即复制生成的密钥并妥善保存(离开页面后将无法再次查看)
  9. 进入源仓库
    1. 点击settings
    2. 左侧Secrets and variables-Actions
    3. New repository secret
    4. 填写刚才的名称和密钥
    5. 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_tokenexternal_repository

5. SSH 密钥配置

  1. 检查是否已有 SSH Key

    • Windows: 进入 C:\Users\你的用户名\.ssh,查看是否存在 id_rsa(私钥)和 id_rsa.pub(公钥)文件。

      • 若有,说明已生成过 SSH Key,可直接使用。
      • 若无,需重新生成。
    • Linux:

      cd ~/.ssh
      ls
      

      检查是否存在 id_rsaid_rsa.pub 文件。

  2. 生成 SSH Key(若无) 运行以下命令(替换 xxx@xxx.com 为你的 GitHub 注册邮箱):

ssh-keygen -t rsa -C "xxx@xxx.com"
  • 连续按 3 次回车(使用默认路径,不设密码)。
  • 生成的文件:
    • id_rsa:私钥(切勿泄露)。
    • id_rsa.pub:公钥(需添加到 GitHub)。
  1. 将公钥添加到 GitHub
  2. 复制公钥内容(id_rsa.pub):
  3. 登录 GitHub → 点击头像 → SettingsSSH and GPG KeysNew SSH Key
  4. 测试 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。