Hugo, a powerful static site generator, has become a go-to choice for developers and bloggers seeking efficient and flexible website creation, especially for sites with a very large number of pages. Whether hosted on a cheap VPS or services like Netlify, Hugo simplifies the process of building and maintaining static sites. However, static sites often lack dynamic features found in traditional content management systems (CMS), such as displaying «most popular» or «most read» pages.
How to add «most popular» functionaly to a static website? Answer: This can be done with Hugo and Goaccess, two very fast web tools. In such way you rely only on your server’s data, the data that you own, and respect for privacy of your website’s visitors.
In this blog post, we’ll explore a straightforward method to bring dynamic content to your Hugo site by integrating a «most popular» functionality. There are online instuctions to do just that, using Google Analytics API, Python scripting, Hugo itself, and GitHub Actions. They did not work for me, so instead we’ll focus on a simplified solution using GoAccess, Hugo, and a few lazy easy Unix one-liners any sys admin knows. You need to have access to log file of your website to be able to use these instructions because we will solely rely on server’s data instead of Google Analytics API.
Try free online Keyword Extractor and Text Analyzer
Try ChatGPT detector
Here is the bash script which runs the whole thing, namely, it parses log file and starts Hugo. Make sure you have goaccess and hugo installed on your server and that your hugo site is out there. Copy, adjust and save it, for example, as generatehugosite
.
#!/bin/bash
# Set the log file path
LOG_FILE="/path/to/site.access.log"
# Set the report file path
REPORT_FILE="/path/to/site.report.csv"
# Set the YAML file path
YAML_FILE="/path/to/hugosite/data/popular.yaml"
# Run the goaccess command and create the report
goaccess --log-format=COMBINED --output="$REPORT_FILE" "$LOG_FILE" && \
# Filter lines containing '/THIS/' from the report and extract the URLs
grep '/THIS/' "$REPORT_FILE" | \
# Use AWK to print URLs with more than 1 hit
awk -F ',"' '$3 > 1 {print $11}' | \
# Format the URLs into YAML list items and remove unnecessary characters
sed -e 's/\/THIS\// - "/' -e 's/[/]//g' | head -7 > "$YAML_FILE" && \
# Generate the Hugo site using the updated YAML file
hugo -s /path/to/hugosite/
Let’s go through each part of the script:
goaccess
command:
grep '/THIS/' "$REPORT_FILE"
:
awk -F ',"' '$3 > 1 {print $11}'
:
sed -e 's/\/THIS\// - "/' -e 's/[/]//g'
:
head -7 > "$YAML_FILE"
:
hugo -s /path/to/hugosite/
:
This script works for me, but it is more of an example, you may need to adjust it according to your log format. Copy this script, adjust paths as needed and save it. Creat files by doing touch /path/to/hugosite/data/popular.yaml
and touch /path/to/site.report.csv
. Make sure /path/to/site.access.log
is not empty (make sure you have nginx config for your site to have a separate log file) Do not run the scipt just yet, as the most important thing is yet to come - the wonderful Hugo template to process the data file with most popular posts, namely the popular.yaml
file.
Presumably you already have a Hugo website. Add this template to partials or just copy it to your index.html
layout template. The idea is to read URLs from /data/popular.yaml
data file and search for them across your slugs. Slugs must be included in posts frontmatter. Without slugs in posts frontmatter this template will not work.
<div class="card">
<div class="card-content">
{{ range $index, $slug := $.Site.Data.popular }}
{{ range $.Site.RegularPages }}
{{ if eq .Params.slug $slug }}
<h1 class="title post-title"><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
{{ end }}
{{ end }}
{{ end }}
</div>
</div>
Now, let’s add comments to explain the different sections of the template:
<div class="card">
<div class="card-content">
<!-- Iterate over each slug in the 'popular' YAML data -->
{{ range $index, $slug := $.Site.Data.popular }}
<!-- Iterate over all regular pages on the site -->
{{ range $.Site.RegularPages }}
<!-- Check if the current page has a matching slug -->
{{ if eq .Params.slug $slug }}
<!-- Display post title with a link to the permalink -->
<h1 class="title post-title"><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
<!-- Display whatever you want from post frontmatter, such as description, category, tags, date -->
{{ end }}
{{ end }}
{{ end }}
</div>
</div>
Also Read How to automatically tag posts in Hugo Static Site Generator with Python
This template iterates through each slug in the ‘popular’ YAML data, matches it with the corresponding regular page in Hugo, and then displays the post title with a link to the permalink. Adjust the template based on your site’s structure and styling preferences. Works with Hugo Static Site Generator v0.54.0.
Make sure that the /data/popular.yaml
file has the specified format, nothing else and no headings:
- "first-post"
- "another-post"
- "yet-another-post"
The popular.yaml
file should contain a list of popular post slugs, each surrounded by double quotes and in the format - "slug"
. This ensures that the file contains only slugs and follows the specified structure.
Add generatehugosite
to cron to schedule your bash script to run periodically, you can follow these steps:
crontab -e
If you’re prompted to select an editor, choose your preferred one.
0 0 * * * /bin/bash /path/to/your/generatehugosite
Replace /path/to/your/generatehugosite
with the actual path to your bash script.
In the example above, 0 0 * * *
means «every day at midnight.»
This cron schedule will execute your script at the specified intervals, ensuring that your Hugo site is updated with the most read posts regularly. Adjust the schedule according to your preferences and update frequency requirements.
Remember to replace /path/to/your/generatehugosite
with the actual path to your bash script. Make sure file persmissions of site.access.log allow running the script.
Questions or comments? Please, ask me on LinkedIn