How to create Hugo blog post urls without subfolders in an SEO-friendly way

If you want blog post urls of the form example.com/my-blog-post/, here’s how to do that without hurting your SEO.

Ron Erdos
Updated April 14, 2021
Tested with Hugo version 0.82.0

Overview

Do you want your Hugo blog post urls to have the form example.com/my-blog-post/, without any subdirectories?

I’ll show you how you to do this without causing any SEO issues.

Before hitting on this solution, I tried a few other methods, but they all produced duplicate content (the same content on different urls), which is not great for SEO.

And Google does have a habit of finding duplicate content, even if you don’t link to it.

Let’s dive in to the solution!

Method

The best, most SEO-friendly way to remove subfolders from your blog post urls is quite simple: Just put all your blog posts and all your pages in the root (top level) of your /content/ folder.

In other words, don’t use any subfolders (such as /posts/ or /pages/) inside your /content/ folder.

However, there are two tradeoffs. I do have effective workarounds for those tradeoffs, though.

Tradeoffs

Tradeoff 1: Messy repo

The first tradeoff is that you’ll end up with all your blog posts and all your pages (e.g. your “About” and “Contact” pages) mixed together in your /content/ directory, rather than neatly organised in folders.

It means that when you want to work on just your blog posts or just your pages, it’s a lot more work.

Your /content/ folder might look like this, with pages and posts mixed together:

📁 content
   about.md
   blog-post-about-nasa.md
   contact.md
   yet-another-nasa-blog-post.md

While that’s fine from Hugo’s perspective, it can make it hard from a human perspective to quickly find the piece of content you’re looking for.

Workaround to Tradeoff 1

Let’s fix this and bring order back to our repo.

Step 1: Add an underscore to the start of page filenames

Simply add a leading underscore to the filename of pages.

When you do this in our example above, our /content/ folder looks like this:

📁 content
   _about.md
   _contact.md
   blog-post-about-nasa.md
   yet-another-nasa-blog-post.md

Voila! All our pages are up top; all our posts are down below.

We’re not finished yet though; we need to override the url of each page, otherwise they will have unsightly underscores, like this:

example.com/_about/
example.com/_contact/

Step 2: Override the url of each page using YAML front matter

You can override this default behaviour by using a url field in your page front matter. It can be named either url or slug.

So in the first page above, _about.md, here’s how our YAML front matter might look:

---
title: About Us
date: 2020-03-09 07:00:00 +11:00
url: "/about/"
---

Now our About page will have a nice, clean url that looks like this:

example.com/about/

I don’t need this url YAML front matter field for my blog posts, because their filenames don’t have a leading underscore, so I’m happy for Hugo to use blog post filenames as the URI.

So my blog post urls look like this: example.com/blog-post-about-nasa/

Perfect.

Now there’s just one more tradeoff we need to know about and manage.

Tradeoff 2: Pages will appear in your list of blog posts

OK, so now your content team can quickly tell which content files are blog posts, and which are pages.

However, Hugo won’t know the difference when it comes time to make a list of blog posts (e.g. on the homepage) unless we give it something to work with.

And unless we help Hugo in this way, your pages (such as About and Contact) will end up in your list of blog posts. Which means they could end up featured way too prominently on your homepage.

Workaround to Tradeoff 2

We’re going to use Hugo’s build options to stop our pages (such as About and Contact) from appearing in our lists (such as a list of posts on the homepage).

We simply add:

_build:
  list: never

… to the YAML front matter of pages.

So the YAML front matter of our About page would look like this:

---
title: About Us
date: 2020-03-09 07:00:00 +11:00
url: "/about/"
_build:
  list: never
---

Thanks to Krzysztof Paszek for pointing out that we can use build options—I had unnecessarily rolled my own functionality for this.

Wrapping up

Once again, this is the best, most-SEO friendly way I’ve found to publish Hugo blog post urls without any subfolders. There are a few one-time tasks to configure, but once they’re done, you’ll reap the benefits forever.

"Thanks so much for your work ... I'm migrating my WordPress blog to Hugo and it's been really helpful." — Francisco S., engineer and blogger

"I love your content. The information that you provide have been very useful in the development of my personal website." — Mattia C., engineer and researcher

The planets in our solar system