Hugo and Neocities


Last modified on 12/27/20

Categories: Development Projects Tags: Neocities Hugo


NeoCities provides free hosting for static sites, which introduced me to the concept of static site generators. Having run a few Wordpress sites in the past, I appreciate how much static site generators streamline a website and it has become my preference. The markdown files used for blog posts and these are easily migrated between generators, so trying different generators out is trivial.

Initially I used Jekyll, but the setup felt kind of messy and the site kept taking longer to generate even though it wasn’t that large. I switched to Hexo, which was much faster, and then moved to Hugo because it had a site template I liked and was comparably fast.

I use Chocolatey for Windows package management.

choco install hugo -confirm
choco upgrade hugo -confirm

On debian-based Linux, use

sudo apt-get install hugo

Uploading to NeoCities

Using Python to Upload

Updated 12/19/20

The NeoCities upload dashboard is intentionally basic, users are encouraged to develop their own tools for deployment. I use soulshake’s NeoCities Python API. I noticed recently neoslaughter was working on adding a push method for recursive directory uploading. It does not currently work as-is so I lifted the code and incorporated it into my upload script. python-neocities has since been updated to utilize recursion with an undocumented push function.

cd \lib\python2.7\
git clone
cd python-neocities
python install and make install throws an error on Linux. I’m not entirely sure what is going on but I ended up manually installing the dependencies, requests, click, and tabulate via pip.

I have put off sharing my incremental upload script because it is a mess and occasionally misses files (usually tag or category). I think the reason is I am using python’s filecmp dircmp, which only shallowly compares, but I have not taken the time to investigate.

#! python3
import neocities
from glob import glob
import os

USERNAME = "user name"
PASSWORD = "password"
LOCAL_DIRECTORY = "public" # Hugo generates the site in the public directory

file_extensions = [

def push(d):
    ''' recursive directory upload, adapted from '''
    files = glob(d + '/**', recursive=True)
    for file_name in files:
        if os.path.splitext(file_name)[1] in file_extensions:
            destination_path = file_name.replace(LOCAL_DIRECTORY,'').replace("\\","/")
            nc.upload((file_name, destination_path))
            print("Uploaded {} as {}".format(file_name, destination_path))

nc = neocities.NeoCities(USERNAME, PASSWORD)

The Windows command line workflow is pretty straight-forward. Hugo does not have a clean flag like Hexo, so you must delete the old directory with rmdir.

C:\Hugo\Sites\neocities>rmdir /S public | delete existing public folder, Windows asks for confirmation
C:\Hugo\Sites\neocities>hugo  | generate latest version of site in public directory
C:\Hugo\Sites\neocities>hugo server | test site
C:\Hugo\Sites\neocities>python | push site to NeoCities

Using rsync and webdav on Linux

If you’re a NeoCities supporter you can mount your site as a local drive and use rsync to update.

sudo apt-install davfs2
sudo mkdir /mnt/davs
sudo mount.davfs 'neocities webdav url' /mnt/dav

After hugo build and serve, rsync is used to push the site. Use –dry-run to check before syncing, the info flag reports which files were copied.

rsync -rv /home/neonaut/Documents/Hugo/public/ /mnt/dav/ --checksum --info=COPY --no-whole-file --inplace

I am still sorting out how to upload incrementally with this method. Local tests are incremental but when I rsync webdav it uploads the entire site. The file transfer is also much slower than if I use python to upload.


Site Workflow

Updated 12/27/20 I realized I would update the site more if I had a better workflow, then documented the workflow so I won’t forget it when I invariably ignore the site for 6 months.

I use Atom as my editor with the plugins:

The tool bar is most useful to me for the New Post button. F9 pulls up the terminal, and from there I navigate where I need to for Hugo, etc. markdown-writer has a tag/category/post management feature that relies on a .json listing all those items. The .json is linked by updating _mdwriter.cson. I’m using a temporary .json handcrafted in the fires of Mt. Doom but ideally a Hugo template does the work for us.

When I decided to try microblogging I added a second set of markdown files in a folder called “notes” and created a unique list template that lists the date, title, and full content (unlike the project blog list, which lists the title, categories, and tags). The benefit of this method is the microblog posts will be easy to migrate or combine if needed.

I decided (and I hope I’m right) that so-called “ugly” URLs would be easier to manage with Neocities limited dashboard. Using the dashboard, if I wanted to delete a section of “pretty” posts that each have their own subfolder I would have to enter the subfolder, delete the index.html file, then delete the folder itself. Hugo will generate pages this way with the following config parameter:

uglyurls = true


After adding emoji support and moods on my personal blog, I found I was constantly having to check the emoji reference page or my directories to see what emoji to use. The autocomplete-emojis plugin eliminates the need to use the reference sheet. See my post on Mutant Standard Emojis.

Tag and Category Clouds

Added 10/15/19 I was able to add nice taxonomy clouds thanks to Brian Phyre’s tag cloud post.

<div class="cat-cloud">
   {{ $cats := $.Site.Taxonomies.categories.Alphabetical }}
   {{ $cats := where $cats "Count" ">=" 1 }}
   {{ $cats := where $cats "Term" "not in" (slice "personal") }}
   {{ $count := 0 }}
   {{ range $key, $cat := $cats }}
     {{ if $cat.Term }}
       {{ $catURL := printf "categories/%s" $cat.Term | relURL }}
       {{ if ne $key 0}} {{ end }}
       <a style="font-size: {{ add 100 (mul (sub $cat.Count 8) 2) }}%;"
         href="{{ $catURL }}">{{ $cat.Term }} <span class="cat-cloud-count">{{$cat.Count}}</span></a>
     {{ end }}
   {{ end }}

<div class="tag-cloud">
   {{ $tags := $.Site.Taxonomies.tags.Alphabetical }}
   {{ $tags := where $tags "Count" ">=" 3 }}
   {{ $tags := where $tags "Term" "not in" (slice "tags") }}
   {{ $count := 0 }}
   {{ range $key, $tag := $tags }}
     {{ if $tag.Term }}
       {{ $tagURL := printf "tags/%s" $tag.Term | relURL }}
       {{ if ne $key 0}} {{ end }}
       <a style="font-size: {{ add 100 (mul (sub $tag.Count 8) 5) }}%;"
         href="{{ $tagURL }}">{{ $tag.Term }} <span class="tag-cloud-count">{{$tag.Count}}</span></a>
     {{ end }}
   {{ end }}

Chronological Archives

Using year/month taxonomies per onedrawingperday’s example, see Content Archives in Hugo.

Image Management

atom-markdown-image-assistant allows you to copy-paste into your markdown file, and it automatically saves the image in a directory and provides a link. As I use ugly urls I prefer to have all my images in an images/ folder rather than individual folders. Additionally, imagemin can be used to minify .pngs on the fly.

I quickly ran into issues with image/url paths since mine are custom. Hugo actually ends up putting the images in the microblog home directory, which is actually great for me, but of course the auto-links are for the relative path. Adding the correct home directory to the front of the image link is not a big deal, and the way my content is organized this works out well.

ExifCleaner is a really convenient GUI program for batch removal of metadata.

Image Galleries

I wanted to create photo albums without using existing silos and figured out I could use a static gallery generator and host those on Neocities as well. Sigal was ridiculously easy to set up, so that’s the one I went with. It’s still a work in progress.

Inspoboards & Moodboards

Added 11/08/19 I’ve been looking into making inspoboards. I was introduced to these on Tumblr and they flourish there but Tumblr, by its nature, imposes limitations on the concept. I decided I wanted to be able to create responsive grids that incorporate all kinds of different images. The inspoboards are different from photo galleries because it’s supposed to be an immersive, continuous space, the images are part of a whole rather than individuals.

You can accomplish a great deal with Javascript, but Chris Coyier’s Seamless Responsive Photo Grid is a pure CSS method that is simple and does exactly what I want. You simply put a list of images in a section tag.

With this method you need to manually list all the images. It would be nice if I could just drop photos in a directory and generate a page, but I have a html template now so the main thing is getting all those file names into image tags. There are several cmd commands that can pull filenames from a directory, for instance see MatthewA’s How to easily copy all names of files in a folder in Windows, but I found user Marged on Stackoverflow has a great method for grabbing all the images in a directory and wrapping them in HTML img tags.

for %i in (*.jpg) do echo ^<img src="%i" /^> >> all.html

Easy peasy, just copypasta and you’re done. To see a different implemenation, check out cyber rot.