Pretty Hugo URLs Without a Trailing Slash Using Nginx

I care a lot about URLs, to the point that I wish I cared less about them. Bad URL structures are unprofessional and look crappy.

Hugo does a pretty good job of generating URLs. In fact, up to version v0.11 they generated amazing URLs! And then they started adding the dreaded trailing slash. Instead of:

https://example.com/my-post

We get:

https://example.com/my-post/

Eww. And they call these Pretty URLs!

Okay, so how do we get the pre v0.12 behavior back? It’s actually really easy. We just need to add a few lines to our config.toml:

# config.toml

uglyurls = true
[permalinks]
  post = ":filename"

Yes. In order to get URLs that are actually pretty, we need to set the uglyurls flag to true. I think it’s hilarious too.

The first line both changes how Hugo generates the static files, and changes the way it generates links by appending .html to everything.

The second line changes how we generate URLs for files of type post. For any files in content/post URLs will be generated by just the filename, without a trailing slash or file extension.

To understand why this works, it’s helpful to understand what Hugo is doing behind the scenes and how servers like Nginx and Apache serve static files. Adding the uglyurls flag changes how files are generated. Normally you would get a file structure like this:

# content/my-page.md

# public/my-page/index.html

Then when a browser asks the server for https://example.com/my-page/, the server sees that it is a directory, and serves the default file inside of it. What uglyurls does is change the generation of these files like so:

# content/my-page.md

# public/my-page.html

Now in order for the server to find this resource, the browser would need to request https://example.com/my-page.html. But wait, that’s not what we want! So let’s tell Nginx to look for our files differently. In your Nginx config file, we just need to change one line:

# /etc/nginx/conf.d/default

location / {
  try_files $uri.html $uri $uri/ =404;
}

You should already have a try_files line. Comment it out and replace it with this one. What this does is every time Nginx receives a request, it first tries to append .html onto the URL. If there is a file that matches that it will serve it. If not it’ll try the URL as is, and then it’ll try the URL with a trailing slash.

This broke my URLs on my dev machine!

Yep. :( It’s kind of a hack. But don’t fret too much! There’s an easy fix.

I keep two versions of my config file. One for my development server and one for production.

# config.toml
...

# config-production.toml
...
uglyurls = true
[permalinks]
  post = ":filename"

Then, when I go to do a production deploy, I tell Hugo to use the production config.

hugo --source=/var/www/my-site/ --config=config-production.toml

Voilà! URLs that are actually pretty in production and URLs that are semi-pretty in development.

I seriously wish I cared less about petty things like URLs sometimes. :)

Penned on March 11, 2015 by Kevin Sweet