Skip to content

Astro, Cloudflare Pages, and Non-Trailing Slash Canonical URLs

Posted on 1 April 2026 software

I have hosted this site on Cloudflare Pages for a few years now, and embarrassingly, I only recently noticed that there was a mismatch between my dev environment (Astro’s dev server) and production (Cloudflare Pages) when it came to trailing slashes in URLs.

I have a slight preference for non-trailing slash URLs (e.g. https://jamesharding.dev/posts/happy-retirement-dad instead of https://jamesharding.dev/posts/happy-retirement-dad/), and I had set up my dev environment to use non-trailing slashes. However, Cloudflare Pages automatically adds a trailing slash to all directory-based files (the Astro default, which I had not changed), which meant that the actual canonical URLs generated by Astro in production were different from those in development.

To fix this, I had to change my Astro configuration to file:

export default {
  trailingSlash: 'never',
  build: {
    format: 'file',
  },
  // ... other config options
};

This tells Astro to generate files rather than folders (for example /about.html instead of /about/index.html) which is compatible with Cloudflare Pages’ automatic trailing slash behaviour.

One more problem though: URL’s generated by Astro’s Astro.url API were now including the .html extension, which I didn’t want since Cloudflare Pages automatically strips the .html extension. I am using the Astro.url API to generate the canonical URL for each page, and OpenGraph image URLs, and I wanted to ensure that it matched the non-trailing slash format.

A quick helper function to clean the path and remove the .html extension did the trick:

// utils.ts

export const cleanPath = (path: string): string => path.replace(/\.html$/, '');

And now in my main Layout file:

// Layout.astro

// In the frontmatter
import { cleanPath } from '../utils';
const canonicalURL = new URL(cleanPath(Astro.url.pathname), Astro.site);

// In the HTML
<link rel="canonical" href={canonicalURL.toString()} />

Anywhere that I use Astro.url to generate URLs, I wrap it with the cleanPath function to ensure that the .html extension is removed and the URL format is consistent with my preference for non-trailing slashes.

A Confession

If you’re looking at the URL bar of the website now, you’ll see that there are trailing slashes.

After lots of research and “decision paralysis”, I decided that I was okay with using trailing slashes, as long as the canonical URLs were consistent. The trailing slashes in the URL bar are a minor inconvenience, but at least the canonical URLs are correct and consistent across both development and production environments.

It also means that I can remove this hacky cleanPath function. It just required a big find-and-replace to update all of the hard-coded URLs to use slashes.

I spent a long time trying to decide which of these to use, and the fact the Cloudflare, Apple, Microsoft, etc. all use trailing slashes by default made me feel like it was the “right” choice, even though I personally slightly prefer non-trailing slashes.

In the end, I decided that consistency and simplicity were more important than my personal preference!



← Back to all notes