Installation Process
-
First, you need to install Hugo. Installing Hugo requires using a package manager. For Windows, Hugo can be installed directly with
Winget. Open the terminal and enter:winget install Hugo.Hugo.ExtendedIf you get a “not recognized as the name of cmdlet” message, check if
winget.exeexists inC:\Users\Username\AppData\Local\Microsoft\WindowsApps. If it does, add this directory to your environment variables. Otherwise, search forwingetin the Microsoft Store - this is Windows’ official package manager and usually comes with the system. -
Hugo’s tutorial explains how to create a project. For step 2 (config file) and step 3 (downloading themes), follow PaperMod’s guide.
Directory Structure
content: Stores blog contentdata: Not requiredlayouts: Custom HTMLpublic: Exported project filesresources: Custom CSS and JavaScriptstatic: Image storagethemes: Themes
Inside themes/PaperMod are PaperMod-related files. The main directories are:
assets: PaperMod’s CSS and JavaScript codelayouts: PaperMod’s HTML
Config.yml
It’s recommended to configure directly according to GitHub - xyming108/sulv-hugo-papermod
Creating Articles
Enter hugo new article_name.md in the terminal to create a markdown file in the content folder. You can modify the front matter to change the article’s properties.
Common front matter fields:
-
Basic fields
title: Page title, used in browser title, article title, etc.date: Creation date, e.g., 2024-11-15T20:00:00+00:00lastmod: Last modification time, commonly used to display update timedraft: Whether it’s a draft (true means draft, will be ignored when building the site)author: Author namedescription: Page description, used for SEO and summaryslug: Page-friendly URL fragmenturl: Custom page URLtype: Page type (e.g., post, page), determines template selection -
Categories and tags
categories: Page categories, e.g., [“Tech”, “Programming”]tags: Page tags, e.g., [“Hugo”, “Static Site Generator”] -
Page content control
summary: Page summary. If not specified, it will be auto-extracted from contentweight: Sort weight. Lower values appear firstaliases: Page aliases for URL redirects, e.g., ["/old-url/"]layout: Specify the layout template, e.g., single or listoutputs: Output formats. Default is HTML, can be JSON, AMP, etc.resources: Page resources for defining images, PDFs, etc. -
SEO and social
keywords: Page keywords, e.g., [“Hugo”, “Markdown”, “SEO”]canonical: Canonical URL, specifies the preferred page for search enginesimages: Image links for sharing on social platforms
Creating Subdirectories
You can use folders to organize directories. Here’s an example of my Chinese blog structure:
@Kunyang ➜ blog git(master) tree /F
D:.
│ _index.md
│
├───Life
│ ...
├───Tech
│ │ _index.md
│ │
│ ├───Web
│ │ Papermod.md
| | ...
│ │ _index.md
│ │...
│
└───Wool
...
Each level has an _index.md to manage the directory hierarchy. The file contains only front matter. Since I use multiple languages, don’t add a url field to the top level:
# zh/blog/_index.md
title: "Blog 📒"
date: 2022-06-11T21:59:32-04:00
draft: false
hidemeta: true
For other subdirectories, it’s recommended to add a url field to _index.md:
# zh/blog/tech/web/_index.md
title: "Web Building 🚧"
date: 2024-11-15T01:01:32-04:00
draft: false
hidemeta: true
url: "/zh/blog/tech/web/"
weight: 1
You can use the weight field for sorting.
Last Modified Date
You can modify post_meta.html and add:
{{- $scratch := newScratch }}
{{ $date := .Date }}
{{ $lastmod := "" }}
{{- if not .Lastmod.IsZero -}}
{{ $lastmod = .Lastmod }}
{{- end }}
{{- if not .Date.IsZero -}}
{{- $scratch.Add "meta" (slice (printf "<span title='%s'>%s</span>" (.Date) (.Date | time.Format (default "January 2, 2006" site.Params.DateFormat)))) }}
{{- end }}
{{- if ne $lastmod $date -}}
{{- $scratch.Add "meta" (slice (printf "<span title='%s'>%s%s</span>" (.Lastmod) (i18n "updated") (.Lastmod | time.Format (default "January 2, 2006" site.Params.DateFormat)))) }}
{{- end }}
...
{{- with ($scratch.Get "meta") }}
{{- delimit . " · " -}}
{{- end -}}
Local Preview
- Enter
hugo server -Din the terminal to start. It’s recommended to create a .bat file to execute this command. - After the server starts, open a browser and visit
localhost:1313for local preview.
Website Deployment
I use GitHub Pages.
- Create a repository on GitHub named
your_username.github.io - When local preview looks good, enter
hugo -F --cleanDestinationDirin the terminal. Output files will be generated in thepublicfolder. - Use the
publicfolder as the local repository for GitHub Pages. Each time you generate output files, push them to GitHub, and GitHub Pages will deploy automatically.
Custom Domain
It’s recommended to purchase a domain early to avoid reconfiguring when changing domains.
-
Click your avatar in the top-right corner of GitHub → Settings → Pages → Add a Domain
-
Enter your domain, e.g.,
kyxie.me. GitHub will generate a DNS TXT record (like a username and password) to verify you own the domain.
-
My domain was registered with Cloudflare. Go to kyxie.me → DNS → Records → Add Records and add three rules. The first two are:
Type Name Target CNAME @ kyxie.github.io CNAME www kyxie.github.io The third rule is Type TXT, with the Name generated by GitHub as the Record Name. Set TTL to Auto, and Content to the Value GitHub generated.
-
Then go to SSL/TLS → Edge Certificates → and check “Always Use HTTPS”
-
After configuration, go back to GitHub and click Verify. If successful, it will look like this:
-
Since my site is built with Hugo, add a file named
CNAME(no extension) in thestaticfolder with your domain namekyxie.meas content. Then publish and push to GitHub.
Multi-language Support
-
If you want multi-language support (Chinese and English, for example), create two folders in the
contentfolder, such asChineseandEnglish, placing content in their respective folders. -
In
config.yml, make the following changes:defaultContentLanguage: en defaultContentLanguageInSubdir: true languages: en: languageName: "English" contentDir: content/English zh: languageName: "中文" contentDir: content/Chinese
Search Functionality
-
Create
search.mdin both language folders (hugo new search.md), and modify the front matter to:title: "Search" date: ... draft: false layout: search -
Add to
config.yml:menu: main: -identifier: Search name: Search url: search weight: ...
Hover Effects
Primarily modify themes/PaperMod/assets/css/common/header.css.
-
Home link hover effect in top-left:
.logo a:hover { transition: 0.15s; color: grey; } -
Social media icons hover effect:
svg:hover { transition: 0.15s; } .social-icons a:nth-child(1) svg:hover{ color: #C84370 !important; } .social-icons a:nth-child(2) svg:hover { color: grey !important; } ...
Use nth-child to set hover colors for individual icons.
-
Dark mode and light mode toggle hover effect:
#moon:hover { transition: 0.15s; color: deepskyblue; } #sun:hover { transition: 0.15s; color: gold; } -
Menu link hover effect:
#menu a:hover { transition: 0.15s; color: grey; } -
Button hover effect in
themes/PaperMod/assets/css/common/profile-mode.css:.button:hover { -webkit-transform: scale(1.1); -moz-transform: scale(1.1); -ms-transform: scale(1.1); -o-transform: scale(1.1); /* box-shadow: 0 0 0 1px grey; */ transform: scale(1.1) translateZ(0) translate3d(0, 0, 0) rotate(0.01deg); }
Custom Social Media Icons
-
Use an SVG icon website like icons8 to find needed icons, e.g., WeChat
-
Use the Stroke option in the left menu to adjust line width
-
Click
download, thenSVG Embed, set the custom icon size to 24*24, and clickcopy HTML -
Paste the copied HTML in
themes/PaperMod/layouts/partials/svg.html -
Modify to use
fill=currentColor stroke=currentColorfor dark/light mode switching compatibility -
WeChat and Weibo icon examples:
{{- else if (eq $icon_name "wechat") -}} <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 50 50"> <g fill="currentColor" stroke="currentColor" stroke-width="2"> <path d="M 19 6 C 9.746094 6 2 12.359375 2 20.5 C 2 24.894531 4.292969 28.679688 7.835938 31.324219 L 5.179688 39.304688 L 13.472656 34.167969 C 15.1875 34.707031 17.082031 35 19 35 C 19.746094 35 20.472656 34.945313 21.195313 34.863281 C 23.378906 39.105469 28.328125 42 34 42 C 35.722656 42 37.316406 41.675781 38.796875 41.234375 L 45.644531 45.066406 L 43.734375 38.515625 C 46.3125 36.375 48 33.394531 48 30 C 48 23.789063 42.597656 18.835938 35.75 18.105469 C 34.398438 11.125 27.324219 6 19 6 Z M 19 8 C 26.308594 8 32.328125 12.351563 33.703125 18.011719 C 26.183594 18.148438 20 23.355469 20 30 C 20 31.019531 20.160156 32.003906 20.4375 32.941406 C 19.964844 32.980469 19.484375 33 19 33 C 17.101563 33 15.199219 32.710938 13.632813 32.15625 L 13.183594 32 L 8.820313 34.699219 L 10.1875 30.59375 L 9.5625 30.171875 C 6.082031 27.820313 4 24.445313 4 20.5 C 4 13.640625 10.65625 8 19 8 Z M 13 14 C 11.898438 14 11 14.898438 11 16 C 11 17.101563 11.898438 18 13 18 C 14.101563 18 15 17.101563 15 16 C 15 14.898438 14.101563 14 13 14 Z M 25 14 C 23.898438 14 23 14.898438 23 16 C 23 17.101563 23.898438 18 25 18 C 26.101563 18 27 17.101563 27 16 C 27 14.898438 26.101563 14 25 14 Z M 34 20 C 40.746094 20 46 24.535156 46 30 C 46 32.957031 44.492188 35.550781 42.003906 37.394531 L 41.445313 37.8125 L 42.355469 40.933594 L 39.105469 39.109375 L 38.683594 39.25 C 37.285156 39.71875 35.6875 40 34 40 C 27.253906 40 22 35.464844 22 30 C 22 24.535156 27.253906 20 34 20 Z M 29.5 26 C 28.699219 26 28 26.699219 28 27.5 C 28 28.300781 28.699219 29 29.5 29 C 30.300781 29 31 28.300781 31 27.5 C 31 26.699219 30.300781 26 29.5 26 Z M 38.5 26 C 37.699219 26 37 26.699219 37 27.5 C 37 28.300781 37.699219 29 38.5 29 C 39.300781 29 40 28.300781 40 27.5 C 40 26.699219 39.300781 26 38.5 26 Z"> </path> </g> </svg> {{- else if (eq $icon_name "weibo") -}} <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 172 172"> <g fill="currentColor" stroke="currentColor" stroke-width="4"> <path d="M120.4,20.64c-2.67406,0 -5.25406,0.26875 -7.74,0.71219c-1.86781,0.3225 -3.1175,2.10969 -2.795,3.9775c0.3225,1.88125 2.10969,3.13094 3.9775,2.80844c2.17688,-0.38969 4.35375,-0.61813 6.5575,-0.61813c20.93563,0 37.84,16.90438 37.84,37.84c0,4.52844 -0.83312,8.85531 -2.31125,12.91344c-0.45687,1.16906 -0.25531,2.48594 0.5375,3.45344c0.80625,0.9675 2.05594,1.42437 3.29219,1.19594c1.23625,-0.22844 2.24406,-1.11531 2.63375,-2.29781c1.73344,-4.75687 2.72781,-9.87656 2.72781,-15.265c0,-24.65781 -20.06219,-44.72 -44.72,-44.72zM120.4,41.28c-1.46469,0 -2.84875,0.14781 -4.16562,0.37625c-1.86781,0.33594 -3.13094,2.10969 -2.795,3.99094c0.3225,1.86781 2.10969,3.1175 3.9775,2.795c1.00781,-0.17469 2.00219,-0.28219 2.98312,-0.28219c9.54063,0 17.2,7.65938 17.2,17.2c0,2.05594 -0.37625,4.01781 -1.06156,5.87219c-0.645,1.78719 0.28219,3.7625 2.06938,4.4075c1.78719,0.645 3.7625,-0.26875 4.4075,-2.05594c0.92719,-2.55312 1.46469,-5.32125 1.46469,-8.22375c0,-13.26281 -10.81719,-24.08 -24.08,-24.08zM72.46844,42.6775c-11.04562,0 -27.50656,8.66719 -42.18031,23.07219c-14.76781,14.76781 -23.40813,30.24781 -23.40813,43.57781c0,25.9075 33.12344,41.3875 65.88406,41.3875c42.47594,0 70.90969,-24.46969 70.90969,-43.91375c0,-11.87875 -10.07812,-18.35562 -19.08125,-21.23125c-2.15,-0.72562 -3.60125,-1.08844 -2.52625,-3.96406c0.72563,-1.73344 1.77375,-5.01219 1.77375,-9.36594c0,-4.945 -3.44,-9.23156 -10.32,-9.9975c-0.79281,-0.08062 -2.13656,-0.14781 -3.82969,-0.14781c-5.6975,0 -15.48,0.71219 -22.52125,3.66844c0,0 -1.38406,0.57781 -2.49938,0.57781c-1.00781,0 -1.80062,-0.48375 -1.11531,-2.37844c2.52625,-7.91469 2.16344,-14.39156 -1.80063,-18.00625c-2.23062,-2.23062 -5.42875,-3.27875 -9.28531,-3.27875zM72.46844,49.5575c1.43781,0 3.37281,0.215 4.43438,1.26313l0.1075,0.12094l0.12094,0.1075c1.54531,1.41094 1.51844,5.61687 -0.09406,10.72312c-1.38406,3.99094 -0.09406,6.75906 0.81969,8.04906c1.51844,2.16344 4.00437,3.39969 6.81281,3.39969c2.28438,0 4.39406,-0.79281 5.14656,-1.11531c5.50938,-2.31125 13.88094,-3.13094 19.87406,-3.13094c1.53187,0 2.60687,0.05375 3.07719,0.1075c2.78156,0.30906 4.1925,1.37063 4.1925,3.15781c0,3.23844 -0.77937,5.61688 -1.23625,6.73219l-0.05375,0.1075l-0.04031,0.09406c-1.075,2.88906 -1.06156,5.50938 0.04031,7.80719c1.62594,3.39969 4.82406,4.46125 6.54406,5.03906l0.28219,0.09406c5.34813,1.70656 14.2975,5.88562 14.2975,14.68719c0,7.90125 -6.81281,17.65688 -18.90656,25.16844c4.77031,-5.54969 7.47125,-12.05344 7.47125,-19.05437c0,-21.01625 -24.37562,-37.47719 -55.48344,-37.47719c-31.10781,0 -55.47,16.46094 -55.47,37.47719c0,0.645 0.01344,1.27656 0.05375,1.90812c-0.45687,-1.76031 -0.69875,-3.60125 -0.69875,-5.49594c0,-11.16656 7.59219,-24.91312 21.33875,-38.65969c14.86188,-14.59313 29.48188,-21.11031 37.36969,-21.11031zM69.875,82.33156c26.84813,0 48.60344,13.69281 48.60344,30.59719c0,16.89094 -21.75531,30.58375 -48.60344,30.58375c-26.83469,0 -48.59,-13.69281 -48.59,-30.58375c0,-16.90437 21.75531,-30.59719 48.59,-30.59719zM66.27375,89.52063c-10.38719,0.08062 -20.47875,5.76469 -24.85937,14.52594c-5.40188,11.13969 -0.36281,23.38125 12.59094,27.33187c12.95375,4.31344 28.42031,-2.16344 33.82219,-14.02875c5.38844,-11.5025 -1.43781,-23.73062 -14.39156,-26.95562c-2.365,-0.61813 -4.77031,-0.88688 -7.16219,-0.87344zM71.63531,104.06c1.98875,0 3.60125,1.59906 3.60125,3.58781c0,1.98875 -1.6125,3.60125 -3.60125,3.60125c-1.98875,0 -3.60125,-1.6125 -3.60125,-3.60125c0,-1.98875 1.6125,-3.58781 3.60125,-3.58781zM56.47781,107.64781c1.12875,0.01344 2.2575,0.18812 3.31906,0.55094c4.60906,1.46469 6.02,5.83188 3.18469,9.47344c-2.4725,3.64156 -8.12969,5.45563 -12.38938,3.64156c-4.23281,-1.81406 -5.29437,-6.19469 -2.82187,-9.46c1.85437,-2.74125 5.30781,-4.24625 8.7075,-4.20594z"> </path> </g> <path d="" fill="none"></path> </svg>
Sidebar Table of Contents
See: GitHub - xyming108/sulv-hugo-papermod.
Markdown Rendering Styles
See: Tinkering with Hugo & PaperMod Theme - Dvel’s Blog
Traffic Analytics
I use Umami. See: Deploying Umami with Docker on Debian | Kunyang’s Blog
Embedding Bilibili, YouTube Videos or Presentations
See: GitHub - xyming108/sulv-hugo-papermod
Embedding Music Player
See: Hugo Embedding Music Player
Hide lyrics: lrc-type=0
Changing Global Font
First, find your preferred font and search for it on Google Fonts. My current article font is CodeNewRoman. Google Fonts generates HTML and CSS. Insert the HTML in themes/PaperMod/layouts/partials/extend_head.html and the CSS in themes/PaperMod/assets/css/extended/blank.css.
body {
font-family: 'Code New Roman', sans-serif;
font-size: 1rem;
line-height: 1.5;
margin: 0;
}
Changing Code Font
Similar to global fonts, but CSS is inserted in a different location.
.post-content pre, code {
font-family: 'Code New Roman', sans-serif;
max-height: 40rem;
}
Comment Feature
See: GitHub - xyming108/sulv-hugo-papermod
Gmail Configuration
-
Log in to Google Account, go to Security / Signing in to Google / 2-Step Verification / App passwords. Click Generate and save this 16-character password.
-
Configure in Twikoo:
-
SENDER_EMAIL: Your Gmail address
-
SENDER_NAME: Sender name, I use “Notification from Kunyang’s Blog”
-
SMTP_SERVICE: Gmail
-
SMTP_HOST: smtp.gmail.com
-
SMTP_PORT: 587
-
SMTP_SECURE: true
-
SMTP_USER: Your Gmail address
-
SMTP_PASS: 16-character app password
-
SMTP_SUBJECT: Email subject, I use “You have received a response from Kunyang’s Blog”
-
MAIL_TEMPLATE: Email template, mine is:
<div style="border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;"> <h2 style="border-bottom:1px solid #dddddd;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;"> You have received a new response from <a style="text-decoration:none;color:#12ADDB;" href="https://kyxie.github.io/" target="_blank">Kunyang's Blog</a> </h2> ${PARENT_NICK} Your comment: <div style="padding:0 12px 0 12px;margin-top:18px"> <div style="background-color:#f5f5f5;padding:10px 15px;margin:18px 0;word-wrap:break-word;"> ${PARENT_COMMENT} </div> <p> <strong>${NICK}</strong> says: </p> <div style="background-color:#f5f5f5;padding:10px 15px;margin:18px 0;word-wrap:break-word;"> ${COMMENT} </div> <p> Click <a style="text-decoration:none;color:#12ADDB;" href="${POST_URL}" target="_blank">to view the reply</a>, welcome to <a style="text-decoration:none;color:#12ADDB;" href="${SITE_URL}" target="_blank">${SITE_NAME}</a>。<br> </p> </div> </div>
-
After configuration, you’ll receive Gmail notifications for comments. For Outlook configuration, see: About Email Configuration? · twikoojs/twikoo · Discussion #249 (github.com).
Custom Email Reply Template
See: Customizing Twikoo Email Notification Template | Guo Le’s Blog
Code Highlighting
Modify two locations:
First in themes/PaperMod/assets/css/common/post-single.css:
.post-content pre code {
display: block;
margin: auto 0;
padding: 10px;
/* Main code color */
color: #abb2bf;
font-weight: 200;
overflow-x: auto;
word-break: break-all;
}
Second in themes/PaperMod/assets/css/hljs/an-old-hope.min.css (my custom modification):
/* Comments */
.hljs-comment,
.hljs-quote {
font-weight: 200;
color: #7f848e;
}
.hljs-deletion,
/* HTML tags */
.hljs-name,
.hljs-regexp,
.hljs-tag {
font-weight: 200;
color: #e06c75;
}
/* HTML attributes */
.hljs-tag {
font-weight: 200;
color: #d19a66;
}
/* CSS class names */
.hljs-template-variable,
.hljs-variable,
.hljs-selector-class,
.hljs-selector-id {
font-weight: 200;
color: #a9b600;
}
/* Built-in functions */
.hljs-built_in,
.hljs-builtin-name {
font-weight: 200;
color: #56b6c2;
}
/* Function parameters */
.hljs-params {
font-weight: 200;
color: #e5c07b;
}
/* Numbers */
.hljs-number {
font-weight: 200;
color: #d19a66;
}
/* CSS properties */
.hljs-attribute {
font-weight: 200;
color: #ee7c2b;
}
/* Strings */
.hljs-addition,
.hljs-bullet,
.hljs-symbol,
.hljs-string {
font-weight: 200;
color: #98c379;
}
/* Function names */
.hljs-section,
.hljs-title {
font-weight: 200;
color: #56b6c2;
}
/* Keywords */
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-link,
.hljs-meta,
.hljs-type {
font-weight: 200;
color: #c678dd;
}
.hljs {
display: block;
overflow-x: auto;
background: #1c1d21;
color: #c0c5ce;
padding: 0.5em;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: 700;
}
/* Selection background color */
.hljs ::selection,
.hljs::selection {
background-color: #3d4556;
}
Code Block Rounded Corners
Modify in themes/PaperMod/assets/css/common/post-single.css:
.post-content .highlight pre {
background-color: var(--theme) !important;
margin: 0;
}
Adding Friendlinks
See: GitHub - xyming108/sulv-hugo-papermod
Image Hosting
I previously used Imgur as an image host, but users in mainland China said they couldn’t access the images even with a VPN. Now that I have a domain, I plan to set up my own image hosting using Cloudflare.
-
First, download PicGo. Windows users can download the
.exefile from GitHub. Mac users should use Homebrew:brew install picgo --caskAfter installation, the PicGo icon appears in Launchpad. If it shows as damaged and asks to move to Trash, enter in the terminal:
sudo xattr -r -d com.apple.quarantine /Applications/PicGo.appAfter resolving the issue, the icon appears in the top menu bar (Windows shows it in the bottom-right corner).
-
Next, go to Cloudflare and create an R2 bucket named
img, selecting the US West region (because the CDN is also in US West).
-
Go to R2 Overview → top-right Manage R2 API Tokens. Give it a name and select Object Read and Write. You can optionally restrict it to a specific Bucket.
-
Save the information displayed: Token value, Access Key ID, Secret Access Key, and jurisdiction-specific endpoints. This page shows only once, so save the details.
-
Go to the
imgbucket → Settings. FindR2.dev subdomainand select “Allow”. -
Then bind a domain. Note you can’t use
kyxie.medirectly, but you can set a subdomain likeimg.kyxie.me. Cloudflare automatically adds a Record in DNS. -
In PicGo, go to Plugin settings, search for S3, install the S3 plugin. Note: Node.js environment is required.
-
In image hosting settings, enable Amazon S3 and configure as shown:
-
The custom domain field can be initially set to
img.kyxie.me. To make images accessible to users in mainland China, I use WebP Cloud Services to cache images for faster loading. -
After registering, click Create Proxy → select US Hillsboro, OR → Set Proxy Origin Url to
http://img.kyxie.me. After confirmation, the system generates a URL likehttp://xxx.webp.li. Paste this URL into PicGo’s custom domain. Final configuration looks like:
-
Your image hosting is now set up and PicGo is ready. After testing, users in mainland China can also view images.
-
To add watermarks, see WebP Cloud Services Watermark Documentation.