Skip to content

fix: localized error route status handling#17087

Merged
ematipico merged 9 commits into
mainfrom
fix/i18n-localized-404
Jun 25, 2026
Merged

fix: localized error route status handling#17087
ematipico merged 9 commits into
mainfrom
fix/i18n-localized-404

Conversation

@jp-knj

@jp-knj jp-knj commented Jun 16, 2026

Copy link
Copy Markdown
Member

Close: #12175
Closes AST-188

Changes

When it defines a locale-specific page like /pt/404.astro,

  • Astro resolves it for missing /pt/* routes, returns the correct 404 status for /pt/404,
  • keeps normal routes like /docs/404 as normal pages.

Testing

Added focused unit tests for the new localized error route helpers and app routing behavior, then reviewed the generated test cases to make sure they match the existing test style and cover the important cases.

The tests cover:

  • selecting localized /404 and /500 routes when available
  • falling back to the global error route when no localized route exists
  • returning 404 for /pt/404
  • keeping normal localized pages like /pt/about as 200
  • keeping normal routes like /docs/404 as normal pages
  • fetching prerendered localized 404 pages from the correct output path

Docs

This may need docs because this PR fixes support for localized custom error pages

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fe37920

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 392 packages
Name Type
astro Patch
@e2e/astro-linked-lib Patch
@e2e/actions-blog Patch
@e2e/actions-react-19 Patch
@e2e/astro-component Patch
@e2e/astro-envs Patch
@e2e/astro-island-hydration-error Patch
@test/astro-cloudflare-node-prerender-mdx Patch
@test/astro-cloudflare Patch
@e2e/content-collections Patch
@e2e/csp-server-islands Patch
@e2e/css Patch
@test/custom-client-directives Patch
@e2e/dev-toolbar Patch
@e2e/error-cyclic Patch
@e2e/error-sass Patch
@e2e/errors Patch
@e2e/hydration-race Patch
@e2e/i18n Patch
@test/nested-style-bug-e22e Patch
@e2e/preact-compat-component Patch
@e2e/preact-component Patch
@e2e/preact-lazy-component Patch
@e2e/prefetch Patch
@e2e/react-component Patch
@e2e/server-islands-key Patch
@e2e/server-islands Patch
@e2e/solid-circular Patch
@e2e/solid-component Patch
@e2e/solid-recurse Patch
@e2e/svelte-component Patch
@e2e/e2e-tailwindcss Patch
@e2e/ts-resolution Patch
@e2e/view-transitions Patch
@e2e/vite-virtual-modules Patch
@e2e/vue-component Patch
@performance/md Patch
@performance/mdoc Patch
@performance/mdx Patch
@test/0-css Patch
fake-astro-library Patch
@test/actions Patch
@test/alias-css-url Patch
@test/alias-path-alias-style Patch
@test/ts-paths-no-baseurl Patch
@test/aliases-tsconfig Patch
@test/aliases Patch
@test/api-routes Patch
@test/asset-query-params-chunks Patch
@test/asset-url-base Patch
@test/astro-pages Patch
@test/astro-assets-prefix Patch
@test/astro-assets Patch
@test/astro-basic Patch
@test/astro-check-errors Patch
@test/astro-check-no-errors Patch
@test/astro-check-watch Patch
@test/astro-children Patch
@test/astro-client-only Patch
@test/astro-component-bundling Patch
@test/astro-component-code Patch
@test/astro-css-bundling Patch
@test/astro-dev-headers Patch
@test/astro-dev-http2 Patch
@test/astro-doctype Patch
@test/astro-dynamic Patch
@test/astro-env-content-collections Patch
@test/astro-env-required-public Patch
@test/astro-env-server-fail Patch
@test/astro-env-server-secret Patch
@test/astro-env Patch
@test/astro-envs Patch
@test/astro-expr Patch
@test/astro-get-static-paths Patch
@test/astro-head Patch
@test/astro-manifest-client-script Patch
@test/astro-manifest-invalid Patch
@test/astro-manifest Patch
@test/astro-markdown-frontmatter-injection Patch
@test/astro-markdown-plugins Patch
@test/astro-markdown-remarkRehype Patch
@test/astro-markdown-skiki-default-color Patch
@test/astro-markdown-skiki-langs Patch
@test/astro-markdown-skiki-themes-custom Patch
@test/astro-markdown-skiki-themes-integrated Patch
@test/astro-markdown-skiki-wrap-false Patch
@test/astro-markdown-skiki-wrap-null Patch
@test/astro-markdown-skiki-wrap-true Patch
@test/astro-markdown-url Patch
@test/astro-markdown Patch
@test/astro-mode Patch
@test/astro-page-directory-url Patch
@test/astro-partial-html Patch
@test/astro-preview-allowed-hosts Patch
@test/astro-preview-headers Patch
@test/astro-public Patch
@test/astro-script-template-dedup Patch
@test/astro-scripts Patch
@test/astro-slots-nested Patch
@test/concurrency Patch
@test/build-readonly-file Patch
@test/cache-memory-query-include Patch
@test/cache-memory-query Patch
@test/client-address-node Patch
@test/client-only-css-chunk-leak Patch
@test/code-component Patch
@test/component-library Patch
@test/config-vite-css-target Patch
@test/config-vite Patch
@test/react-container Patch
@test/content-with-spaces-in-folder-name Patch
@test/content-collection-picture-render Patch
@example/content-collection-references Patch
@test/content-collection-tla-svg Patch
@test/content-collections-base Patch
@test/content-collections-empty-dir Patch
@test/content-collections-empty-md-file Patch
@test/content-collections-image-hmr Patch
@test/content-collections-mutation Patch
@test/content-collections-number-id Patch
@test/content-collections-type-inference Patch
@test/content-collections-with-config-mjs Patch
@test/content-collections Patch
@test/content-frontmatter Patch
@test/content-intellisense Patch
@test/content-layer-loader-schema-function Patch
@test/content-layer-remark-plugins Patch
@test/content-layer Patch
@test/content-ssr-integration Patch
@test/content-static-paths-integration Patch
@test/content Patch
@test/core-image-data-url Patch
@test/core-image-deletion-ssr Patch
@test/core-image-deletion Patch
@test/core-image-errors Patch
@test/core-image-fs-config Patch
@test/core-image-remark-infersize Patch
@test/core-image-layout Patch
@test/core-image-picture-emit-file Patch
@test/core-image-remark-imgattr Patch
@test/core-image-ssg Patch
@test/core-image-ssr Patch
@test/core-image-svg-in-client Patch
@test/core-image-svg Patch
@test/core-image-unconventional-settings Patch
@test/core-image Patch
@test/csp-adapter Patch
@test/csp-fonts Patch
@test/csp Patch
@test/css-assets Patch
@test/css-dangling-references Patch
@test/css-deduplication Patch
@test/css-double-bundle Patch
@test/css-dynamic-import-dev Patch
@test/css-import-as-inline Patch
@test/css-inline-stylesheets Patch
@test/css-no-code-split Patch
@test/css-path-case Patch
@test/css-pure-chunk-query-params Patch
@test/custom-404-injected-from-dep Patch
@test/custom-404-pkg Patch
custom-fetch-error-pages Patch
@test/custom-renderer Patch
@test/data-collections-schema Patch
@test/data-collections Patch
@test/debug-component Patch
@test/dev-container Patch
@test/dev-render Patch
@test/dev-request-url Patch
@test/dynamic-endpoint-collision Patch
@test/dynamic-route-build-file Patch
@test/endpoint-routing Patch
@test/error-bad-js Patch
@test/error-build-location Patch
@test/error-non-error Patch
@test/extension-matching Patch
@test/fetch Patch
@test/fonts Patch
@test/astro-fontsource-package Patch
@test/get-static-paths-pages Patch
@test/glob-pages-css Patch
@test/head-propagation-prerender-env Patch
@test/hmr-markdown Patch
@test/hmr-new-page Patch
@test/hmr-slots-render Patch
@test/hoisted-imports Patch
@test/html-component Patch
@test/html-escape Patch
@test/html-page Patch
@test/html-slots Patch
@test/hydration-race Patch
@test/i18n-client-import Patch
@test/i18n-css-leak-basic Patch
@test/import-ts-with-js Patch
@test/impostor-md-file Patch
@test/integration-add-page-extension Patch
@test/integration-server-setup Patch
@test/jsx-queue-rendering Patch
@test/large-array-solid Patch
@test/legacy-collections-backwards-compat Patch
@test/lightningcss-scoped-nesting Patch
@test/live-loaders Patch
@test/markdown Patch
@test/middleware-dev Patch
@test/middleware-full-ssr Patch
@test/middleware-no-user-middlewaqre Patch
@test/middleware-tailwind Patch
@test/minification-html-default Patch
@test/minification-html-jsx Patch
@test/minification-html Patch
@test/non-ascii-path Patch
@test/non-html-pages Patch
@test/page-format Patch
@test/page-level-styles Patch
@test/parallel-components Patch
@test/partials-css-boundary Patch
@test/partials Patch
@test/passthrough-image-service Patch
@test/postcss Patch
@test/preact-compat-component Patch
@test/preact-component Patch
@test/remote-css Patch
@test/request-signal Patch
@test/reuse-injected-entrypoint Patch
@test/root-srcdir-css Patch
@test/scoped-style-strategy Patch
@test/server-entry-fake-adapter Patch
@test/server-entry Patch
@test/server-islands-hybrid Patch
@test/server-islands-ssr Patch
@test/sessions Patch
@test/slots-preact Patch
@test/slots-react Patch
@test/slots-solid Patch
@test/slots-svelte Patch
@test/slots-vue Patch
@test/solid-component Patch
@test/sourcemap Patch
@test/space-in-folder-name Patch
@test/special-chars-in-component-imports Patch
@test/ssr-assets Patch
@test/ssr-dynamic Patch
@test/ssr-partytown Patch
@test/ssr-prerender-get-static-paths Patch
@test/ssr-prerender Patch
@test/ssr-preview Patch
@test/ssr-renderers-static-vue Patch
@test/ssr-request Patch
@test/ssr-hoisted-script Patch
@test/ssr-scripts Patch
@test/static-build-code-component Patch
@test/static-build-dir Patch
@test/static-build-frameworks Patch
@test/static-build-page-url-format Patch
@test/static-build-ssr Patch
@test/static-build Patch
@test/static-redirect Patch
@test/svelte-component Patch
@test/svg-deduplication Patch
@test/tailwindcss Patch
@e2e/third-party-astro Patch
@test/url-import-suffix Patch
@test/view-transitions Patch
@test/virtual-astro-file Patch
@test/vitest Patch
@test/vue-component Patch
@test/vue-with-multi-renderer Patch
@test/alpinejs-basics Patch
@test/alpinejs-directive Patch
@test/alpinejs-plugin-script-import Patch
@test/astro-cloudflare-allowed-hosts Patch
@test/astro-cloudflare-astro-dev-platform Patch
@test/astro-cloudflare-astro-env Patch
@test/astro-cloudflare-binding-image-service Patch
@test/astro-cloudflare-cache-provider-wait-until Patch
@test/astro-cloudflare-cache-provider Patch
@test/astro-cloudflare-client-address Patch
@test/astro-cloudflare-compile-image-service Patch
@test/astro-cloudflare-custom-entryfile Patch
@test/astro-cloudflare-dev-image-endpoint Patch
@test/astro-cloudflare-external-image-service Patch
@test/astro-cloudflare-external-redirects Patch
@test/astro-cloudflare-internal-redirects Patch
@test/astro-cloudflare-no-output Patch
@test/astro-cloudflare-prerender-node-env Patch
@test/astro-cloudflare-prerender-queue-consumers Patch
@test/astro-cloudflare-prerender-styles Patch
@test/astro-cloudflare-prerenderer-errors Patch
@test/routing-priority-cloudflare Patch
@test/cf-server-entry Patch
@test/astro-cloudflare-server-island-prerender-framework Patch
@test/astro-cloudflare-sql-import Patch
@test/cf-ssr-deps Patch
@test/astro-cloudflare-static Patch
@test/astro-cloudflare-svelte-rune-deps Patch
@test/astro-cloudflare-top-level-return Patch
@test/cf-user-optimize-deps Patch
@test/astro-cloudflare-vite-plugin Patch
@test/astro-cloudflare-with-base Patch
@test/astro-cloudflare-with-react Patch
@test/astro-cloudflare-with-solid-js Patch
@test/astro-cloudflare-with-svelte Patch
@test/astro-cloudflare-with-vue Patch
@test/astro-cloudflare-wrangler-preview-platform Patch
@test/markdoc-content-collections Patch
@test/content-layer-markdoc Patch
@test/headings-custom Patch
@test/headings Patch
@test/image-assets-custom Patch
@test/image-assets Patch
@test/markdoc-propagated-assets Patch
@test/markdoc-render-with-space Patch
@test/markdoc-render-html Patch
@test/markdoc-render-null Patch
@test/markdoc-render-partials Patch
@test/markdoc-render-simple Patch
@test/markdoc-render-table-attrs Patch
@test/markdoc-render-typographer Patch
@test/markdoc-render-with-components Patch
@test/markdoc-render-with-config Patch
@test/markdoc-render-with-extends-components Patch
@test/markdoc-render-with-indented-components Patch
@test/markdoc-render-with-transform Patch
@test/markdoc-variables Patch
@test/content-layer-rendering Patch
@test/mdx-css-head-mdx Patch
@test/image-remark-imgattr Patch
@test/mdx-astro-container-escape Patch
@test/mdx-frontmatter-injection Patch
@test/netlify-skew-protection Patch
@test/netlify-hosted-astro-project Patch
@test/nodejs-api-route Patch
@test/nodejs-badurls Patch
@test/nodejs-encoded Patch
@test/nodejs-errors Patch
@test/nodejs-headers Patch
@test/nodejs-image Patch
@test/locals Patch
@test/node-middleware Patch
@test/nodejs-prerender-404-500 Patch
@test/nodejs-prerender Patch
@test/nodejs-prerendered-error-page-fetch Patch
@test/nodejs-preview-headers Patch
@test/redirects Patch
@test/node-sessions Patch
@test/ssr-assets-middleware Patch
@test/node-static-headers Patch
@test/node-trailingslash Patch
@test/url Patch
@test/well-known-locations Patch
@test/react-component Patch
@test/sitemap-chunks Patch
@test/sitemap-dynamic Patch
@test/sitemap-i18n-fallback Patch
@test/sitemap-ssr Patch
@test/sitemap-static Patch
@test/sitemap-trailing-slash Patch
async-rendering Patch
conditional-rendering Patch
@test/empty-class Patch
svelte-prop-types Patch
@test/astro-vercel-basic Patch
@test/astro-vercel-image Patch
@test/astro-vercel-integration-assets Patch
@test/vercel-isr Patch
@test/vercel-max-duration Patch
@test/vercel-edge-middleware-with-edge-file Patch
@test/vercel-edge-middleware-without-edge-file Patch
@test/astro-vercel-no-output Patch
@test/astro-vercel-prerendered-error-pages Patch
@test/astro-vercel-redirects-serverless Patch
@test/astro-vercel-redirects Patch
@test/vercel-server-islands Patch
@test/astro-vercel-serverless-prerender Patch
@test/astro-vercel-serverless-with-dynamic-routes Patch
@test/astro-vercel-static-assets Patch
@test/vercel-static-headers Patch
@test/astro-vercel-static Patch
@test/vercel-streaming Patch
@test/astro-vercel-with-web-analytics-enabled-output-as-static Patch
vercel-hosted-astro-project Patch
@test/vue-app-entrypoint-async Patch
@test/vue-app-entrypoint-css Patch
@test/vue-app-entrypoint-no-export-default Patch
@test/vue-app-entrypoint-relative Patch
@test/vue-app-entrypoint-src-absolute Patch
@test/vue-app-entrypoint Patch
@test/vue-basics Patch
vue-prop-types Patch
astro-benchmark Patch
@benchmark/adapter Patch
@benchmark/timer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the pkg: astro Related to the core `astro` package (scope) label Jun 16, 2026
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

e18e dependency analysis

No dependency warnings found.

@jp-knj jp-knj force-pushed the fix/i18n-localized-404 branch 2 times, most recently from 5295030 to e797b8e Compare June 16, 2026 04:17
@codspeed-hq

codspeed-hq Bot commented Jun 16, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 18 untouched benchmarks


Comparing fix/i18n-localized-404 (fe37920) with main (8f4a3db)

Open in CodSpeed

@github-actions github-actions Bot added the 🚨 action Modifies GitHub Actions label Jun 16, 2026
@jp-knj jp-knj force-pushed the fix/i18n-localized-404 branch 2 times, most recently from eaf7cea to 17bb65a Compare June 16, 2026 09:37
Comment on lines +7 to +25
export function getOutputFilename(
buildFormat: NonNullable<AstroConfig['build']>['format'],
name: string,
routeData: RouteData,
) {
if (routeData.type === 'endpoint') {
return name;
}
if (name === '/' || name === '') {
return name === '' ? 'index.html' : '/index.html';
}
if (buildFormat === 'file' || STATUS_CODE_PAGES.has(name)) {
return `${removeTrailingForwardSlash(name || 'index')}.html`;
}
if (buildFormat === 'preserve' && !routeData.isIndex) {
return `${removeTrailingForwardSlash(name || 'index')}.html`;
}
return `${removeTrailingForwardSlash(name)}/index.html`;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Extracted getOutputFilename so both build code and the error handler can use the same output path logic.

This lets localized prerendered error pages use the correct URL, without making the error
handler import Node-only modules(node:fs) from core/util.ts.

@github-actions github-actions Bot added the semver: minor Change triggers a `minor` release label Jun 16, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This PR is blocked because it contains a minor changeset. A reviewer will merge this at the next release if approved.

@jp-knj jp-knj force-pushed the fix/i18n-localized-404 branch from 4e9de86 to 7810fba Compare June 16, 2026 13:50
Comment on lines +1 to +42
import type { Locales } from '../types/public/config.js';

// Checks if the pathname has any locale
export function pathHasLocale(path: string, locales: Locales): boolean {
// pages that use a locale param ([locale].astro or [locale]/index.astro)
// and getStaticPaths make [locale].html the pathname during SSG
// which will not match a configured locale without removing .html
// as we do in normalizeThePath
const segments = path.split('/').map(normalizeThePath);
for (const segment of segments) {
for (const locale of locales) {
if (typeof locale === 'string') {
if (normalizeTheLocale(segment) === normalizeTheLocale(locale)) {
return true;
}
} else if (segment === locale.path) {
return true;
}
}
}

return false;
}

/**
*
* Given a locale, this function:
* - replaces the `_` with a `-`;
* - transforms all letters to be lowercase;
*/
export function normalizeTheLocale(locale: string): string {
return locale.replaceAll('_', '-').toLowerCase();
}

/**
*
* Given a path or path segment, this function:
* - removes the `.html` extension if it exists
*/
export function normalizeThePath(path: string): string {
return path.endsWith('.html') ? path.slice(0, -5) : path;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

a pure leaf (locale path matching) shared by i18n/index.ts and error-routes.ts, with no dependency back on the i18n barrel.

}
}
return `/${status}${suffix}`;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The always-loaded DefaultErrorHandler imports the error-route helpers from error-routes.ts, which only pulls path.ts, not the i18n barrel (createI18nMiddleware, etc.)

@jp-knj jp-knj marked this pull request as ready for review June 16, 2026 14:59
@github-actions github-actions Bot added the pkg: integration Related to any renderer integration (scope) label Jun 19, 2026
@ematipico ematipico force-pushed the fix/i18n-localized-404 branch from 6b03e8a to fe37920 Compare June 25, 2026 10:31
@ematipico ematipico removed the semver: minor Change triggers a `minor` release label Jun 25, 2026
@ematipico ematipico merged commit fb0ab02 into main Jun 25, 2026
33 checks passed
@ematipico ematipico deleted the fix/i18n-localized-404 branch June 25, 2026 10:48
@astrobot-houston astrobot-houston mentioned this pull request Jun 25, 2026
dadezzz pushed a commit to dadezzz/university_notes that referenced this pull request Jun 30, 2026
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [astro](https://astro.build) ([source](https://github.com/withastro/astro/tree/HEAD/packages/astro)) | [`7.0.2` → `7.0.3`](https://renovatebot.com/diffs/npm/astro/7.0.2/7.0.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/astro/7.0.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/astro/7.0.2/7.0.3?slim=true) |

---

### Release Notes

<details>
<summary>withastro/astro (astro)</summary>

### [`v7.0.3`](https://github.com/withastro/astro/blob/HEAD/packages/astro/CHANGELOG.md#703)

[Compare Source](https://github.com/withastro/astro/compare/astro@7.0.2...astro@7.0.3)

##### Patch Changes

- [#&#8203;17189](withastro/astro#17189) [`24d2c9e`](withastro/astro@24d2c9e) Thanks [@&#8203;astrobot-houston](https://github.com/astrobot-houston)! - Fixes a bug where an error thrown inside one route's `getStaticPaths()` would prevent other valid routes from being matched in dev mode

- [#&#8203;16932](withastro/astro#16932) [`8f4a3db`](withastro/astro@8f4a3db) Thanks [@&#8203;fkatsuhiro](https://github.com/fkatsuhiro)! - Fixes HMR for action files during development. Editing files in `src/actions/` now takes effect on the next request without requiring a dev server restart.

- [#&#8203;17087](withastro/astro#17087) [`fb0ab02`](withastro/astro@fb0ab02) Thanks [@&#8203;jp-knj](https://github.com/jp-knj)! - Fixes localized custom error pages in i18n projects so routes like `/pt/404` are used for missing localized pages and return the correct status code

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMzQuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI0Ni4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🚨 action Modifies GitHub Actions pkg: astro Related to the core `astro` package (scope) pkg: integration Related to any renderer integration (scope)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom 404 pages in localized sites

2 participants