Website development using Static Site Generator (SSG)
published: ⏰ 8I’d like to share my experience with using static site generators (SSG). For this purpose, I use Hugo, starting from early 2025. The main focus of this post will be on Hugo, without any LLM or vibe-coding. Plus, a small plug and tribute to the developers for their hard work, as Hugo is distributed free of charge with open source code.
Introduction
My experience with CMS-based website development goes back a long time (2006 - 2008, using PHP). And honestly, I had neither the time nor the desire to repeat that exercise. I didn’t use WordPress or other CMS platforms, and here’s why.
Why Static Site Generators (SSG) in particular? There are several reasons:
- Performance – The output of a generator consists of plain HTML files, with no server-side logic involved, which means no additional load on the hosting. Therefore, even the simplest hosting option can be used.
- Security – Since there is no server-side code, potential vulnerabilities are eliminated (excluding possible misconfigurations of the host itself). Also, DDoS attacks have less impact. We only serve static content, which is also cached.
- Ease of content management – This is somewhat debatable, but in general, SSGs allow working with content in Markdown, which I personally find very convenient. All website content is version-controlled via GIT; changes can be made through Merge Requests (MR), and any previous version can be rolled back at any time.
Variants of SSG
When I created the first version of my site, I didn’t have much relevant experience with such generators, so I chose more intuitively, but in general, I hit the mark. There is a website listing popular SSGs – A List of Static Site Generators for Jamstack Sites. I tried setting up and using the first 4–6 generators from this list according to my needs. Each has its own pros and cons, and some provide quite a solid set of features out of the box.
In the end, I chose Hugo. I won’t be able to describe all its capabilities – mainly, I’ll focus on those that were required for my site. More detailed comparisons with other generators are covered in the sections below.
Installation and Usage
Here, Hugo is unmatched. All you need is to install the binary file and run the command hugo build.
That’s it – no NPM, no long builds, no dependency downloads, or anything like that. There’s also support for real-time
updates via hugo server.
Localization
One of the important requirements was supporting site localization. I currently use two languages: Russian and English. Therefore, this was one of the blocking criteria for selection. Some SSGs were immediately ruled out because they did not support localization.
Different generators implement localization in their own way:
- For some, it’s necessary to create two separate sites for different languages, with almost no shared resources. You could say that localization is not supported.
- Others have a single site, but the directory structure for articles in different languages is highly isolated from each other. It’s slightly better than the first point, but still inconvenient to use.
- Localization exists, but there’s no way to create an article in only one language. Sometimes you don’t have time or opportunity to translate immediately, yet you’re not given a choice.
How is it done in Hugo? Simply and conveniently – Multilingual mode:
- All translations of articles can be stored together as:
/blog/article-1/assets/*.webp/blog/article-1/index.en.md/blog/article-1/index.fr.md
- Create the necessary translation and place it in the article’s directory next to the original.
- If a translation for a language is missing, create an empty file and set
headless:truein the frontmatter. - Resources from
assetscan be shared across all languages. - There is also support for using the
i18nfunction to translate text (a list of terms stored in separate files). - Localization is supported in templates as well.
Customization and Templates
There are ready-made templates available for most generators. Fundamentally, there aren’t any strict limitations in all SSGs. If desired, you can implement almost anything. Here, simplicity of these modifications stands out.
Initially, I used Ananke as the base theme. It was convenient for a quick start, but since it’s designed for broad universality, it brought in a lot of unnecessary code. This often hindered attempts to make adjustments, and there was also some clutter when making changes to both the template and site files. Eventually, over the course of a week or so during evenings and weekends, I completely rebuilt the current version of the site from scratch according to my needs. Some parts of the styling were taken from online templates. And no, I didn’t use LLM for vibe-coding.
Hugo uses Go Template (html/template, text/template) for defining templates. The structure is predefined by templates and sections. The main templates are:
- Base – the main template that defines the overall site structure (
index.html). - Home – the homepage.
- Page – a template for regular pages (pages within a section).
- Section – a template for sections (a list of pages in a section).
- Taxonomy – a template for taxonomies (tags, categories, etc.)
These templates can be overridden at different levels. For example, the Page template is used for these pages:
- Article 1 – the default template, applied to all pages at any nesting level.
- Article 2 – a custom template, only for products.
Page Parameters
The approach is similar across all generators, as far as I remember. If additional parameters are needed for a page, they can be specified in the frontmatter. For example, articles can have a featured image or a text-based cover.
If featuredTitle is specified, then a text-based cover will be displayed. If featuredImage is used, an image will
be shown.
article.md
---
featuredTitle: WebDev
featuredSubtitle: Site
categories: [ 'web' ]
tags: [ "web", "site", "ssg", "generator", "hugo" ]
title: "Разработка сайта с использованием Static Site Generator (SSG)"
toc: true
show_comments: true
---
# ВведениеIn the template itself, you simply select what’s needed:
article-card.html
{{ $image := "" }}
{{ if .Params.featuredImage }}
{{ $image = (resources.Get .Params.featuredImage).RelPermalink }}
{{ end }}
{{ if $image }}
<img src="{{- $image -}}"/>
{{ end }}
{{ if and (not $image) .Params.featuredTitle }}
<div>{{ .Params.featuredTitle }}</div>
{{ if .Params.featuredSubtitle }}
<div>{{ .Params.featuredSubtitle }}</div>
{{ end }}
{{ end }}Custom Components and UI Elements
If you look at SSGs based on NodeJS, then usually there’s ReactJS or Vue, which means the ability to create custom components or integrate external libraries.
I haven’t tried creating ReactJS components for Hugo, but it seems possible via a separate NodeJS project. In turn, Hugo offers an alternative approach to components – shortcodes. They resemble Web Components – parameterized HTML templates that can be used directly from Markdown.
For example, a contact card:
In the markdown
{ { <contact-card title="Telegram" fontIcon="telegram" url="https://t.me/tekBlueprint" >}}
Telegram group with news and discussions.
{ {</contact-card>}}Template contact-card.html
{{ $link := .Get "url" }}
{{ $fontIcon := .Get "fontIcon" }}
{{ $imgIcon := .Get "imgIcon" }}
<div>
<div>
{{ if $fontIcon }}
<i class="bi bi-{{$fontIcon}}"></i>
{{ end }}
{{ if $imgIcon }}
<img src="{{ $imgIcon }}" style="width: 1rem;"/>
{{ end }}
{{ .Get "title" }}
</div>
<div>
<p>
<a href="{{ $link }}" target="_">{{ i18n "follow" }}</a>
{{ .Inner }}
</p>
</div>
</div>There are also partials – these are fragments where the context of the
current page is available. For example, I use them for <head/> and <footer/> blocks of pages. Localization support
is also provided.
RSS, Sitemap, robots.txt
To be honest, I don’t remember how it was in other generators - they’ve already been filtered out by this step. I think they all supported it too.
With Hugo, everything works out of the box:
- RSS:
- Automatically generated, per language.
- Can be configured via template. I never finished setting up article covers, no time for that.
- I use it as a news feed.
- Sitemap:
- Automatically generated, per language.
- Can be configured via template.
- robots.txt:
- Automatically generated, but manually configured in my case.
- Also configurable via template.
Resource Management
NodeJS-based generators allow processing and bundling resources just like for regular web applications. Everything here is quite straightforward and simple.
Hugo has similar functionality. Images, styles, scripts, fonts are stored either in assets/ or static/. Resources
from assets/ must be processed using additional
functions: resources.GetMatch, resources.Get,
etc. Static resources from static/ can be referenced directly, as they are copied in their original form.
It’s also possible to bundle resources (CSS/JS) through pipelines. For example,
{{- $cssVendor := slice
(resources.Get "vendor/lib1/style.css")
(resources.Get "vendor/lib2/style.css")
| resources.Concat "assets/vendor.css" | resources.Minify | resources.Fingerprint}}Finally, a file assets/vendor.css with a unique hash in the filename will be generated.
If necessary, you can perform image processing. For example, automatically converting an image from the article to WebP format via Process:
{{ $linkToCover = (.Process "webp").RelPermalink }}Site Generation
There were no special requirements here, but Hugo won due to its speed.
The site is currently small (a bit over 100 pages), and the site build time is around 300ms. During this process, not only are the pages generated, but also RSS, sitemap, all tag and category pages, and CSS/JS resource compression.
│ EN │ RU
──────────────────┼─────┼─────
Pages │ 107 │ 106
Paginator pages │ 2 │ 2
Non-page files │ 60 │ 0
Static files │ 12 │ 12
Processed images │ 0 │ 0
Aliases │ 68 │ 67
Cleaned │ 0 │ 0
Total in 331 msLimitations
SSG has certain limitations that should be taken into account. Since there’s no server-side part, additional functionality must be added via third-party services/scripts. For example, for article comments giscus is used, which works through GitHub.
Search also needs to be implemented either through third-party services or via JS scripts that will work on the client side (lunr). I haven’t managed to implement this yet, only mentioned in the documentation.
Conclusion
Actually, I’m not using all of Hugo’s capabilities, only the necessary minimum required. The documentation is quite good and there’s a forum where you can find answers to many questions. Therefore, if you need a static site without unnecessary complications, then Hugo is a good choice.