August 3, 2026
The trailing slash problem in static sites
Discovering the problem
The story begins with a real issue encountered by an Rspress user.
The user deployed a site under /next/ and used a relative link on the homepage:
At the domain root, the link worked whether or not the URL ended with /. It also resolved correctly when the user visited /next/:
However, when the user visited /next, the link resolved to:
The page itself loaded successfully, so why did the relative link lose the /next segment?
This problem led me to ask: what difference does a trailing slash actually make?
The root URL is a special case
RFC 3986 defines an empty path as equivalent to / for HTTP(S) URLs. Therefore, even if you enter https://example.com, the browser still sends GET /.
A trailing slash changes relative URL resolution
The browser resolves relative links against the current page URL. It treats the final segment of /next as the current resource name, while /next/ means that the current resource is inside a directory:
At the client-router layer, URL matching is usually more permissive than file lookup on a Web server. For example, React Router does not distinguish between paths with and without a trailing / by default. Both URLs below therefore match the same route whose path is /guide:
/guide/guide/
This permissive behavior applies only to route matching; it does not mean that the client automatically normalizes the URL. React Router does not remove .html or treat /index.html as a directory index. Whether /guide.html and /guide/index.html are accessible still depends on additional handling by the framework or Web server.
This explains why “the page loads” and “relative links resolve correctly” are separate questions. Permissive matching only helps the client find a route; it does not update the URL in the address bar. As long as the address bar contains /next, the browser resolves relative paths using the first rule above.
The distinction originates from the early Web, where URL paths were commonly mapped to file systems. The following URLs could refer to two different static outputs:
When a URL points to a directory, a Web server usually appends a trailing slash before looking for index.html. Modern hosting platforms can use rewrites to serve the same page for multiple URLs, but this does not change how browsers resolve relative links.
Solving the problem
The key is to keep the current URL in the address bar consistent with the preferred URL generated by the site.
Rspress route.cleanUrls controls whether generated URLs include the .html extension. However, it only affects links generated by Rspress. It cannot prevent users from arriving at /guide.html, /guide/, or another variant through bookmarks, search results, or external websites.
Rspress therefore introduces route.cleanUrlsRedirect in version 2.1.0. During client startup, it first matches the actual route, then uses history.replaceState to update the address bar to the format selected by cleanUrls. For example, when cleanUrls: true:
/guide.html,/guide/, and/guide/index.html→/guide/reference/index.html→/reference/
Normalization happens before the page renders, does not reload the page, and preserves the site base, query, hash, and existing history state. In the opening example, Rspress changes /next to /next/ before rendering, so relative links no longer escape from /next/.
Starting with Rspress 2.1.0, both cleanUrls and cleanUrlsRedirect will be enabled by default. Client-side normalization cannot fully replace a server-side 301/308 redirect or solve duplicate-URL SEO problems on its own, but it can prevent application errors caused by inconsistent URL formats.
For the complete URL conversion rules and configuration details, see the route.cleanUrlsRedirect API documentation.
Best practices
URLs no longer need to map directly to files on disk. Whether a site uses trailing slashes is a policy choice, not a question of correctness. Browsers and search engines still treat /about and /about/ as two distinct URLs.
To prevent application errors and avoid indexing the same page under multiple URLs:
- Server: Use 301/308 redirects to consolidate
/aboutand/about/into a single preferred URL. Variants such as/about.htmland/about/index.htmlcan be handled at the same time. For example, Cloudflare Workers Static Assets supportsauto-trailing-slashby default: regular HTML files use/file, directory indexes use/folder/, and other forms receive a 307 redirect to the corresponding canonical URL. - Client: Enable
cleanUrlsand keep a client-side redirect as a fallback. If the hosting platform cannot be configured with redirects, the client can write the preferred URL at the earliest page entry point. However, this runs only after the HTML and JavaScript load, so it cannot fully replace a server-side redirect.
Keep every URL source consistent: internal links, canonical URLs, sitemaps, and shared URLs should all use the preferred form. Other variants should redirect to it with 301/308 responses.
Final checks:
/fileand/file/should not both serve the same content as two independent URLs.- Public URLs, static output, Rspress configuration, and CDN rules should follow the same trailing-slash policy.