Skip to content

fix(serve): serve index.html from public subdirectories - #22559

Open
desty wants to merge 1 commit into
vitejs:mainfrom
desty:fix/public-subdir-index-html
Open

fix(serve): serve index.html from public subdirectories#22559
desty wants to merge 1 commit into
vitejs:mainfrom
desty:fix/public-subdir-index-html

Conversation

@desty

@desty desty commented May 31, 2026

Copy link
Copy Markdown

Description

Fixes #6714

When index.html lives under a public/ subdirectory (e.g. public/foo/bar/index.html), navigating to /foo/bar/ now serves that file — matching /foo/bar/index.html and typical static-server directory index behavior.

Also aligns extensionless public HTML resolution between dev and preview:

URL resolves to (when present)
/foo/bar/ public/foo/bar/index.html
/foo public/foo.html

Approach

Update servePublicMiddleware to mirror htmlFallbackMiddleware for public files only:

  • rewrite only for GET/HEAD with Accept containing text/html or */*
  • require the rewritten path to exist in the in-memory publicFiles set
  • then serve with the existing sirv instance (extensions: [])

This deliberately does not use sirv's extensions: ['html'], which would also map /foo/foo/index.html (preview does not do that).

Tests

Playground html coverage (serve + build):

  • /subdir/ and /subdir/index.htmlpublic/subdir/index.html
  • /public-file and /public-file.htmlpublic-file.html

Review follow-ups addressed

  • Run the new test in build as well (not serve-only) — @sapphi-red
  • Make extensionless public HTML behavior consistent between dev and preview — @sapphi-red
  • Avoid broad sirv extensions: ['html'] / document intentional /file/file.html@avinashkamat48

Verification

pnpm test-serve html   # 65 passed
# new case also verified under:
VITE_TEST_BUILD=1 vitest run -c vitest.config.e2e.ts html -t "should serve index.html from public subdirectories"

Rebased onto latest main.

@sapphi-red sapphi-red added feat: html p2-nice-to-have Not breaking anything but nice to have (priority) labels Jun 1, 2026
Comment thread playground/html/__tests__/html.spec.ts Outdated
expect(await bothSlashIndexHtml.text()).toContain('both/index.html')
})

test.runIf(isServe)(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should run this test in build as well so that it's consistent.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f1caef5 — removed runIf(isServe) so this test now runs in build as well (the extensionless-alias assertions stay dev-only since the fallback middleware only exists on the dev server).

Verified locally:

  • pnpm test-serve html → 65 passed
  • pnpm test-build html✓ should serve index.html from public subdirectories

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the extensionless-alias assertions stay dev-only since the fallback middleware only exists on the dev server

we should make this behavior consistent in dev and build.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 472b50b. Dev now mirrors the preview server's html fallback for public files: /foo resolves to /foo.html and /foo/ to /foo/index.html, using the same method/Accept conditions as htmlFallbackMiddleware (instead of sirv's extensions option, which would also resolve /foo/foo/index.html — something the preview server does not do).

The test assertions now run unconditionally in both serve and build:

  • pnpm test-serve html → 65 passed
  • pnpm test-build html → 55 passed (✓ should serve index.html from public subdirectories in both)
  • assets / fs-serve playgrounds pass in serve as regression checks

@avinashkamat48 avinashkamat48 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding extensions: ["html"] to the public sirv middleware appears to broaden behavior beyond directory indexes: an explicit request for /foo can now resolve to public/foo.html if that file exists. The PR title/test only covers /subdir/ -> /subdir/index.html, so this extensionless-file fallback may be an unintended public serving change. Please add a test for /file vs /file.html (or avoid the global extensions option and handle only trailing-slash index.html) so the new behavior is intentional.

@desty
desty force-pushed the fix/public-subdir-index-html branch from 472b50b to b0ade3b Compare August 2, 2026 07:53
@desty

desty commented Aug 2, 2026

Copy link
Copy Markdown
Author

Rebased onto latest main and updated the PR description to match the final approach (472b50b / current tip).

Review feedback is addressed as follows:

  • test runs in serve and build
  • public HTML fallback mirrors htmlFallbackMiddleware so dev matches preview (/foo/index.html, /foo.html)
  • no sirv extensions: ['html'] (avoids /foo/foo/index.html)

@sapphi-red ready for another look when you have time.

Serve public/ subdirectory indexes at trailing-slash URLs and align
extensionless .html resolution with preview's htmlFallbackMiddleware.

When public/foo/bar/index.html exists, /foo/bar/ now serves it (fixes vitejs#6714).
For Accept: text/html requests, /foo also resolves to public/foo.html so
dev matches preview. Avoids sirv extensions which would also map
/foo -> /foo/index.html (preview does not).
@desty
desty force-pushed the fix/public-subdir-index-html branch from b0ade3b to 02638e9 Compare September 5, 2026 02:43
@desty

desty commented Sep 5, 2026

Copy link
Copy Markdown
Author

Rebased onto latest main.

Review feedback from June/July is still in this patch:

  • the new test runs in serve and build
  • public HTML fallback mirrors htmlFallbackMiddleware (/foo/index.html, /foo.html)
  • no sirv extensions: ['html'] (avoids /foo/foo/index.html, which preview does not do)

The previous Build&Test: node-24, macos-latest failure was playground/wasm/__tests__/wasm.spec.ts > should work when inlined — unrelated to this change.

@sapphi-red ready for another look when you have time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat: html p2-nice-to-have Not breaking anything but nice to have (priority)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serve index.html inside subdirectory of public from subdirectory path

3 participants