diff --git a/.changeset/fix-advanced-routing-404-fallback.md b/.changeset/fix-advanced-routing-404-fallback.md new file mode 100644 index 000000000000..4ec812e69c1b --- /dev/null +++ b/.changeset/fix-advanced-routing-404-fallback.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug where `experimental.advancedRouting` with `astro/hono` handlers threw `TypeError: Cannot read properties of undefined (reading 'route')` for unmatched routes instead of rendering the custom 404 page. diff --git a/packages/astro/src/core/fetch/fetch-state.ts b/packages/astro/src/core/fetch/fetch-state.ts index afbbfc5eb552..3b531477e8c4 100644 --- a/packages/astro/src/core/fetch/fetch-state.ts +++ b/packages/astro/src/core/fetch/fetch-state.ts @@ -15,7 +15,6 @@ import { AstroCookies } from '../cookies/index.js'; import { type Pipeline, Slots } from '../render/index.js'; import { ASTRO_GENERATOR, - DEFAULT_404_COMPONENT, fetchStateSymbol, originPathnameSymbol, pipelineSymbol, @@ -36,7 +35,7 @@ import { Rewrites } from '../rewrites/handler.js'; import { isRoute404or500, isRouteServerIsland } from '../routing/match.js'; import { normalizeUrl } from '../util/normalized-url.js'; import { getOriginPathname, setOriginPathname } from '../routing/rewrite.js'; -import { routeHasHtmlExtension } from '../routing/helpers.js'; +import { getCustom404Route, routeHasHtmlExtension } from '../routing/helpers.js'; import type { ResolvedRenderOptions } from '../app/base.js'; import { getRenderOptions } from '../app/render-options.js'; import { getFirstForwardedValue, validateForwardedHeaders } from '../app/validate-headers.js'; @@ -820,9 +819,14 @@ export class FetchState implements AstroFetchState { // Fall back to a 404 route so middleware can still run. if (!this.routeData) { - this.routeData = pipeline.manifestData.routes.find( - (route) => route.component === '404.astro' || route.component === DEFAULT_404_COMPONENT, - ); + const custom404 = getCustom404Route(pipeline.manifestData); + // Only use SSR 404 routes here. Prerendered 404 pages are already + // built to static HTML, so the pipeline can't render them at + // runtime. Leaving routeData unset lets the error handler serve + // the pre-built page from disk instead. + if (custom404 && !custom404.prerender) { + this.routeData = custom404; + } } if (!this.routeData) { pipeline.logger.debug('router', "Astro hasn't found routes that match " + this.request.url); diff --git a/packages/astro/test/units/fetch/index.test.ts b/packages/astro/test/units/fetch/index.test.ts index 9f2d6fb33409..3741e1b15d4f 100644 --- a/packages/astro/test/units/fetch/index.test.ts +++ b/packages/astro/test/units/fetch/index.test.ts @@ -79,6 +79,16 @@ describe('FetchState (astro/fetch)', () => { assert.equal(state.routeData!.route, '/[b_ssr]'); assert.equal(state.routeData!.prerender, false); }); + + it('falls back to the 404 route when no route matches', () => { + const notFoundPage = createPage(simplePage, { route: '/404' }); + const app = createTestApp([createPage(simplePage, { route: '/' }), notFoundPage]); + const request = stampApp(new Request('http://example.com/does-not-exist'), app); + const state = new FetchState(request); + + assert.ok(state.routeData, 'routeData should fall back to the 404 route'); + assert.equal(state.routeData!.route, '/404'); + }); }); // #endregion @@ -238,6 +248,24 @@ describe('pages()', () => { assert.match(text, /