TL;DR
- If you’re serving an Astro static site with Nginx, standardizing on URLs with trailing slashes is easier than trying to remove them.
- Configure Astro to require trailing slashes at development time using the trailingSlash configuration option so inconsistent URLs break in
dev
. - It’s a good idea to make the default behaviors explicit in case anything changes later:
The rest of this post dives into more detail on why you might want to standardize on URLs with trailing slashes, and how to do so with Astro and Nginx. This mostly documents my own learning process.
why normalize URLs?
Something as simple as whether pages on your site are addressable both with and without a trailing slash can have a meaningful impact on how search engines rank your site.
Building your site in such a way that both /blog
and /blog/
point to the same page seems like a user-friendly thing to do, but search engines will treat each variation as a distinct page. If you should be so fortunate as to receive inbound links from other sites, Google will view links to the slashed and “bare” versions of your page as distinct URLs, diluting the SEO benefits of those inbound links1.
slashes and semantics
Historically, URLs ending with a slash are treated as directories, while those without a slash are treated as files:
These conventions emerged from the way that file-based web servers like Apache and Nginx handle URLs. When a request is made for a URL ending with a slash, the server will look for an index.html
file in the directory corresponding to the URL. When a request is made for a URL without a trailing slash, the server will look for a file with the same name as the URL. These behaviors remain the norm for static web servers to this day.
slashes are arbitrary for dynamic web apps
While trailing slashes have clear-cut semantics when dealing with static files in a directory structure, they don’t have any special meaning for dynamic web applications that use routing to serve content. At a high level, routing is a technique for mapping a requested URL to code that will generate the content to be served. Routing-based web servers like node’s Express or python’s Flask parse a URL to determine which function to call, not which file to serve, so the distinction between “files” and “directories” no longer applies.
Dynamic servers are consequently free to treat URLs with and without trailing slashes as equivalent, or redirect one to the other based on aesthetic preferences. For instance, SvelteKit removes trailing slashes by default. For full-stack applications with a node backend, SvelteKit will redirect routes with trailing slashes to their non-slashed counterparts, e.g. /blog/
to /blog
.
static site consistency is easier with slashes
Redirection is critical for user experience; URLs should work whether or not they have a trailing slash. When serving a static site with Nginx, only Nginx can handle redirects in production. So the sane thing to do is to standardize on trailing slashes and build your site with index files in directories so that you aren’t fighting against Nginx’s default behavior.
Astro configuration
Astro has a configuration option to control whether or not routes with trailing slashes are matched (i.e. treated as valid URLs), but for static sites this option only applies during development. Still, it’s a good idea to have only slash-ending URLs work at development time.
Also, while Astro will default to building your static routes as directories with index files, it’s a good idea to make this explicit in your configuration using the build.format option.
Nginx configuration
Inside your main Nginx configuration file (mine is called default.conf
), you’ll want to add a try_files directive to your location /
block. This directive will tell Nginx to look for a file with the same name as the requested URI, and if it doesn’t find one, it will look for a directory with the same name and serve the index.html
file inside it. If that fails, it will return a 404 error. This is default Nginx behavior made explicit.
conclusion
I’m bothered when code works for reasons that I don’t understand, and the fact that Nginx’s default redirection behavior is undocumented sent me down a rabbit hole. You don’t need to touch the default settings for either Astro or Nginx to get consistent URLs with a trailing slash, for the reasons I’ve outlined above. But codifying these default behaviors in your settings should make your configuration less brittle.
Footnotes
Some familiarity with Google’s PageRank algorithm is helpful here. Inbound links are a key determinant of how well a URL will rank in search results, so spreading links for the same page between two URLs results in each URL ranking lower than if all links were consolidated. But this only really matters if you have inbound links to begin with, so this could be considered an aspirational problem for most blog authors (myself included). ↩