diff --git a/.changeset/chatty-kiwis-fail.md b/.changeset/chatty-kiwis-fail.md new file mode 100644 index 0000000000..823cbbc315 --- /dev/null +++ b/.changeset/chatty-kiwis-fail.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix .html.astro file routing in dev diff --git a/packages/astro/src/vite-plugin-astro-server/request.ts b/packages/astro/src/vite-plugin-astro-server/request.ts index 29431a9295..29964f031c 100644 --- a/packages/astro/src/vite-plugin-astro-server/request.ts +++ b/packages/astro/src/vite-plugin-astro-server/request.ts @@ -23,10 +23,7 @@ export async function handleRequest( const { config } = settings; const origin = `${moduleLoader.isHttps() ? 'https' : 'http'}://${req.headers.host}`; const buildingToSSR = config.output === 'server'; - // Ignore `.html` extensions and `index.html` in request URLS to ensure that - // routing behavior matches production builds. This supports both file and directory - // build formats, and is necessary based on how the manifest tracks build targets. - const url = new URL(origin + req.url?.replace(/(index)?\.html$/, '')); + const url = new URL(origin + req.url); const pathname = decodeURI(url.pathname); // Add config.base back to url before passing it to SSR @@ -60,8 +57,18 @@ export async function handleRequest( pathname, async run() { const matchedRoute = await matchRoute(pathname, env, manifest); - - return await handleRoute(matchedRoute, url, pathname, body, origin, env, manifest, req, res); + const resolvedPathname = matchedRoute?.resolvedPathname ?? pathname; + return await handleRoute( + matchedRoute, + url, + resolvedPathname, + body, + origin, + env, + manifest, + req, + res + ); }, onError(_err) { const err = createSafeError(_err); diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index aab47ecc74..9cd4ab239a 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -1,7 +1,7 @@ import type http from 'http'; import mime from 'mime'; -import type { AstroSettings, ManifestData } from '../@types/astro'; -import { DevelopmentEnvironment, SSROptions } from '../core/render/dev/index'; +import type { AstroSettings, ComponentInstance, ManifestData, RouteData } from '../@types/astro'; +import { ComponentPreload, DevelopmentEnvironment, SSROptions } from '../core/render/dev/index'; import { attachToResponse } from '../core/cookies/index.js'; import { call as callEndpoint } from '../core/endpoint/dev/index.js'; @@ -23,6 +23,14 @@ type AsyncReturnType Promise> = T extends ( ? R : any; +interface MatchedRoute { + route: RouteData; + filePath: URL; + resolvedPathname: string; + preloadedComponent: ComponentPreload; + mod: ComponentInstance; +} + function getCustom404Route({ config }: AstroSettings, manifest: ManifestData) { // For Windows compat, use relative page paths to match the 404 route const relPages = resolvePages(config).href.replace(config.root.href, ''); @@ -34,7 +42,7 @@ export async function matchRoute( pathname: string, env: DevelopmentEnvironment, manifest: ManifestData -) { +): Promise { const { logging, settings, routeCache } = env; const matches = matchAllRoutes(pathname, manifest); @@ -57,12 +65,21 @@ export async function matchRoute( return { route: maybeRoute, filePath, + resolvedPathname: pathname, preloadedComponent, mod, }; } } + // Try without `.html` extensions or `index.html` in request URLs to mimic + // routing behavior in production builds. This supports both file and directory + // build formats, and is necessary based on how the manifest tracks build targets. + const altPathname = pathname.replace(/(index)?\.html$/, ''); + if (altPathname !== pathname) { + return await matchRoute(altPathname, env, manifest); + } + if (matches.length) { const possibleRoutes = matches.flatMap((route) => route.component); @@ -86,6 +103,7 @@ export async function matchRoute( return { route: custom404, filePath, + resolvedPathname: pathname, preloadedComponent, mod, }; diff --git a/packages/astro/test/dev-routing.test.js b/packages/astro/test/dev-routing.test.js index 7188e1ff0c..5b46cf249e 100644 --- a/packages/astro/test/dev-routing.test.js +++ b/packages/astro/test/dev-routing.test.js @@ -322,5 +322,17 @@ describe('Development Routing', () => { const response = await fixture.fetch('/1'); expect(response.status).to.equal(200); }); + + it('200 when loading /html-ext/1', async () => { + const response = await fixture.fetch('/html-ext/1'); + expect(response.status).to.equal(200); + expect(await response.text()).includes('none: 1') + }); + + it('200 when loading /html-ext/1.html', async () => { + const response = await fixture.fetch('/html-ext/1.html'); + expect(response.status).to.equal(200); + expect(await response.text()).includes('html: 1') + }); }); }); diff --git a/packages/astro/test/fixtures/without-site-config/src/pages/html-ext/[slug].astro b/packages/astro/test/fixtures/without-site-config/src/pages/html-ext/[slug].astro new file mode 100644 index 0000000000..599fd0f26e --- /dev/null +++ b/packages/astro/test/fixtures/without-site-config/src/pages/html-ext/[slug].astro @@ -0,0 +1,6 @@ +--- +export function getStaticPaths() { + return [{ params: { slug: '1' } }]; +} +--- +

none: {Astro.params.slug}

diff --git a/packages/astro/test/fixtures/without-site-config/src/pages/html-ext/[slug].html.astro b/packages/astro/test/fixtures/without-site-config/src/pages/html-ext/[slug].html.astro new file mode 100644 index 0000000000..79ab2d434b --- /dev/null +++ b/packages/astro/test/fixtures/without-site-config/src/pages/html-ext/[slug].html.astro @@ -0,0 +1,6 @@ +--- +export function getStaticPaths() { + return [{ params: { slug: '1' } }]; +} +--- +

html: {Astro.params.slug}