Reflections of a Hugo Novice
This blog is built with the static site generator Hugo. As someone who has a background in web development but never worked with Go or Hugo before, I’d like to share my rationale for choosing Hugo, the experience of building my own Hugo theme from scratch and my thoughts on this tool. What are the pros and cons of using Hugo? Is it the right tool for you? Is Hugo boss (sorry, I had to)? Read on to find out.
1. The initial competitor: Astro
Blast from the past: Astro’s 2022 pre-release site on archive.is
And I have to say: Astro impresses from the get-go! The team behind the framework greets you with a site that looks great and does a fantastic job at showcasing Astro’s strengths, hyping you up already on the landing page. But what is more important, they also provide a thorough, clearly structured, really excellent documentation and - what do you know - a fantastic tutorial on how create a fully working blog with Astro!
Seriously, the tutorial is genuinely fun and if you meet the stated minimum requirements of knowing the basics of HTML, CSS and JavaScript I totally recommend you give it a try. At the end of the tutorial you’ll know how to create blog posts from markdown text files and have a fully working blog with a tagging system and a tags page, an archive, an RSS feed and a dark & light mode. You’d only have to adjust the styling to your liking - or choose one of the many free themes - and indeed you would be good to go live with your very own blog. The tutorial even tells you how to deploy your blog on Netlify for free!
You do not have to know React or a similar JS framework in order to use Astro, but if you do, you will feel right at home: you structure your project around reusable, ideally simple and small components that utilize the power of JSX, a JavaScript-powered syntax that looks like HTML (and will render to HTML), while allowing you to make use of JavaScript variables, expressions, methods and custom-defined JSX elements.
This is not nearly as complicated as it might sound. As long as you know the basics of HTML and JavaScript it’s pretty intuitive and Astro’s documentation does a good job of introducing JSX. Astro also supports TypeScript right out out of the box, same for Sass/SCSS if you prefer that for styling over vanilla CSS. And of course you could also install Tailwind, as Astro runs with Node.js, meaning you have access to the rich npm ecosystem.
What is more, Astro even provides integration for other frameworks like React, Vue and Svelte! You heard that right, Astro can act as a framework for frameworks (which is both ridiculous and awesome at the same time) and if you have some tried and tested components from another framework, you can simply copy&paste them into your new Astro project if you need dynamic elements (called “islands” in Astro). Which in turn enables you to make use of framework specific libraries like React Hook Form or zod too.
Coming from React development myself, getting into Astro was a breeze and, after dealing with the “joys” of Next.js for work, also felt like a breath of fresh air. Astro felt familiar and intuitive, keeping the good aspects of React development and achieving a lot of what Next.js does in a way that is much more efficient, clean and sane.
Yet still… for setting up a simple website and blog, all of this somehow still felt like “too much”. Essentially, all a blog needs to be is static HTML pages, consisting of text-elements, hyperlinks and images, styled with some CSS and maybe a little bit of JavaScript for e.g. a search feature. Is there really no simpler way to generate this? Why would I need the npm ecosystem for that task, that Astro relies upon, what is the necessity for all of these node modules and for opening the door to technical debt?
If you don’t know what any of this means: it means relying on third-party tools, on external dependencies (and npm tells me that Astro has 63 dependencies - most of these coming with their own dependencies) that need to be updated from time to time. And since they are all separately maintained and updated, but have to work together once they are intertwined as a project’s dependencies, this means things like to break from time to time. In short: it means having to deal with an element of potential instability when building your site.
(In fact, just as I am writing this, a massive npm hack happened , targeting only one developer’s packages, but, I quote: “Chalk and Debug are used in countless frameworks and libraries, indirectly affecting projects that did not consciously install these packages.” - the hackers didn’t have to go to such lengths just to prove my point!)
The same problem also exists for 11ty, another JavaScript static site generator that is supposed to be even more lean and performant than Astro, but that relies on Node.js and the npm ecosystem as well.
To be clear: I do not want to malign Astro or 11ty. On the contrary, I think I made it obvious that I am very impressed with Astro and 11ty looks promising as well. I am sure there are scenarios, depending on the type of sites that need to be built, where both of these tools would be the perfect choice. I’ll definitely keep them in mind for the future. In all fairness I have to emphasize that Astro is not known to blow up on a regular basis because of npm package conflicts either. And of course there is a reason why the npm ecosystem exists, allowing us to access and integrate many powerful libraries and tools into our projects. Node.js and npm packages have clear use cases where they provide a lot of value. However, in my specific case, they are overkill at best, potential sources of problems at worst and in any case not needed at all.
So I decided to leave the familiar JavaScript ecosystem and look elsewhere in my quest for simplicity and minimalism with a static site generator. And I found: Hugo.
2. Hugo: features, setup and first impressions
Click the image for a snapshot of Hugo’s site in 2015
I had known about Hugo for quite some time and heard good things about it. Namely that indeed it builds even large, complex sites incredibly fast and very efficiently, while being flexible and allowing for relatively easy customization. The static sites generated by Hugo load fast, have no unnecessary overhead and moreover, Hugo comes with some very handy built-in features, like optimized image processing and multilingual support - perfect for my needs, since I wanted to create posts in English and my native language German as well!
Compared to Astro’s web presence, Hugo's website feels a little, well, underwhelming. However, this is not about the most award-worthy presentation and I can’t ask for simplicity and then complain when I get it. On to the relevant points: the team behind Hugo also provides a quick start guide, telling you that you need to install Go (a programming language), Git (a version control system), Dart Sass (a superset for the styling language CSS) and of course Hugo itself on your machine and how to do that on Windows, Mac, Linux and even BSD. Complete with the option to also build Hugo from source.
These tools are needed to build your site. But the final output is self-contained and does not rely on any of these tools anymore, same as with Astro. And while theoretically Hugo itself might break with a buggy update or hypothetically Go might implode one day, such scenarios are a) very unlikely and b) still much less of a risk than relying on several dozens (or even hundred) of npm package dependencies.
Like Astro, Hugo requires you to write your posts in markdown text files and makes use of “themes”, a set of underlying vanilla HTML and CSS files that act as templates to generate your site from the markdown text. You can edit these templates and of course also add JavaScript code if you like. JSX is not supported, but of course with Hugo being a generator, you don’t have to manually write everything hardcode in HTML yourself. Instead, Hugo makes use of Go’s text & html template packages to generate static HTML pages from your content. This means you’ll work with Go variables and methods, but only in case you decide to modify the templates your theme and site runs on. More on that soon.
None of this is necessary at first, as the quick start guide proceeds to teach you the basic terminal commands (they “warn” you beforehand that you need to be comfortable with the command line, but to be honest: you just have to copy&paste some commands they provide) for creating content, starting the built-in development server and building your site for publishing.
You learn how to create blog posts, from markdown text files and also how to install a theme, add some essential data to the main configuration file of a Hugo-based website (by default: hugo.toml
) and that’s it. Nothing else needed. You are also provided with multiple options for hosting/deploying your website. And indeed, if you select one out of the many, often really beautiful
free themes
that are provided by the Hugo community, and you love that theme just the way it is, you’ll only have to add some theme-specific rules to your config file and then you are all set and ready to launch your new website! Mind you, for all of this you don’t have to touch a single HTML or CSS file at all or know the first thing about JavaScript or Go.
However, let’s say you would like to customize your chosen theme a bit. This is where things get, well… interesting.
3. Creating a Hugo theme from scratch
I quite liked the Paper and PaperMod themes, initially I went with the latter one. Yet I felt the urge to customize them, just a little bit: change some colors and the way the card elements looked, restructure the header and for my multilingual setup I did not want country flags that link to translations on each post, but I felt that the “central flag” in the header should provide dynamic links depending on the page/post.
That sounds easy enough, right? I opened up the HTML files for the theme, looked at the code - and understood nothing. Okay, “nothing” is a bit overdramatic and in hindsight I have to say that the code for PaperMod is well-written and pretty clear, plus the theme comes with a quite thorough and easy-to-follow documentation on GitHub. But at first, as a complete Hugo and Go noob, I was really confused. Especially about the multilingual language translation feature.
This struck a nerve. How can I base my own website on a theme and code that I do not understand, where making even small modifications becomes a challenge? That felt (and feels) unacceptable to me. Sure enough there is no need to reinvent the wheel all the time. For example, I confess that I did not set out to code my own static site generator from scratch, even though there are still things about Hugo that elude me and feel like magic too (but hey, if I find myself with a lot of spare time I could always read the source code for Hugo). But in case of my site’s theme, reinventing the wheel was exactly what I decided on: I wanted to create my own theme from scratch, in order to learn the basics of Go and Hugo and understand what is going on under the hood of my own site.
3.1 Documentation grievances
So I ditched PaperMod and instead I ran the command hugo new theme
, which creates a skeleton for a new theme. I will share a few snippets of code from this point onwards, to illustrate some points of complaint to those who know how to read code. If you are not into programming, feel free to skip past them, of course. So here we go, this is the code for the navigation menu that the hugo new theme
command produces:
{{- define "_partials/inline/menu/walk.html" }}
{{- $page := .page }}
{{- range .menuEntries }}
{{- $attrs := dict "href" .URL }}
{{- if $page.IsMenuCurrent .Menu . }}
{{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }}
{{- else if $page.HasMenuCurrent .Menu .}}
{{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }}
{{- end }}
{{- $name := .Name }}
{{- with .Identifier }}
{{- with T . }}
{{- $name = . }}
{{- end }}
{{- end }}
<li>
<a
{{- range $k, $v := $attrs }}
{{- with $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end -}}
>{{ $name }}</a>
{{- with .Children }}
<ul>
{{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }}
</ul>
{{- end }}
</li>
{{- end }}
{{- end }}
I beg your pardon? This is supposed to be a basic setup that is intuitive to understand and easy to modify?
Some serious reading of the documentation was obviously needed. Which leads to another issue: while the documentation for Hugo is quite thorough, it’s structure and presentation could be improved. Maybe this is just me, but when hunting for some nested reference, the card grid layout that the docs have going on and that you have to traverese does in no way feel like an improvement over the standard, tried and tested collapsible index list which e.g. the Astro docs provide.
Questionable UI for documentation
- the docs were not updated yet and provide outdated solutions
- the docs were updated and tell you something that is in contrast to how every older guide and theme on the internet does things
To be fair, as far as I have seen the developers of Hugo make an effort to maintain backwards compability, meaning the outdated information will still work. But still, it’s not helpful if you are trying to learn and understand how Hugo and themes work. For example, to illustrate scenario 2: once you run the hugo new theme
command, you’ll be surprised by the file structure it spits out, which is different from how the folders/directories and files are structured and named in pretty much any Hugo theme in existence. Under Docs > Templates > New template system
the confused novice theme-builder discovers the explanation for this discrepancy: earlier this year “we performed a full re-implementation of how Go templates are handled in Hugo”, the Hugo devs inform us, and continue: “We’re working on a full overhaul of the documentation on this topic.”
As far as I can tell, the full overhaul of the docs has not happened yet. The restructuring brings some naming confusion with it, since central directories and files are renamed, restructured or removed entirely now, which explains the mismatch compared to how all other authors so far have structured their Hugo themes.
This is not the only such instance, apparently renaming and naming in general are a recurring source of confusion in the docs, at least for beginners. I’ll leave it to you to traverse the nested cards and figure out e.g. the difference between Docs > Functions > time > time.Format
and Docs > Methods > Time > Format
and why the first will tell you that you need to perform the operation .Date | time.Format
while the second gives an example for using .Date.Format
directly. Or what is meant by Docs > Functions > global > site
telling us to use “the site function to return the Site object” like this: site.Params
. But in case the Site object is in context we could also use the Site property for current context like this: .Site.Params
. And for template context like this: $.Site.Params
. Any questions?
A personal favorite of mine and contender for an obscure-naming award is the lang.Translate
function, which might also be called using the aliases i18n
(which at least is a commonly used abbreviation for “internationalization” in software development) and - for maximum cryptic code - just T
. You’ll see all of them used in different themes. In fact, you saw “T” already in the code snippet I shared above. Good luck searching the docs for “T” when encountering this function for the first time as a noob!
3.2 Go syntax struggles
A few words on Go. Overall I am intrigued by the language, it seems fun and interesting and there is no denying that it is fast, efficient and powerful. However, the Go syntax used specifially in the text and html template packages exchanges readability for brevity.
By far the worst offender is the “dot”, which points to the current context variable - which means it can change of course with context. The dot can be thought of as “element” from a JavaScript map method iteration or perhaps even more fittingly as the “this” keyword of a Java class. For example, by default the dot will refer to the current page, so .Title
is self explanatory. However, if we call with .GetTerms "tags"
(the code gets the list of all tags of the page while checking if there are any at the same time), the dot will now refer to the list of tags inside that context. Therefore, we can loop over it with simplyrange .
and now inside the loop, the dot will refer to the tag from the current iteration and we can get it’s name by using .Title
.
So far, so good, that is not too bad, once you get the hang of it. But the code becomes hard to read and leads to unnecessary confusion when trying to write more complex nested code and relying on the dot. You will inevitably lose track of the context that the current dot is referring to sooner or later.
The prime example for this is my language switch functionality, that dynamically links to the translated version of the current page the user is on. In my theme, I actually created several helper variables to make the code for this element more readable, but for the sake of demonstration I re-wrote that code to it’s “natural state”, without named helper variables. It then looks like this:
{{- if gt (len site.Languages) 1 }}
<div>
<ul>
{{- range site.Languages }}
{{- if ne .Lang $.Lang }}
{{- $targetPage := index (where site.Home.Translations "Lang" .Lang) 0 }}
{{- with index (where $.Translations "Lang" .Lang) 0 }}{{- $targetPage = . }}{{- end }}
<li>
<a href="{{ $targetPage.RelPermalink }}">
<img
src="/flags/{{ $targetPage.Lang }}.svg"
alt="{{ $targetPage.LinkTitle }} ({{ or $targetPage.Language.LanguageName $targetPage.Lang }})"
/>
</a>
</li>
{{- end }}
{{- end }}
</ul>
</div>
{{- end }}
With all the different context blocks from the global site object to the nested range
loop context, the if
and with
check contexts that the various dots, dot-Lang, dollar-dot-Lang refer to, I think it would be quite hard to argue that this is intuitive code that is easy to read and understand and maintain.
Mind you, this is not a maliciously constructed, obscure example with purposely ill-chosen variable naming and deliberate ignoring of best practices that I made up just for the sake of demonstration. Far from it, me creating named helper variables is the unusual approach, you will find that code like this is no rarity when browsing Hugo themes. Case in point: the Hugo developers themselves write code like that, as you can see from the skeleton theme’s navigation menu code I shared above (the one with the “T”, among other things). Writing and reading code like that is a pain and off-putting for anyone trying to get into Hugo. Plain and simple.
3.3 Resources for learning
While Hugo’s documentation is not beginner friendly, you will eventually find what you are looking for, if you dig deep enough. I cannot recommend YouTube as a good resource for learning, as most beginner/tutorial/guide videos I watched go down the easy route of telling you to just install a theme and go with the solutions that work out of the box. The rare helpful videos that provide details and more in-depth information are unfortunately quite dated and therefore of limited use.
In contrast, I recommend referring to the official Hugo forums for help. The majority of the Hugo community is welcoming and helpful from what I’ve seen. You can always ask for help there and find answers to almost any topic. In fact, I never had to ask myself, as searching for previous threads where a problem I encountered was described and solved before was almost always a success. However, the problem of stumbling upon (out)dated information persists on the forums too.
The final resource for learning more about how Hugo works I have already mentioned: other themes. Choose a few that look cool and seem straightforward and not too complicated and try to make sense of their code. Just be prepared to encounter confusing naming aliases as well as approaches that are dated and no longer recommended or referrenced in the docs. But overall, seeing how somebody else approached a feature you’d like to implement - or even simply how they structured their theme - can be incredibly helpful and insightful.
4. Conclusion
I was asking myself more than once: who is the target audience for Hugo? I am still not quite sure.
I can think of one perfect candidate: if you just want to get a site online and start posting content as soon as possible, while you do not care about customization of your site whatsoever, choosing Hugo with a ready-made theme would certainly be a great option for you. Whether you have coding experience or not, provided you can follow written instructions and are able to copy&paste the commands in Hugo’s quick start guide, you are all set and ready within minutes without ever touching or writing a single line of code.
However, I figure that almost everybody will want to add at least a little personal touch to the look and feel of their own site, in order to make it, well, their own site. Here I would distinguish between three types of users:
No or very little coding experience: I would not recommend Hugo for you. Or maybe, this “unburdened” background and approach would lead to having an easier time learning how to work with and customize Hugo? Who knows. In fact, I came accross one article by a lady with a non-tech background who indeed successfully built a Hugo site by herself . The open-mindedness and amount of determination to learn, her drive and the willingness to leave her comfort zone and push herself are very remarkable and honestly astounding, much respect to her! But from my experience, she is very much an exception. The majority of non-tech people - and even a seizable percentage of people in software development - would have neither the patience nor the desire to invest so much time and effort into a tool like Hugo that many people would perceive as “non-user-friendly” and “inaccessible” compared to what they know and are used to. You are more than welcome to prove me wrong, though. In fact, I’d love that, so if you are curious about Hugo but have no experience with coding, by all means don’t be deterred by my reflections but give it a try!
Coding experience, but no frontend web dev experience: it depends on how much you want to customize the visuals of your site. Of course if you are well-versed in Go, you will have an easy time working with the templates. But you will have to learn some basics of CSS too (and maybe JavaScript, depending on your needs). If you have neither experience in nor inclination towards CSS, you will not enjoy customizing your website, as there is no way around CSS. Of course this goes for any static site generator where you don’t just use a pre-built theme as is. Be prepared for styling frustration, breaking your chosen theme’s styling by seemingly minor changes to the stylesheet and embodying the “how to center a div” meme.
Experience in frontend web development: like myself, you will likely initially struggle a bit with Go, but otherwise you should be fine. However, I don’t think that web devs are the intended target audience for Hugo. Judging from my experience and the people I know in the field: the overwhelming majority of web developers would choose a JavaScript-based framework without giving it a second thought. And not even an optimized and lean one like Astro or 11ty. It’s what everyone is most familiar with, the approach of “use JavaScript everywhere and for everything” is what gave rise to Node.js, JSX and even JavaScript-based styling in the first place. The ability to install several dozens of npm packages for your project is considered to be more of a benefit than a liability by many web dev enthusiasts and reducing the size of websites and amount of JS code in the client is unfortunately oftentimes an afterthought in “the industry”. Be that as it may, Hugo never really took off in web dev circles.
It is not only among web developers where Hugo didn’t take off, though: I did a little test and googled “migrating from Hugo to Astro”. That search gives you a lot of personal stories as result. Searching the other way around, I could not find even a single personal report on either the first or the second page of the Google results. This is unfortunate, but not unexcpected at all and supports my assessment that Hugo is “asking too much” of and requires “too much effort” for most people, both with and without coding experience. The non-tech lady who successfully fought her way through building a Hugo blog by herself has left Hugo for the website building service Webflow in the meantime too, by the way.
So that leaves me with this answer to the question “who is the target audience for Hugo”: aside from developers with experience in Go, probably Hugo will be a good match for those who care a lot about efficient, minimalist and robust software and sites, who want to be in full control of and being able to customize every aspect of their site and who are willing to put in the time and effort to learn. If that sounds like you, no matter your background experience, I do recommend that you give Hugo a try. It does check off your requirements and is a powerful and versatile tool, once you’ve mastered it.
For everybody else I regret to say that I cannot recommend Hugo in good faith. In all likelihood you will have a much easier and also more fun time with a tool like Astro, achieving almost identical results with less effort and headache. Additionally you will gain some insight into how most of modern web development is done and learn modern JavaScript syntax and tools like Node.js and npm which dominate the industry.
As for my personal conclusion: setting up my website with Hugo took a lot more time than I initially anticipated. The journey was not always smooth sailing and laced with moments of frustration. Nevertheless, I am glad that I stuck with my decision to the end, because I view time invested to learn something new always as time well invested. The things we learn can almost always be applied from the specific topic to more general problems too. And engaging with Hugo provided me with the opportunity to explore something new and try out something else beyond the same old, familiar and well-trodden paths of web development.
I am very happy with the final result, my theme Argo: I like how my theme looks (obviously), it provides me with a fast, efficient and clean site, with all the features implemented that I wanted. Including the dynamic link to the translated version of the current post, despite the syntax madness. Give it a try, click the flag and you’ll see this article in German! And now, if I want to change something about my site, add features or restructure something, I know exactly where to look and what to do. The site truly is mine now, every aspect of it. And that is worth a lot to me.