Skip to content

fix: serve directory index when path segment contains a dot (e.g. /docs/1.4)#230

Open
Ethan-Arrowood wants to merge 3 commits intovercel:mainfrom
Ethan-Arrowood:fix/dotted-directory-segment-404
Open

fix: serve directory index when path segment contains a dot (e.g. /docs/1.4)#230
Ethan-Arrowood wants to merge 3 commits intovercel:mainfrom
Ethan-Arrowood:fix/dotted-directory-segment-404

Conversation

@Ethan-Arrowood
Copy link
Copy Markdown

@Ethan-Arrowood Ethan-Arrowood commented Mar 31, 2026

Problem

I ran into this while using the Docusaurus serve command (which uses serve-handler under the hood) to preview a built Docusaurus site. The site has versioned docs at URLs like /docs/1.4, where 1.4 is a directory on disk (build/docs/1.4/index.html) — not a file with an extension.

path.extname('/docs/1.4') returns '.4' (non-empty), so serve-handler treats the path as a file request and runs the early lstat optimization. That lstat call succeeds — because build/docs/1.4 is a real directory. Since stats is now set, the if (!stats && (cleanUrl || rewrittenPath)) block is skipped, and findRelated never runs to look up index.html inside the directory. With directoryListing: false, renderDirectory returns empty, stats is cleared, and the response is a 404 — even though build/docs/1.4/index.html exists.

Concretely: a build output like this:

build/
└── docs/
    └── 1.4/
        └── index.html   ← exists, but GET /docs/1.4 returns 404

returns 404 when served with:

{
  "cleanUrls": true,
  "directoryListing": false,
  "trailingSlash": false
}

Fix

After the early lstat, if the resolved path turns out to be a directory, clear stats so findRelated runs and can fall back to index.html (or a .html sibling):

if (stats && stats.isDirectory()) {
    stats = null;
}

Affected configuration

Reproduces when all of the following are true:

  • cleanUrls: true
  • directoryListing: false
  • trailingSlash: false
  • The requested path's final segment contains a dot (e.g. /docs/1.4, /api/v1.0)

Tests

Added fixtures and four new test cases covering the affected scenarios:

Request Fixture(s) Expected
GET /1.4 1.4/index.html 200 — directory index served
GET /1.4/ 1.4/index.html 301 → /1.4 (trailing-slash redirect unaffected)
GET /1.2 1.2.html 200 — .html file served via cleanUrls
GET /1.3 1.3/index.html + 1.3.html 200 — directory index wins over .html sibling

path.extname() returns a non-empty string for segments like '4.6',
causing serve-handler to treat them as file requests. If the early
lstat reveals a directory, clear stats so findRelated can fall back
to index.html or the clean-URL .html sibling.

Fixes: paths like /docs/4.6 returning 404 when directoryListing is
disabled and cleanUrls is enabled.
- Rename 4.6/ fixture to 1.4/ (more generic versioning example)
- Add 1.2.html to cover cleanUrls serving a .html file whose name
  contains a dot (requested as /1.2)
- Add 1.3/ + 1.3.html to cover the case where both a dotted directory
  and a same-named .html sibling exist; directory index.html wins
@Ethan-Arrowood Ethan-Arrowood changed the title fix: serve directory index when path segment contains a dot (e.g. /docs/4.6) fix: serve directory index when path segment contains a dot (e.g. /docs/1.4) Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant