diff --git a/.changeset/hot-dingos-dress.md b/.changeset/hot-dingos-dress.md new file mode 100644 index 0000000000000..c3d59944c12b4 --- /dev/null +++ b/.changeset/hot-dingos-dress.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes an issue where `i18n` is enabled, Astro couldn't render the `404.astro` component when navigating non-existent routes. diff --git a/packages/astro/src/i18n/middleware.ts b/packages/astro/src/i18n/middleware.ts index 097332805759a..f4649dce2d32a 100644 --- a/packages/astro/src/i18n/middleware.ts +++ b/packages/astro/src/i18n/middleware.ts @@ -1,6 +1,6 @@ import type { APIContext, MiddlewareHandler, SSRManifest } from '../@types/astro.js'; import type { SSRManifestI18n } from '../core/app/types.js'; -import { ROUTE_TYPE_HEADER } from '../core/constants.js'; +import { REROUTE_DIRECTIVE_HEADER, ROUTE_TYPE_HEADER } from '../core/constants.js'; import { type MiddlewarePayload, normalizeTheLocale, @@ -65,6 +65,12 @@ export function createI18nMiddleware( return async (context, next) => { const response = await next(); const type = response.headers.get(ROUTE_TYPE_HEADER); + + // This is case where we are internally rendering a 404/500, so we need to bypass checks that were done already + const isReroute = response.headers.get(REROUTE_DIRECTIVE_HEADER); + if (isReroute === 'no' && typeof i18n.fallback === 'undefined') { + return response; + } // If the route we're processing is not a page, then we ignore it if (type !== 'page' && type !== 'fallback') { return response; diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 8de52d158bb03..837e2ef089381 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -127,7 +127,6 @@ type HandleRoute = { url: URL; pathname: string; body: ArrayBuffer | undefined; - origin: string; manifestData: ManifestData; incomingRequest: http.IncomingMessage; incomingResponse: http.ServerResponse; @@ -139,7 +138,6 @@ export async function handleRoute({ url, pathname, body, - origin, pipeline, manifestData, incomingRequest, @@ -156,7 +154,6 @@ export async function handleRoute({ let request: Request; let renderContext: RenderContext; let mod: ComponentInstance | undefined = undefined; - let options: SSROptions | undefined = undefined; let route: RouteData; const middleware = (await loadMiddleware(loader)).onRequest; const locals = Reflect.get(incomingRequest, clientLocalsSymbol); @@ -181,15 +178,6 @@ export async function handleRoute({ if (value) incomingResponse.setHeader(name, value); } - options = { - pipeline, - filePath, - preload: preloadedComponent, - pathname, - request, - route, - }; - mod = preloadedComponent; renderContext = await RenderContext.create({ @@ -248,18 +236,17 @@ export async function handleRoute({ if (statusCode === 404 && response.headers.get(REROUTE_DIRECTIVE_HEADER) !== 'no') { const fourOhFourRoute = await matchRoute('/404', manifestData, pipeline); - if (options && options.route !== fourOhFourRoute?.route) - return handleRoute({ - ...options, - matchedRoute: fourOhFourRoute, - url: new URL(pathname, url), - body, - origin, + if (fourOhFourRoute) { + renderContext = await RenderContext.create({ + locals, pipeline, - manifestData, - incomingRequest, - incomingResponse, + pathname, + middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? undefined : middleware, + request, + routeData: fourOhFourRoute.route, }); + response = await renderContext.render(fourOhFourRoute.preloadedComponent); + } } // We remove the internally-used header before we send the response to the user agent. diff --git a/packages/astro/test/fixtures/i18n-routing/src/pages/404.astro b/packages/astro/test/fixtures/i18n-routing/src/pages/404.astro index fce4a30b836c4..bfde753739fef 100644 --- a/packages/astro/test/fixtures/i18n-routing/src/pages/404.astro +++ b/packages/astro/test/fixtures/i18n-routing/src/pages/404.astro @@ -7,6 +7,7 @@ const currentLocale = Astro.currentLocale;

404 - Not Found

+

Custom 404

Current Locale: {currentLocale ? currentLocale : "none"}

diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js index 8e6c672bee28b..b557038277fff 100644 --- a/packages/astro/test/i18n-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -83,6 +83,12 @@ describe('[DEV] i18n routing', () => { assert.equal((await response.text()).includes('Endurance'), true); }); + it('should render the 404.astro file', async () => { + const response = await fixture.fetch('/do-not-exist'); + assert.equal(response.status, 404); + assert.match(await response.text(), /Custom 404/); + }); + it('should return the correct locale on 404 page for non existing default locale page', async () => { const response = await fixture.fetch('/es/nonexistent-page'); assert.equal(response.status, 404); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d1124961abdc..37aef3718a873 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8787,7 +8787,6 @@ packages: libsql@0.4.5: resolution: {integrity: sha512-sorTJV6PNt94Wap27Sai5gtVLIea4Otb2LUiAUyr3p6BPOScGMKGt5F1b5X/XgkNtcsDKeX5qfeBDj+PdShclQ==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lilconfig@2.1.0: @@ -10697,9 +10696,6 @@ packages: resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==} peerDependencies: vue: '>=3.2.13' - peerDependenciesMeta: - vue: - optional: true vite@5.4.10: resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} @@ -16948,7 +16944,6 @@ snapshots: vite-svg-loader@5.1.0(vue@3.5.12(typescript@5.6.3)): dependencies: svgo: 3.3.2 - optionalDependencies: vue: 3.5.12(typescript@5.6.3) vite@5.4.10(@types/node@18.19.50)(sass@1.80.6):