How does a file host resolve a URL like /docs/setup to a document, and why can a trailing slash change the result?
answer
- routes are patterns, files are names
- flat file or directory index
- resolution is host config, not HTTP
- the two slash forms are different URLs
- relative links resolve against different bases
basics
~20 sThe host maps the path onto stored bytes. An extensionless path resolves either to a file with an added extension or to a directory's index document, and hosts differ - so one form can work where the other misses.
solid answer
~50 sA static build emits a route as either a flat document (`/docs/setup.html`) or a directory with an index document (`/docs/setup/index.html`). A host serves an extensionless URL by one of two conventions: append an extension, or treat the path as a directory and serve its index. Which one it does is host configuration, not a web standard, so the same artifact can be fully reachable on one host and half broken on another. The trailing slash matters because the two forms are different URLs: many hosts redirect one to the other, and if they do not, the same document is reachable twice - duplicating cache entries and analytics rows - and relative references resolve against different base paths in each form. Pick one form, make the build emit it, and configure the host to redirect the other.
code
http · 11 linesGET /docs/setup HTTP/1.1
Host: example.com
HTTP/1.1 301 Moved Permanently
Location: /docs/setup/
GET /docs/setup/ HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
Content-Type: text/htmlgo deeper
Know that an exported route becomes either a flat document or a folder with an index document, and that the host decides which URL form reaches it.
Explain the resolution steps a host may take, and why the two slash forms are distinct URLs that resolve relative references against different base paths.
Diagnose from the symptom: works when clicked, fails on reload or paste. Probe the deployed artifact by URL form and check statuses and redirects rather than guessing.
Make the canonical form a written convention enforced at build time, because URL shape leaks into caches, analytics, search indexing and every external link that outlives the team.
## The problem: routes were never files A route is a pattern. A file is a name in a directory. A static export has to project the first onto the second, and the projection is lossy in exactly the places URLs are ambiguous. Two layouts are common for the same route: - **flat**: `/docs/setup` becomes `docs/setup.html` - **directory index**: `/docs/setup` becomes `docs/setup/index.html` Both are legitimate, and builds differ in which they emit - sometimes configurably. Neither is what the browser asks for, which is a path with no extension. | | Flat document | Directory index | |---|---|---| | File emitted | `docs/setup.html` | `docs/setup/index.html` | | Served natively at | `/docs/setup.html` | `/docs/setup/` | | Needs from the host | an implicit `.html` rule | an index-document rule | | Canonical URL usually | no trailing slash | trailing slash | | Relative reference base | `/docs/` | `/docs/setup/` | ## The host's resolution conventions When a request arrives for `/docs/setup`, a file host does some subset of: 1. Look for a stored object at exactly `docs/setup`. 2. Try `docs/setup.html` - an implicit extension rule. 3. Treat the path as a directory and serve `docs/setup/index.html`. 4. Redirect to `/docs/setup/` and resolve the index from there. 5. Fall through to the configured not-found behaviour. **None of this is specified by HTTP.** It is host configuration, and it varies: a host that only does step 3 will not serve a flat build, and a host that only does step 2 will not serve a directory build without a redirect. This is why an export that works perfectly from a local preview can return not-found on a real target - the preview server implemented a friendlier subset. ## Why the trailing slash is not cosmetic `/docs/setup` and `/docs/setup/` are **different URLs**. Three things follow: - **Relative references resolve differently.** In `/docs/setup`, a reference to `img/a.png` resolves to `/docs/img/a.png`; from `/docs/setup/` it resolves to `/docs/setup/img/a.png`. A document reachable at both forms can only have its relative links correct in one of them. Root-relative or absolute references avoid this entirely, which is why exports usually prefer them. - **Duplication.** If both forms return 200, the page has two addresses: two cache entries at every layer, two rows in analytics, and two URLs a search engine must reconcile. A canonical link helps search engines and does nothing for the caches. - **A redirect on every navigation.** If the host normalises by redirecting, a link written in the non-canonical form costs an extra round trip - and the app's own internal links are the usual offenders, because the client router never performs that redirect while the shipped bundle handles navigation, so the inconsistency only appears on a full page load. The fix is boring and effective: **choose one form**, make the build emit that form, write every internal link in it, and configure the host to redirect the other permanently. ## Two more ways the projection bites - **Case sensitivity.** Many object stores match names exactly, while a developer machine's filesystem may not. A link written `/Docs/Setup/` that worked locally can miss remotely. Lowercase everything the build emits and everything the app links to. - **Paths that were never emitted.** Routes resolved entirely by the client router have no file behind them, so a deep link typed into the address bar or arriving from a shared URL hits the host's fallback. The usual answer is a catch-all rewrite to the shell document, but scope it: a rewrite that swallows *every* unmatched path also swallows requests for missing assets, which then arrive as HTML and fail to parse. ## Who decides the emitted layout The layout is a build choice, and it has to be made compatible with a host choice. In practice you are picking one of three combinations: a flat build on a host with an implicit extension rule, a directory build on a host that resolves index documents, or either build on a host that redirects between the forms. The failure cases are the mismatches - a directory build on a host that only tries extensions returns misses for every route but the root, and a flat build on an index-only host does the same. Because the symptom is identical in both directions ("most routes 404, the home page works"), check what the build emitted *and* what the host resolves before changing either. ## Diagnosing it The symptom - "it works when I click through the app, but fails on reload or when pasted" - is the signature of a path the host cannot resolve, because in-app navigation never asks the host for a document at all. Test the deployed artifact the way readers reach it: 1. Request each URL form directly and record the status and any `Location`. 2. Do it for a route the build prerendered and for a route only the client router knows. 3. Request a deliberately missing asset path and confirm it does not return HTML. Those three probes catch nearly every resolution defect an export ships with.
- Why do relative asset references so often break only after deployment?A local preview usually serves one canonical form of each path, so relative references resolve consistently. In production the same document can be reached with and without a trailing slash, and the base path differs between them, so `img/a.png` points at two different places. Root-relative references remove the ambiguity.
- Should internal links be written with or without the trailing slash?Whichever form the build emits and the host serves without redirecting - the point is that one form is canonical everywhere. Mismatched internal links are invisible during in-app navigation, because the client router handles those, and only surface as an extra redirect on a full load.
saying these in an interview costs you the question
- Calling the trailing slash purely cosmetic
- Assuming every host resolves an extensionless path the same way
- Trusting the local preview server to match the real host
- Thinking relative asset paths are safe because the document is identical
- Believing a catch-all rewrite to the shell makes every URL correct