Building a Personal Blog with Hugo (2)

Picking up where we left off, the previous post briefly covered how to install and use Hugo. But stopping right there would feel a bit thin.

To ensure a good writing experience and stay focused on producing content, it’s worth spending more time up front — only then will you get twice the result with half the effort later. So this post goes deeper into optimization.

I’d like to thank the bigshot Sulv for the generous help. His blog: https://www.sulvblog.cn/


Hugo Directory Overview

First, let me introduce the directories that Hugo generates when creating a new site and what they do, so you have a sense of direction for the operations below. A website generated with Hugo contains the following files and folders:

  • archetypes: stores .md template files; this folder takes priority over the /archetypes folder in the theme
  • config.toml: the configuration file
  • content: stores all the content of the site
  • data: stores data files for templates to use
  • layouts: stores .html templates; this folder takes priority over the /layouts folder in the theme
  • static: stores static files such as images, css, and js; files in this directory are copied directly to /public, and this folder takes priority over the /static folder in the theme
  • themes: stores themes
  • public: stores the generated static files after running the hugo command

Steps

1. Edit config.yml

The config.yml file is the configuration for the whole site. It tells Hugo how to build the site, such as global parameters and menus. The file defaults to toml format; in the previous article, the -f yml argument in the Generate Site section of the commands changed the default configuration file to yaml format.

  • The default configuration provided by the official docs: Sample config.yml
  • For the meaning of the parameters, refer to the theme Wiki: Features, Variables, as well as the relevant content in the official Hugo docs: Configure Hugo.
  • The configuration file used here is the one given by Sulv; thanks again to Sulv. The content of the configuration file needs to be modified according to your actual situation; the main thing is to change site identifiers such as links and titles to your own. Also, logo.gif should be placed at static/img/logo.gif in the site’s root directory.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
baseURL: https://www.sulvblog.cn
# baseURL: https://www.sulvblog.cn  # Bound domain name
languageCode: zh-cn # en-us
title: Sulv's Blog
theme: PaperMod # Theme name, must match the folder under themes

enableInlineShortcodes: true
enableEmoji: true # Allow Emoji, recommended true
enableRobotsTXT: true # Let search engine crawlers index the site; recommended true

hasCJKLanguage: true # Auto-detect whether the content contains Chinese, Japanese, or Korean; enable it if your articles use many Chinese quotation marks

buildDrafts: false
buildFuture: false
buildExpired: false

#googleAnalytics: UA-123-45 # Google Analytics
# Copyright: Sulv

paginate: 10    # Number of articles shown per page on the home page

minify:
    disableXML: true
    # minifyOutput: true

permalinks:
  post: "/:title/"
  # post: "/:year/:month/:day/:title/"

defaultContentLanguage: zh # The language page displayed first at the top
defaultContentLanguageInSubdir: true

languages:
    zh:
      languageName: "Chinese"
      # contentDir: content/english
      weight: 1
      profileMode:
        enabled: true
        title: (〃'▽'〃)
        subtitle: "🧨Isn't it a pleasure to learn and practice what you have learned? Isn't it a joy to have friends come from afar?</br>👏🏼Welcome to Sulv's blog</br>👇Contact information"
        imageUrl: "img/logo.gif"
        imageTitle: 
        imageWidth: 150
        imageHeight: 150
        buttons:
          - name: 👨🏻‍💻Tech
            url: posts/tech
          - name: 📕Reading
            url: posts/read
          - name: 🏖Life
            url: posts/life
          # - name: 🌹Sulv's Blog
          #   url: https://www.xyming108.top
      menu:
        main:
          - identifier: search
            name: 🔍Search
            url: search
            weight: 1
          - identifier: home
            name: 🏠Home
            url: /
            weight: 2
          - identifier: posts
            name: 📚Posts
            url: posts
            weight: 3
          # - identifier: tech
          #   name: 👨🏻‍💻Tech articles
          #   url: posts/tech
          #   weight: 5
          # - identifier: life
          #   name: 🏖Life
          #   url: posts/life
          #   weight: 6
          - identifier: archives
            name: ⏱Timeline
            url: archives/
            weight: 20
          # - identifier: categories
          #   name: 🧩Categories
          #   url: categories
          #   weight: 30
          - identifier: tags
            name: 🔖Tags
            url: tags
            weight: 40
          - identifier: about
            name: 🙋🏻‍♂️About
            url: about
            weight: 50
          - identifier: links
            name: 🤝Friend Links
            url: links
            weight: 60

outputs:
    home:
        - HTML
        - RSS
        - JSON

params:
    env: production # to enable google analytics, opengraph, twitter-cards and schema.
    # description: "This is a pure blog..."
    author: Sulv
    # author: ["Me", "You"] # multiple authors

  
    defaultTheme: auto  # defaultTheme: light or  dark 
    disableThemeToggle: false
    DateFormat: "2006-01-02"
    ShowShareButtons: true
    ShowReadingTime: true
    # disableSpecialistPost: true
    displayFullLangName: true
    ShowPostNavLinks: true
    ShowBreadCrumbs: true
    ShowCodeCopyButtons: true
    hideFooter: false # Hide footer
    ShowWordCounts: true
    VisitCount: true

    ShowLastMod: true # Show article update time

    ShowToc: true # Show table of contents
    TocOpen: true # Automatically expand table of contents

    comments: true
    
    socialIcons:
        - name: github
          url: "https://github.com/xyming108"
        - name: twitter
          url:  "img/twitter.png"
        - name: facebook
          url: "https://www.facebook.com/profile.php?id=100027782410997"
        - name: instagram
          url: "img/instagram.png"
        - name: QQ
          url: "img/qq.png"
        - name: WeChat
          url: "img/wechat.png"
        # - name: Phone
        #   url: "img/phone.png"
        - name: email
          url: "mailto:[email protected]"
        - name: RSS
          url: "index.xml"

    # editPost:
    #     URL: "https://github.com/adityatelange/hugo-PaperMod/tree/exampleSite/content"
    #     Text: "Suggest Changes" # edit text
    #     appendFilePath: true # to append file path to Edit link

    # label:
    #     text: "Home"
    #     icon: icon.png
    #     iconHeight: 35

    # analytics:
    #     google:
    #         SiteVerificationTag: "XYZabc"

    assets:
        favicon: "img/logo.gif"
        favicon16x16: "img/logo.gif"
        favicon32x32: "img/logo.gif"
        apple_touch_icon: "logo.gif"
        safari_pinned_tab: "logo.gif"

    # cover:
    #     hidden: true # hide everywhere but not in structured data
    #     hiddenInList: true # hide on list pages and home
    #     hiddenInSingle: true # hide on single page

    fuseOpts:
        isCaseSensitive: false
        shouldSort: true
        location: 0
        distance: 1000
        threshold: 1
        minMatchCharLength: 0
        keys: ["title", "permalink", "summary"]

    twikoo:
      version: 1.4.11

taxonomies:
    category: categories
    tag: tags
    series: series

markup:
    goldmark:
        renderer:
            unsafe: true # Hugo escapes HTML code in Markdown files by default; enable this if you need it
    highlight:
        # anchorLineNos: true
        codeFences: true  
        guessSyntax: true
        lineNos: true
        # noClasses: false
        # style: monokai
        style: darcula

        # codeFences: code fence feature. This should generally be set to true, otherwise the code looks plain and colorless.
        # guessSyntax: guess the syntax. Recommended to set to true; if you do not specify a language, it will be matched automatically.
        # hl_Lines: highlighted line numbers. Generally not set, because you may want different lines highlighted in each code block.
        # lineNoStart: the starting line number; usually starts from 1.
        # lineNos: whether to show line numbers. I prefer showing them, so I set it to true.
        # lineNumbersInTable: use a table to format the line numbers and code instead of tags. This property is generally set to true.
        # noClasses: use class tags instead of inline styles.

privacy:
    vimeo:
        disabled: false
        simple: true

    twitter:
        disabled: false
        enableDNT: true
        simple: true

    instagram:
        disabled: false
        simple: true

    youtube:
        disabled: false
        privacyEnhanced: true

services:
    instagram:
        disableInlineCSS: true
    twitter:
        disableInlineCSS: true
        

2. Configure the 🔍 Search Page

Original reference: Search Page

PaperMod uses Fuse.js Basic to implement the search functionality

The search functionality is already enabled in the configuration file. You need to create a search.md containing the following content in the content/ directory in the site’s root. Additionally, you can customize it to your needs by referring to the theme documentation.

1
2
3
4
5
6
7
8
---
title: "🔍Search" # in any language you want
layout: "search" # is necessary
# url: "/archive"
# description: "Description for Search"
summary: "search"
placeholder: "Default text shown in the search box; modify it yourself"
---

To hide a specific page from search, add the following to its front matter (the content enclosed by the two --- lines at the top):

1
searchHidden: true

3. Configure the ⏱ Timeline Page

Original reference: Archives Layout

The ⏱ Timeline page is the Archives Layout. The archive functionality is already enabled in the configuration file. You need to create an archives.md containing the following content in the content/ directory in the site’s root. Additionally, you can customize it to your needs by referring to the theme documentation.

1
2
3
4
5
6
---
title: "⏱ Timeline"
layout: "archives"
url: "/archives/"
summary: archives
---

4. Configure the 🔖 Tags Page

  • The default language set in config.yml is Chinese, and support for other languages is not enabled. If needed, you can customize it yourself by referring to the official docs: Translations, Multilingual Mode . However, the 🔖 Tags page is not controlled by this setting, so you need to modify the corresponding html template yourself.
  • Also, regarding the default language: the theme supports multilingual, meaning it provides some translations for its built-in page templates, e.g. Home -> 主页. The files that provide multilingual support are in themes/PaperMod/i18n/, where zh.yaml is Simplified Chinese.
  • To prevent your modified files from being reverted when updating the theme, make the modifications in the site’s root directory, so they can override the theme’s original files.

Once you understand the three points above, you can start the following modifications.

4.1. Modify the default translations

Create an i18n/ folder in the site’s root, copy themes/PaperMod/i18n/zh.yaml to i18n/zh.yaml, and edit it

1
2
3
mkdir i18n \
  && cp themes/PaperMod/i18n/zh.yaml i18n/zh.yaml \
    && nano i18n/zh.yaml

Change it to the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- id: prev_page
  translation: "上一页"

- id: next_page
  translation: "下一页"

- id: read_time
  translation:
    one : "1 分钟"
    other: "{{ .Count }} 分钟"

- id: toc
  translation: "目录"

- id: translations
  translation: "语言"

- id: home
  translation: "🏠主页"

- id: edit_post
  translation: "编辑"

- id: code_copy
  translation: "复制"

- id: code_copied
  translation: "已复制!"

4.2. Modify the html template

The template file for 🔖 Tags is themes/PaperMod/layouts/_default/terms.html. Follow the same steps as above: create a layouts/_default/ folder in the site’s root, copy themes/PaperMod/layouts/_default/terms.html to layouts/_default/terms.html, and edit it

1
2
3
mkdir layouts/_default \
  && cp themes/PaperMod/layouts/_default/terms.html layouts/_default/terms.html \
    && nano layouts/_default/terms.html

Change it to the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{{- define "main" }}

{{- if .Title }}
<header class="page-header">
    {{- if eq .Title "Categories" }}
    <h1>🧩{{ .Title }}</h1>
    {{- end }}
    {{- if eq .Title "Tags" }}
        <h1>🔖{{ "Tags" }}</h1>
        <!-- <h1>🔖{{ .Title }}</h1> -->
    {{- end }}
    <!-- <h1>{{ .Title }}</h1> -->
    {{- if .Description }}
    <div class="post-description">
        {{ .Description }}
    </div>
    {{- end }}
</header>
{{- end }}

<!-- Original -->

<ul class="terms-tags">
    {{- $type := .Type }}
    {{- range $key, $value := .Data.Terms.Alphabetical }}
    {{- $name := .Name }}
    {{- $count := .Count }}
    {{- with $.Site.GetPage (printf "/%s/%s" $type $name) }}
    <li>
        <a href="{{ .Permalink }}">{{ .Name }} <sup><strong><sup>{{ $count }}</sup></strong></sup> </a>
    </li>
    {{- end }}
    {{- end }}
</ul>

{{- end }}{{/* end main */ -}}

5. Customize the front matter

Hugo provides a default template for generating articles. When you use hugo new content/posts/demo.md to generate a new page, it provides the most basic front matter, located at archetypes/default.md, with the following content:

1
2
3
4
5
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

This template only contains the three required parameters: title, date, and draft (whether it is a draft?). For writing convenience, here is a template provided by the bigshot Sulv, which I have slightly modified.

Create a new archetypes/post.md and edit it,

1
nano archetypes/post.md

Paste the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
lastmod: {{ .Date }}
author: ["Author"]
tags:
- Tag 1
- Tag 2
# summary->The summary shown on list pages; auto-generated from the first 70 characters by default; can be customized via this parameter; normally no need to set it
summary: ""
# description->An article description you write yourself; it is the web page intro that search engines display below the search result link; recommended to set it
description: ""
weight: # Enter 1 to pin the article to the top for display ordering; leave blank to sort by time by default
slug: ""
draft: false # Whether it is a draft
comments: true
showToc: true # Show table of contents
TocOpen: true # Automatically expand table of contents
hidemeta: false # Whether to hide the article's meta info, such as publish date and author
disableShare: true # Do not show the share bar at the bottom
showbreadcrumbs: true # Show the current path at the top
cover:
    image: ""
    caption: ""
    alt: ""
    relative: false
---

This content will appear in the summary

<!--\more--> # The "\" here is used for escaping; otherwise it won't display properly. Remove it when actually using it.

The main text starts here

6. Configure the 🙋🏻‍♂️ About Page

In the previous section we configured the article generation template. Below I’ll show how to use the template to generate the 🙋🏻‍♂️ About page (about.md).
First, generate about.md in the content/ directory

1
hugo new --kind post content/about.md

You’ll see an about.md with the template content; modify the front matter parameters according to the comments and your own needs.
Then, starting at the very bottom, write the body using Markdown syntax to complete your own 🙋🏻‍♂️ About page

7. Content Management from Novice to Pro

The Hugo Directory Overview section introduced the paths in the site and their purposes. So where do we put our finished articles? The answer is the content/ directory.

The official docs Content Management explain in detail everything you need to know about managing blog content. Below I’ll cover what we need to know for now.

  • Once the blog is live, everything we see is Markdown files in the content/ directory. The content/ directory is the top-level directory that manages all the site’s content; it generally serves as the site’s homepage, with an access URL of: http://example.com. Also, files like about.md, archives.md, etc. are usually stored here.
  • For a Markdown file created in the content/ directory, e.g. about.md, its access URL is: http://example.com/about/ . You may notice it has no extension. In Hugo the default behavior is to present content using Pretty URLs (Pretty URLs), i.e. the “.html” extension is omitted from links. If you have a peculiar taste, you can set uglyurls: true in config.yml to enable the Ugly URLs (Ugly URLs) environment variable, and the access URL will become: https://example.com/about.html/
  • For a directory created in the content/ directory, e.g. posts (by convention we store our blog articles under this path), its access URL is: http://example.com/posts/ , and other directories work the same way.
    • In my blog, I created the following paths:
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      
      content
      ├── about.md
      ├── archives.md
      ├── posts
      │   ├── _index.md
      │   ├── life
      │   │   └── _index.md
      │   ├── read
      │   │   └── _index.md
      │   └── tech
      │       ├── 1st.md
      │       ├── 2nd.md
      │       └── _index.md
      └── search.md
      
      Where posts/life/ corresponds to 🏖 Life; posts/read/ corresponds to 📕 Reading; posts/tech/ corresponds to 👨🏻‍💻 Tech.
    • Let me emphasize here: _index.md. The official reference is Index Pages. In short, this file exists as a custom index page. Here’s one I use as a reference:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      ---
      title: "📚Posts"
      # description: "Knowledge is learned; experience is summarized."
      hidemeta: true # Whether to hide the article's meta info, such as publish date and author
      ---
      
      Knowledge is learned; experience is summarized.
      
      <!--\more--> # The "\" here is used for escaping; otherwise it won't display properly. Remove it when actually using it.
      

Final Words

At this point, the basic configuration of Hugo is essentially complete, and you can focus on creating. If you need more freedom or have other needs, you can also do deep customization. In the next section, I’ll introduce some Hugo writing workflows, a few additional usage tips, and share the script I use to back up Hugo with a Tianyi cloud drive.

References