Hugo boasts a number of excellent themes, for academic themes, you can refer to https://github.com/Chen-Gary/hugo-theme-academic-old . For the installation and configuration of the academic theme, please refer to its README document. This article will delve into advanced Hugo configuration and customization. After reading this blog post, you will gain a deeper understanding of Hugo and be able to complete some customizations:

  • Adjusting article templates
  • Personalization - Integrating Live2D widget
  • Personalization - Supporting embedded Bilibili videos in articles
  • Internationalization & CJK

Article Templates

Generally, there are two ways to create new articles. One is to directly copy the existing file directory and make changes, and the other is to use the hugo new command. For example, you can use hugo new content post/{post-path}/index.md to create an article (the article directory for the academic theme is in content/post, while other themes might have it in content/posts). This command searches for templates to generate content in the following order:

  1. archetypes/post.md
  2. archetypes/default.md
  3. themes/academic/archetypes/post.md
  4. themes/academic/archetypes/default.md

When creating a blog site using the hugo new site {blog-path} command, the default generated directory includes the archetypes/default.md file with the following content:

+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++

This file content is in the Hugo Front matter format, which is toml. We test it using the command hugo new content post/test/index.md, and the generated article content is as follows:

+++
title = 'Test'
date = 2023-11-22T23:53:32+08:00
draft = true
+++

Remove archetypes/default.md and content/post/test/index.md, and run the command hugo new content post/test/index.md again. The newly generated article file content will be:

---
# Documentation: https://sourcethemes.com/academic/docs/managing-content/

title: "Test"
subtitle: ""
summary: ""
authors: []
tags: []
categories: []
date: 2023-11-22T23:58:38+08:00
lastmod: 2023-11-22T23:58:38+08:00
featured: false
draft: false

# Featured image
# To use, add an image named `featured.jpg/png` to your page's folder.
# Focal points: Smart, Center, TopLeft, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight.
image:
  caption: ""
  focal_point: ""
  preview_only: false

# Projects (optional).
#   Associate this post with one or more of your projects.
#   Simply enter your project's folder or file name without extension.
#   E.g. `projects = ["internal-project"]` references `content/project/deep-learning/index.md`.
#   Otherwise, set `projects = []`.
projects: []
---

This allows us to quickly customize the Front matter content.

The academic theme also supports various article templates, such as docs.md, publication, etc., which can be found in the themes/academic/archetypes directory. For example, to create a new Courses section, you can use the command hugo new content --kind docs courses/linux-programming/_index.md. This command applies the themes/academic/archetypes/docs.md layout, and you can view the effect locally by visiting http://localhost:1313/courses/ .

Personalization - Integrating Live2D Widget

This section demonstrates how to customize the theme by integrating a Live2D widget (although we are using the academic theme, who doesn’t love anime?).

Template Basic Structure

First, to customize the theme, you need to have a basic understanding of the Hugo theme structure. The academic theme structure is located in the themes/academic/layouts directory:

themes/academic/layouts
├── _default
│   └── _markup
├── authors
├── book
├── docs
├── partials
│   ├── comments
│   ├── functions
│   ├── jsonld
│   ├── marketing
│   └── widgets
├── project
├── publication
├── section
├── shortcodes
├── slides
├── talk
└── widget_page

Regarding the site content, it can be roughly divided into list pages and detail pages. List pages are like the course list page /courses/, while detail pages are single pages like /courses/example/. The themes/academic/layouts/_default/ directory is the template entry point, where:

  • baseof.html is the main framework of the site, containing the complete HTML, which loads other page components through Go HTML template syntax to form the final page
  • list.html is for list pages
  • single.html is for detail pages

Both list.html and single.html declare the main body of the page using define "main" and are then referenced in baseof.html using the code {{ block "main" . }}{{ end }}.

Of course, we can see that there is a lot of content in the layout directory. Hugo will load different layouts based on the site content directory structure and the type defined in the Front matter. For example, in the aforementioned courses directory, content/courses/example/_index.md defines type: docs, so the layout under the themes/academic/layouts/docs directory will be applied. The detailed loading mechanism and priority of this part are quite complex. If you are interested, you can refer to the official documentation https://gohugo.io/templates/lookup-order/ .

Adding Live2D Widget

Through the code in baseof.html, we can see that the header part of the site HTML is referenced from {{ partial "site_head" . }}. Most themes provide capabilities for user customization, and academic is no exception. By creating custom_head.html, we can extend the theme without modifying the template. The reference relationship is as follows:

┌─────────────────────────────────────┐
│                                     │
│         _default/baseof.html        │
│                                     │
└──────────────────┬──────────────────┘
                   │
                   │  {{ partial "site_head" . }}
                   │
┌──────────────────▼──────────────────┐
│                                     │
│        partials/site_head.html      │
│                                     │
└──────────────────┬──────────────────┘
                   │
                   │ {{ partial "custom_head" . }}
                   │
┌──────────────────▼──────────────────┐
│                                     │
│      partials/custom_head.html      │
│                                     │
└─────────────────────────────────────┘

Referring to the comments in themes/academic/layouts/partials/custom_head.html, we create the file layouts/partials/custom_head.html in the root directory of the site to override the default custom_head.html file loaded by the theme, and fill it with the following content:

<link rel="stylesheet" href="https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/waifu.css">
<script src="https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/live2d.min.js"></script>
<script src="https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/waifu-tips.js"></script>
<script>
    window.addEventListener('load', () => {
        // https://fastly.jsdelivr.net/gh/fghrsh/live2d_api/model_list.json
        localStorage.setItem("modelId", 2);  // 2 -> "bilibili-live/22"
        initWidget({
            waifuPath: "https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/waifu-tips.json",
            cdnPath: "https://fastly.jsdelivr.net/gh/stevenjoezhang/live2d-widget@latest/",
            tools: ["hitokoto", "asteroids", "switch-model", "switch-texture", "photo", "info", "quit"]
        });
    })
</script>

This code loads the style and model of the Live2D widget and executes the relevant initialization code. Ultimately, we can see the Bilibili Live2D widget in the lower left corner, with the following effect:

live2d bilibili model

Live2D widget, derived from Japanese “かんばんむすめ”, refers to a female shop assistant or mascot. The above sample code is referenced from https://github.com/stevenjoezhang/live2d-widget , under the GPL-3.0 license . The model cdnPath parameter is sourced from https://github.com/fghrsh/live2d_api/tree/master , and the model copyright belongs to the original author.

Personalization - Supporting Embedded Bilibili Videos in Articles

Markdown can include raw HTML for rendering, allowing us to embed Bilibili videos in the site by embedding HTML. Taking the video https://www.bilibili.com/video/BV1kE41147oo as an example, open the video and under it, you can find the sharing menu where you can locate the embed code section.

bilibili share

Click on it to copy the embed code, as follows:

<iframe src="//player.bilibili.com/player.html?aid=93495162&bvid=BV1kE41147oo&cid=159633574&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>

Of course, if we want to improve efficiency, we can use the shortcode feature provided by Hugo. Hugo itself provides some built-in shortcodes, such as figure, gist, etc. In this section, we will create a bilibili shortcode to achieve the ability to quickly embed Bilibili videos.

Create a file named layouts/shortcodes/bilibili.html in the root directory of the site, and fill it with the following content:

{{- $vid := .Get "id" | default (.Get 0) -}}
{{ $q := "" }}

{{ if strings.HasPrefix (lower $vid) "bv" }}
  {{ $q = querify "bvid" $vid }}
{{ else }}
  {{ $q = querify "aid" $vid }}
{{ end }}

<iframe style="width: 100%; aspect-ratio: 16 / 9;"
    src="//player.bilibili.com/player.html?{{ $q | safeURL }}&p=1"
    scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true">
</iframe>

The above code will generate the embed link for the Bilibili video based on the passed video id parameter and set the video width to 100% and the aspect ratio to 16:9 using CSS.

Then we can use the bilibili shortcode in Markdown. First, open the video you want to embed, find the BV-prefixed video ID after video/ in the video URL link, such as BV1kE41147oo in the link https://www.bilibili.com/video/BV1kE41147oo/, and then embed the code {{< bilibili BV1kE41147oo >}} in the article. The result is as follows:

For more information on using shortcodes, please refer to the Hugo official documentation https://gohugo.io/content-management/shortcodes/ .

Internationalization & CJK

As an excellent theme, academic supports internationalization configuration. If your site audience includes users from both domestic and overseas, you can enable language switching through internationalization configuration.

The internationalization configuration resides in the config/_default/languages.toml file, which by default only contains English configuration. Add the following code to the configuration file:

[zh]
  languageCode = "zh-Hans"
  contentDir = "content/zh"
  title = "学术"
  [zh.params]
    description = "测试中文站点"
  [[zh.menu.main]]
    name = "示例"
    url = "#hero"
    weight = 10

  [[zh.menu.main]]
    name = "文章"
    url = "#posts"
    weight = 20

  [[zh.menu.main]]
    name = "项目"
    url = "#projects"
    weight = 30

  [[zh.menu.main]]
    name = "出版物"
    url = "#featured"
    weight = 40

  [[zh.menu.main]]
    name = "课程"
    url = "courses/"
    weight = 50

  [[zh.menu.main]]
    name = "联系"
    url = "#contact"
    weight = 60

Also, create a zh directory under the content directory and copy the content from the original content directory into the zh directory. Now, you should see the language switching menu in the upper right corner of the site.

i18n

After adjusting the content of the corresponding articles in the zh directory, you can see the language switching effect of the site. Switch to Chinese, and you will see that the menu also changes to Chinese accordingly.

cn menu

Hugo is written in Go and uses the goldmark markdown library. By default, markdown will render line breaks as spaces during rendering. When using CJK languages (Chinese, Japanese, Korean), there might be spaces between some words after rendering, as shown below:

space in cjk

We can use the CJK extension provided by goldmark to configure the CJK rendering logic. The configuration file is located at config/_default/config.toml. Find the following section:

[markup]
  defaultMarkdownHandler = "goldmark"
  [markup.goldmark]
    [markup.goldmark.renderer]
      unsafe = true  # Enable user to embed HTML snippets in Markdown content.

Add the following code below it:

    [markup.goldmark.extensions.cjk]
      enable = true
      eastAsianLineBreaks = true
      escapedSpace = true

Save the file and observe the effect.