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.
Updated April 14, 2021Tested with Hugo version 0.82.0
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!
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
In other words, don’t use any subfolders (such as
/pages/) inside your
However, there are two tradeoffs. I do have effective workarounds for those tradeoffs, though.
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.
/content/ folder might look like this, with pages and posts mixed together:
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:
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:
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
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:
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:
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.
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.