Enhancing Hugo Project
In this article we will see some advanced customization done to the basic Hugo project that was created here.
Once we have the basic project created in Hugo and hosted on GitHub Pages, we can look at customizing the project, for the ease of use.
In this article, I will focus on some of the customizations, I have used to make the project look and feel better and also increase the ease of use, as I create more posts.
NOTE:
- When we have to update the files that are existing in Themes folder, it is always a good practice to copy that file into the equivalent path of our local root folder, for updating.
- Hugo, when it has to fetch a file, it will always first search the local folder and then the Themes folder.
- I am using Mainroad theme and hence all the updates shown below are relevant to that theme. If you are using different theme, some of the file path and attributes may vary.
Auto-numbering to Headers and TOC in Pages
When you generate a website using Hugo, it doesn’t provide any out of the box Auto Numbering support for Headings, Sub-Headings and TOC in Hugo Pages.
Follow these steps to enable Auto Numbering feature in your Hugo website:
Add autonumbering
property to all our pages
First of all, add a custom property autonumbering
to all our Hugo pages.
We can add this to all our pages in 2 ways:
- We can also add this to the
config.toml
file, so that it is available for every pages.
[Params]
...
autonumbering = true
...
...
- By adding it to the FrontMatter of the every page or by adding to the
default.md
template.
---
...
autonumbering: true
---
When we add it to the config.toml
file, it will be available for every page, by default and then we can enable or disable auto-numbering for a specific page using this property on that page’s FrontMatter.
Add CSS Class to single.html
Next, we are going to edit layouts/_default/single.html
file and add a condition on article element to add autonumbering
CSS class based on autonumbering
property is enabled or disabled.
1{{ define "main" }}
2<main>
3 <article class="post" {{- if .Param "autonumbering" }} autonumbering {{- end }}>
4 <header>
5 ...
6 </header>
7 ...
8 <footer>
9 ...
10 </footer>
11 </article>
12</main>
13{{ end }}
Add CSS Styles to style.css
At last, add the following CSS Styles snippet in the CSS files located at /assets/style.css
folder.
1/* Start of Auto Numbering */
2body {counter-reset: h2}
3h2 {counter-reset: h3}
4h3 {counter-reset: h4}
5h4 {counter-reset: h5}
6
7article[autonumbering] h2:before {counter-increment: h2; content: counter(h2) ". "}
8article[autonumbering] h3:before {counter-increment: h3; content: counter(h2) "." counter(h3) ". "}
9article[autonumbering] h4:before {counter-increment: h4; content: counter(h2) "." counter(h3) "." counter(h4) ". "}
10
11article[autonumbering] .toc__menu ul { counter-reset: item }
12article[autonumbering] .toc__menu li a:before { content: counters(item, ".") ". "; counter-increment: item }
13/* End of Auto Numbering */
Adding Menus
When we generate a website using Hugo, we generally show a menu header on website page and other pages.
Sometime we need the support for nested menu structure which is not readily available with Hugo.
Follow these steps to enable menus and sub-menus feature in your Hugo website:
Update config.toml
Let us update config.toml
with menu and submenu configuration to generate a nested menu structure.
1[menu]
2 [[menu.main]]
3 identifier = "home"
4 name = "Home"
5 url = "/"
6 weight = 1
7 [[menu.main]]
8 identifier = "category"
9 name = "Category"
10 url = "/category"
11 weight = 2
12 [[menu.main]]
13 identifier = "category1"
14 name = "Category1"
15 url = "/tags/category1"
16 parent = "category"
17 weight = 1
18 [[menu.main]]
19 identifier = "category2"
20 name = "Category2"
21 url = "/tags/category2"
22 parent = "category"
23 weight = 2
24 [[menu.main]]
25 identifier = "category3"
26 name = "Category3"
27 url = "/tags/category3"
28 parent = "category"
29 weight = 3
30 [[menu.main]]
31 identifier = "about"
32 name = "About Us"
33 url = "/about/"
34 weight = 4
Lets us understand the fields in [[menu.main]]
:
- identifier should be unique name of menu within the menu structure.
- name should be the name which you want to display on the menu item.
- url is a relative URL which will be opened when you click on the menu item.
- weight is for sequencing the menu items. Menu items with less weight appear first in the menu.
- parent is used in submenu item only to tell who is the parent of the submenu. It uses the identifier of the parent.
Create layouts/partials/menu.html
This is the default menu.html
we get in the theme folder.
1{{- if .Site.Menus.main }}
2<nav class="menu">
3 <button class="menu__btn" aria-haspopup="true" aria-expanded="false" tabindex="0">
4 <span class="menu__btn-title" tabindex="-1">{{ T "menu_label" }}</span>
5 </button>
6 <ul class="menu__list">
7 {{- $currentNode := . }}
8 {{- range .Site.Menus.main }}
9 {{- if .Name }}
10 <li class="menu__item{{ if or ($currentNode.IsMenuCurrent "main" .) ($currentNode.HasMenuCurrent "main" .) }} menu__item--active{{ end }}">
11 <a class="menu__link" href="{{ .URL }}">
12 {{ .Pre }}
13 <span class="menu__text">{{ .Name }}</span>
14 {{ .Post }}
15 </a>
16 </li>
17 {{- end }}
18 {{- end }}
19
20 </ul>
21</nav>
22{{ else -}}
23<div class="divider"></div>
24{{- end }}
This does not support sub-menu categorization.
Copy the menu.html
from /themes/<<UR-THEME>>/layouts/partial/
to /layouts/partial/
and update as given below, to include sub-menus
1{{- if .Site.Menus.main }}
2<nav class="menu">
3 <button class="menu__btn" aria-haspopup="true" aria-expanded="false" tabindex="0">
4 <span class="menu__btn-title" tabindex="-1">{{ T "menu_label" }}</span>
5 </button>
6 <ul class="menu__list">
7 {{- $currentNode := . }}
8 {{- range .Site.Menus.main }}
9 {{- if .Name }}
10 {{- if .HasChildren }}
11 <li class="menu__item menu__dropdown{{ if or ($currentNode.IsMenuCurrent "main" .) ($currentNode.HasMenuCurrent "main" .) }} menu__item--active{{ end }}">
12 <a class="menu__link" href="{{ .URL }}">
13 {{ .Pre }}
14 <span class="menu__text">{{ .Name }}</span>
15 <label class="drop-icon" for="{{ .Name }}">▾</label>
16 {{ .Post }}
17 </a>
18 <input type="checkbox" id="{{ .Name }}">
19 <ul class="submenu__list">
20 {{ range .Children }}
21 <li class="menu__item{{ if or ($currentNode.IsMenuCurrent "main" .) ($currentNode.HasMenuCurrent "main" .) }} menu__item--active{{ end }}">
22 <a class="menu__link" href="{{ .URL }}">
23 {{ .Pre }}
24 <span class="menu__text">{{ .Name }}</span>
25 {{ .Post }}
26 </a>
27 </li>
28 {{ end }}
29 </ul>
30 </li>
31 {{- else }}
32 <li class="menu__item{{ if or ($currentNode.IsMenuCurrent "main" .) ($currentNode.HasMenuCurrent "main" .) }} menu__item--active{{ end }}">
33 <a class="menu__link" href="{{ .URL }}">
34 {{ .Pre }}
35 <span class="menu__text">{{ .Name }}</span>
36 {{ .Post }}
37 </a>
38 </li>{{- end }}
39 {{- end }}
40 {{- end }}
41 </ul>
42</nav>
43{{ else -}}
44<div class="divider"></div>
45{{- end }}
Update assets/css/styles.css
Include the css required for styling the Main Menus and Sub Menus. This is an important step, without which Menus and Submenus will not work the way we want them to.
1/* Start of Main Menu and Submenu */
2.no-js .menu__btn {
3 display: none;
4 }
5
6 .menu__btn {
7 display: block;
8 width: 100%;
9 padding: 0;
10 font: inherit;
11 color: #fff;
12 background: #2a2a2a;
13 border: 0;
14 outline: 0;
15 }
16
17 .menu__btn-title {
18 position: relative;
19 display: block;
20 padding: 10px 15px;
21 padding: 0.625rem 0.9375rem;
22 font-weight: 700;
23 text-align: right;
24 text-transform: uppercase;
25 cursor: pointer;
26 -webkit-user-select: none;
27 -moz-user-select: none;
28 -ms-user-select: none;
29 -o-user-select: none;
30 user-select: none;
31 }
32
33 :focus > .menu__btn-title {
34 box-shadow: inset 0 0 1px 3px #e22d30;
35 }
36
37 button:not(:-moz-focusring):focus > .menu__btn-title {
38 box-shadow: none;
39 }
40
41 .menu__btn:focus,
42 .menu__btn-title:focus {
43 outline: 0;
44 }
45
46 .js .menu__btn--active {
47 color: #e22d30;
48 }
49
50 .menu__list,
51 .submenu__list {
52 list-style: none;
53 background: #2a2a2a;
54 }
55
56 .menu__item:hover > a {
57 background: #e22d30;
58 }
59
60 .menu__item:first-child {
61 border: 0;
62 }
63
64 .menu__item--active {
65 background: #e22d30;
66 }
67
68 .menu__link {
69 display: block;
70 padding: 10px 15px;
71 padding: 0.625rem 0.9375rem;
72 font-weight: 700;
73 color: #fff;
74 text-transform: uppercase;
75 }
76
77 .menu__list .menu__item .submenu__list {
78 background: #2a2a2a;
79 visibility: hidden;
80 opacity: 0;
81 position: absolute;
82 max-width: 15rem;
83 transition: all 0.5s ease;
84 border-top: 5px solid #e22d30;
85 display: none;
86 }
87
88 .menu__item.menu__dropdown input[type="checkbox"] {
89 display: none;
90 }
91
92 .menu__list .menu__item:hover > .submenu__list,
93 .menu__list .menu__item:focus-within > .submenu__list,
94 .menu__list .menu__item .submenu__list:hover,
95 .menu__list .menu__item .submenu__list:focus {
96 visibility: visible;
97 opacity: 1;
98 display: block;
99 }
100
101 .menu__link:hover {
102 color: #fff;
103 }
104
105 .js .menu__list {
106 position: absolute;
107 z-index: 1;
108 width: 100%;
109 visibility: hidden;
110 -webkit-transform: scaleY(0);
111 transform: scaleY(0);
112 -webkit-transform-origin: top left;
113 transform-origin: top left;
114 }
115
116 .js .menu__list--active {
117 visibility: visible;
118 border-top: 1px solid rgba(255, 255, 255, 0.1);
119 border-bottom: 1px solid rgba(255, 255, 255, 0.1);
120 -webkit-transform: scaleY(1);
121 transform: scaleY(1);
122 }
123
124 .menu__list--transition {
125 transition: visibility 0.15s ease, transform 0.15s ease,
126 -webkit-transform 0.15s ease;
127 }
128
129 @media screen and (min-width: 767px) {
130 .menu {
131 border-bottom: 5px solid #e22d30;
132 }
133
134 .menu__btn {
135 display: none;
136 }
137
138 .menu__list,
139 .js .menu__list {
140 position: relative;
141 display: -webkit-flex;
142 display: flex;
143 -webkit-flex-wrap: wrap;
144 flex-wrap: wrap;
145 visibility: visible;
146 border: 0;
147 -webkit-transform: none;
148 transform: none;
149 }
150
151 .menu__item {
152 border-left: 1px solid rgba(255, 255, 255, 0.1);
153 }
154 }
155
156 @media screen and (max-width: 767px) {
157 .menu__item.menu__dropdown .drop-icon {
158 position: absolute;
159 right: 1rem;
160 top: auto;
161 }
162
163 .menu__item.menu__dropdown input[type="checkbox"] + .submenu__list {
164 display: none;
165 }
166
167 .menu__item.menu__dropdown input[type="checkbox"]:checked + .submenu__list {
168 border: none;
169 padding-left: 20px;
170 visibility: visible;
171 opacity: 1;
172 display: block;
173 position: relative;
174 max-width: 100%;
175 }
176 }
177
178 @media screen and (max-width: 620px) {
179 .menu__item.menu__dropdown .drop-icon {
180 position: absolute;
181 right: 1rem;
182 top: auto;
183 }
184
185 .menu__item.menu__dropdown input[type="checkbox"] + .submenu__list {
186 display: none;
187 }
188
189 .menu__item.menu__dropdown input[type="checkbox"]:checked + .submenu__list {
190 border: none;
191 padding-left: 20px;
192 visibility: visible;
193 opacity: 1;
194 display: block;
195 position: relative;
196 max-width: 100%;
197 }
198 }
199/* End of Main Menu and Submenu */
Now you can see sub-menu categories also appear.
Total Number of Posts on Each Menu Category page
It would be good to display the total number of posts per page. Most of the Hugo Themes, do not have them out of the box.
Total Posts Count to Homepage
In order to display the total number of posts on the Homepage, we need to make following config changes.
- Update the
layouts/index.html
1{{ define "main" }}
2<main class="main list" role="main">
3 <!-- Get total number of posts -->
4 <header class="homepage-header">
5 <h2>All Posts ({{ len (where .Site.RegularPages "Section" "!=" "") }})</h2>
6 </header>
7 {{- with .Content }}
8 ...
9 {{ end }}
10</main>
11{{ partial "pagination.html" . }}
12{{ end }}
Total Posts Count on each Category Page
In order to get the Total Post count on each Category Page, we make the following config changes.
- Update the
layouts/_default/list.html
1{{ define "main" }}
2<main class="main list" role="main">
3 {{- with .Title }}
4 <!-- Get number of posts per each category page -->
5 <header class="main__header">
6 <h1 class="main__title">
7 All Posts About {{ . }}
8 {{ $title := urlize . }}
9 {{ with $.Site.Taxonomies.categories }}
10 {{ range $name, $taxonomy := . }}
11 {{ $term := urlize $name }}
12 {{ if eq $term $title }}
13 ({{ $taxonomy.Count }})
14 {{ end }}
15 {{ end }}
16 {{ end }}
17 </h1>
18 </header>
19 {{- end }}
20 {{- with .Content }}
21 ...
22 {{- end }}
23</main>
24{{ partial "pagination.html" . }}
25{{ end }}
Add Back to Top
When we are in the middle or at the bottom of a post and we need to move to the top, that is not readily available in Hugo.
Follow these steps to enable Back to Top feature in your Hugo website:
Create layouts/partials/back-to-top.html
1<script src="https://unpkg.com/vanilla-back-to-top@7.2.1/dist/vanilla-back-to-top.min.js"></script>
2<script>addBackToTop({
3 diameter: 56,
4 backgroundColor: 'rgb(255, 82, 82)',
5 textColor: '#fff'
6})</script>
Call this file in every post
Once the above is created, we need to call this file as first line of code in every post.
1{{ partial "back-to-top.html" . }}
This can be added to the default.md
template in order to make sure it is enabled for all new posts, by default.
This would add the arrow for moving to the top.
Adding Line Numbers and Highlights to the Code Sample
It would be good to display line numbers and highlight few lines in the code samples in the post.
For more details on the Highlight Shortcode, refer this.
linenos
: configure line numbers. Valid values are true, false, table, or inline.hl_lines
: lists a set of line numbers or line number ranges to be highlighted.linenostart=199
: starts the line number count from 199.
{{< highlight go "linenos=true,hl_lines=2,linenostart=199" >}}
// ... code
{{< / highlight >}}
This would result in following section
|
|
{{< highlight go "linenos=table,hl_lines=2,linenostart=199" >}}
// ... code
{{< / highlight >}}
This would result in following section
|
|
{{< highlight go "linenos=inline,hl_lines=2,linenostart=199" >}}
// ... code
{{< / highlight >}}
This would result in following section
199<script src="https://unpkg.com/vanilla-back-to-top@7.2.1/dist/vanilla-back-to-top.min.js"></script>
200<script>addBackToTop({
201 diameter: 56,
202 backgroundColor: 'rgb(255, 82, 82)',
203 textColor: '#fff'
204})</script>
Reading Time
It will be good if readers can get to know, how much time does the particular post take to read, on an average. Hugo provides this functionality, we need to configure our code to enable that.
Follow these steps to enable Reading Time feature in your Hugo website:
Create layouts/partials/post_meta/reading-time.html
1<div class="meta__item meta__icon">
2 {{- partial "svg/reading-time.svg" -}} {{ if gt .ReadingTime 1 }} {{ .ReadingTime }} Minutes {{ else }} Less Than a Minute {{ end }} Read
3</div>
Download svg image reading-time.svg
to the layouts/partials/svg
folder
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg width="18" height="18" viewBox="0 0 88.965 88.966">
<polygon points="66.436,56.656 61.639,56.656 61.639,72.438 75.096,72.438 75.096,67.641 66.436,67.641"/>
<path d="M65.049,46.49v-3.342V23.541c0,0,0.008-6.385,0-6.465l-3.746-7.59l3.324-6.32c0.354-0.67,0.33-1.475-0.062-2.121
C64.174,0.397,63.471,0,62.715,0H5.301C4.11,0,3.143,0.967,3.143,2.16l0.052,80.307h10.369V16.841H7.461V4.32h51.68l-2.186,4.15
c-0.328,0.625-0.332,1.369-0.007,1.998l3.295,6.373h-42.43v65.625h7.915h6.818h14.526h2.236c3.865,4,9.273,6.5,15.264,6.5
c11.719,0,21.25-9.532,21.25-21.25C85.825,56.158,76.547,46.746,65.049,46.49z M64.575,81.966c-7.857,0-14.25-6.394-14.25-14.25
s6.393-14.25,14.25-14.25s14.25,6.394,14.25,14.25S72.432,81.966,64.575,81.966z"/>
</svg>
Update the config.toml
Update the post_meta
section in config.toml
to include reading-time
post_meta = ["date", "categories", "reading-time"]
These above changes would result in following
Render image with alt text, caption and center align
We can configure Hugo to render images with Center align, having alt text and customized style for caption.
Create layouts/_default/_markup/render-image.html
1{{ if .Title }}
2 <figure>
3 <img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" style="display: block;margin: 0.7rem auto;">
4 <figcaption style="text-align: center;font-style: italic;font-size: smaller;">{{ .Title }}</figcaption>
5 </figure>
6{{ else }}
7 <img src="{{ .Destination | safeURL }}" alt="{{ .Text }}">
8{{ end }}
Render link to open clickable links in new tab
Every time a link is clicked in the post, make sure they always open in the new tab
Create layouts/_default/_markup/render-link.html
1<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}
2{{ if or (strings.HasPrefix .Destination `http`) (strings.HasPrefix .Destination `/`) }} target="_blank"{{ end }}>{{ .Text }}</a>
Footer with copyright and current year
Display the current year automatically in the footer.
Update layouts/partials/footer.html
1<footer class="footer">
2 <div class="container footer__container flex">
3 {{ partial "footer_links.html" . }}
4 <div class="footer__copyright">
5 © 2020-{{ now.Format "2020" }} {{ .Site.Params.copyright | default .Site.Title }}
6 </div>
7 </div>
8</footer>
Visitors Count
Keep track of number of visitors to a single post.
Create layouts/partials/visitor-count.html
1<div class="views">
2 <span class="views">
3 <img src="https://visitor-badge.glitch.me/badge?page_id={{ .Permalink }}" alt="Views"/>
4 </span>
5</div>
Update layouts/_default/single.html
Include the visitor-count.html
on top of the single.html
1{{ define "main" }}
2<main>
3 <article class="post" {{- if .Param "autonumbering" }} autonumbering {{- end }}>
4 <header class="post__header">{{ partial "visitor-count.html" . }}
5 ...
6 </header>
7 ...
8 <footer>
9 ...
10 </footer>
11 </article>
12</main>
13{{ end }}
Google Analytics
Add some Analytics to your site with the help of Google Analytics.
Create Account in Google Analytics
First you need to create an Account in Google Analytics here, if you don’t have already.
Configure Google Analytics
-
Click on Admin in the bottom left-hand corner of your dashboard.
-
In Admin, in the Account column, click Create Account.
-
Provide an account name. Configure the data-sharing settings to control which data you share with Google.
-
Click Next to add the first property to the account.
-
In the Property column, click Create Property.
-
Enter a name for the property and select the reporting time zone and currency.
- If a visitor comes to your website on a Tuesday in their time zone, but it’s Monday in your time zone, the visit is recorded as having occurred on Monday.
-
Click Next. Select your industry category and business size.
-
Click Create and accept the Analytics Terms of Service and the Data Processing Amendment.
-
In the Property column, click Data Streams then Add stream.
-
Click iOS app, Android app, or Web (Since we are doing for our website, we will choose Web.)
-
Enter the URL of your primary website, e.g., “example.com”, and a Stream name, e.g. “Example, Inc. (web stream)”.
-
You have the option to enable or disable Enhanced measurement.
-
Click Create stream.
Add G- ID to Hugo Website
-
You’ll need to add the Analytics tag to your web pages to begin seeing data in your new Google Analytics 4 property.
-
Click Admin, in the Property column, check that you have your new Google Analytics 4 property selected, then click Data Streams, then Web. Click the data stream.
-
Your “G-” ID appears at upper right. Copy the “G-” ID.
-
Go to the
config.toml
and paste the “G-” ID obtained above, for the parametergoogleAnalytics
.
googleAnalytics="G-C3MLHYNGL3"
- Deploy the code and that’s it.
From now on, our website will be tracked on the Google Analytics, providing us all information regarding pages views, posts accessed, number of users and many more.
Track localhost
This code would not track the hits to the localhost. If you want localhost also to be tracked, then you need to modify the code.
In the layouts/_default/baseOf.html
file
1{{- if not .Site.IsServer }}
2 {{- if hasPrefix .Site.GoogleAnalytics "G-" }}
3 {{ template "_internal/google_analytics.html" . }}
4 {{- else }}
5 {{ template "_internal/google_analytics_async.html" . }}
6 {{- end }}
7{{- end }}
Remove the highlighted lines from the section above, so that Google Analytics can track the localhost also.
Add Comments to your Hugo Website
Utteranc.es is a lightweight comments widget, which allows you to use GitHub Issues for blog comments. It’s open source, looks clean, comments are stored on GitHub, and even comes with a dark theme.
Utterances Limitations
- Readers must have a GitHub account to comment.
- Utterances supports no other login or authentication mechanisms.
- For storing comments, Utterances supports only GitHub.
- GitLab, BitBucket, and so on are not supported.
Implementing Utterances
- You will need to have your website hosted on GitHub, in a public repository, in order for utterances to work as intended.
- Install utterances app on that repo. Select the above public repo from the drop-down menu that will appear.
- Run the Utterances configuration tool to generate the script configuration. Fill in the requested form and it would generate the custom html which can be copy & pasted into your template.
- name of the repository : Usually, it will be something like «username»/«username».github.io (or more generally owner/repo)
- Blog Post ↔️ Issue Mapping: Choose the mapping between blog posts and GitHub issues.
- label (optional): as the comments will be managed via GitHub Issue system, you will need to set-up a proper label to identify those issues created by Utterances.
- theme: your choice of a light or dark theme, according to the overall style of your current blog template.
- Copy the Enable Utterances script.
- Update the
layouts/partials/comments.html
- Replace everything in it with utterances script code.
1<script src="https://utteranc.es/client.js"
2 repo="prasbhat/prasbhat.github.io"
3 issue-term="title"
4 label="Comments"
5 theme="github-light"
6 crossorigin="anonymous"
7 async>
8</script>
- Comments are already enabled in
layouts/_default/single.html
towards the end, in the Mainroad theme.
1{{ define "main" }}
2<main class="main" role="main">
3 ...
4</main>
5{{ partial "authorbox.html" . }}
6{{ partial "pager.html" . }}
7{{ partial "comments.html" . }}
8{{ end }}
- That’s it. Now when you refresh the browser, you can see the Comments section available.
- As mentioned earlier, one drawback of this tool is, it works only with GitHub and user has to login with their GitHub account, before leaving comments.
- Click on “Sign in wih GitHub”.
- You will be presented with the GitHub Login page.
- After you login, you will be able to comment. After adding comments
These are some of the enhancements, I have implemented in my Hugo project, to make it more enjoyable.