Hugo and Neocities

Preface: This is not a tutorial! It’s a documentation of my workflow process, but it does have many pointers and resources you can use if interested.

A couple months ago I briefly talked about migrating to a static site generator. Back then I was using a pre-made theme (PaperMod), which proved itself to be very demotivating since my website looked “polished” and “professional” which is not at all what I want to go for. I’m not really looking for aesthetics that scream “experienced web developer” since I am anything but. This is my website and I want it to have my own personality imprinted on it, all messiness and novice coding abilities included. I don’t want things to work out of the box, I want to come across a problem and set myself out to find a solution.

After dropping the pre-made theme, I kinda gave up on the idea of static site generators altogether since I simply couldn’t figure out how to port my own theme into Hugo. That is until I came across this Hugo Starter Theme. For some reason, looking through its file structure made something click in my brain and attempting to build my own didn’t seem so daunting anymore. It wasn’t easy at all, but it was very rewarding.

What follows is my workflow for this website.

Coding with VSCodium

There’s a wealth of editors out there, from Vim to Emacs to the trusty Notepad++, but my personal choice is VSCodium, a “community-driven, freely-licensed binary distribution of Microsoft’s editor VS Code.”

It’s a damn fine editor, with a lot of useful features that make coding a breeze. On top of that, I use the following extensions to improve my experience:

Integrating Hugo

When I first got into Hugo I was surprised to find out it has a pretty great documentation. A lot (and I mean a lot) of pages explaining everything you can and can’t do, as well as many more answers scattered through forum posts, articles and tutorial videos.

A pretty useful guide was Mike Dane’s Hugo series, where he goes into detail about pretty much every aspect of working with this static site generator. It really helped me understand concepts that seemed incomprehensible and unfathomably difficult.

What’s pretty cool about Hugo (and SSGs in general) is that you can work with variables and conditionals, as well as automate things that you’d otherwise have to hardcode. For example, I can add “next” and “previous” links to the bottom of my posts only if they’re not standalone pages:

{{ if not }}
  <div class="nextprev">
    {{< /span> if .NextInSection }}
        <a href="{{ .NextInSection.Permalink }}">« next</a>
    {{< /span> end }}
    {{< /span> if .PrevInSection }}
        <a href="{{ .PrevInSection.Permalink }}">prev »</a>
    {{< /span> end }}
{{< /span>- end }}

Other things I’ve automated:

Beyond that, Hugo also supports shortcodes, a very powerful feature that lets you simplify pretty much any HTML code you want to display. I use them to manage my media pages and combine them with Hugo’s ability to pull information from data files (YAML in my case) to manage my logs page.

For detailed information on how I setup every Hugo template, my website source code is stored in a Codeberg repository.

Writing posts in Markdown

Out of the box, VSCode (and, by extension, VSCodium) isn’t exactly a robust Markdown editor. It has very basic functionalities, but basic’s really all they are. In order to mold it to my own needs I use the following extensions:

Additionally, the benefit of working with Markdown and other forms of plain text is that it’s very easy to edit posts from other devices. Combining Syncthing’s file sync functionality with an app called Markor I can edit posts on the go and come back to them later on Desktop.

Optimizing images

For this website, there’s absolutely no reason to upload some ultra HD image that weighs 10MB, so I use ImageMagick to convert everything to jpeg (thanks but no thanks, WebP) and optimize it for the web.

The command that hit the sweet spot for me is: mogrify -sampling-factor 4:2:0 -strip -quality 85 -interlace JPEG -colorspace sRGB *.jpg.

Code formatting with Prettier

The thing about Hugo is that it outputs a really ugly looking HTML. Empty spaces galore. Indentation out the window. It’s kind of silly – I could minify everything if I wanted to – but having poorly formatted HTML annoys me.

The solution then was Prettier, a very popular code formatter. It’s most commonly used as an editor plugin but for my specific needs I am using the CLI version which allows me to batch format Hugo’s public build of my website.

My config file is pretty simple:

    "printWidth": 150,
    "tabWidth": 4,
    "useTabs": false,
    "semi": true,
    "singleQuote": true,
    "quoteProps": "as-needed",
    "trailingComma": "es5",
    "bracketSpacing": true,
    "bracketSameLine": true,
    "arrowParens": "always",
    "proseWrap": "preserve",
    "htmlWhitespaceSensitivity": "css",
    "endOfLine": "lf",
    "embeddedLanguageFormatting": "auto",
    "singleAttributePerLine": false

And since I don’t want Prettier to mess with the compressed CSS, I also use a “.prettierignore” file:

# Ignore all CSS files:

Uploading to Neocities

Currently I am using the Neocities CLI to upload all my files. After setting it up, pushing everything is as easy as typing neocities push . on a terminal.

Automating the process

Having to execute the same commands over and over again gets tiresome very quickly, so I created a simple batch script1 that automates the whole thing for me:

@echo off
pushd %~dp0

set workDir="path\to\working\directory"
set hugoDir="path\to\hugo\directory"
set publicDir="path\to\public\directory"

cd %workDir%
if exist "public" goto :cleanup
goto :build
goto :prettier

rmdir /s /q "public"
timeout 5

cd %hugoDir%
call :checkFail

cd %workDir%
call npx prettier --write public
call :checkFail
set choice=
set /p choice="Deploy website now? (Y/N):"
if NOT '%choice%'=='' set choice=%choice:~0,1%
if /i '%choice%'=='Y' goto :deploy
if /i '%choice%'=='N' goto :end
if '%choice%'=='' goto :end
ECHO "%choice%" is not valid
goto :end

cd %publicDir%
call neocities push .
call :checkFail
goto :end

echo Done!

@echo off
if NOT ["%errorlevel%"]==["0"] (
    echo There was an error.

First it checks if a “public” folder exists and if it does, deletes it. This ensures that I won’t be pushing any unused files or assets. Then, it executes the “hugo” command to build the actual website, followed by the “prettier” command to format the output. Because I sometimes like to check the built files before I push them to Neocities, I added a prompt that asks me if I want to deploy them.

Finally, I aliased the script inside my Powershell configuration file so I can call it as build inside the terminal from anywhere on my computer:

Function webBuild { cmd.exe -/c "path\to\build.bat" }
Set-Alias -Name build -Value webBuild


That’s my workflow laid out and I gotta say it really satisfies me for the time being. The only thing I’m mildly unhappy with is how slow the Neocities CLI is and the fact it doesn’t also sync deleted files, but I don’t know a better tool nor do I have the skills to create one so it’s something I’ll simply have to tolerate.

Until the next one.

  1. I’m too lazy to learn Bash scripting and too uninterested to learn Powershell scripting, sorry ↩︎