diff --git a/web/app/[locale]/layout.tsx b/web/app/[locale]/layout.tsx index 3a69af7b1a..eb48af1013 100644 --- a/web/app/[locale]/layout.tsx +++ b/web/app/[locale]/layout.tsx @@ -1,14 +1,16 @@ import type { Metadata } from "next"; import localFont from "next/font/local"; +import { notFound } from "next/navigation"; import { Nav } from "@/components/nav"; import { Footer } from "@/components/footer"; import { UsageCounting } from "@/components/usage-counting"; import { BUILD_FACTS } from "@/lib/facts"; -import { localeDirection, locales, type Locale } from "@/lib/i18n/config"; +import { isValidLocale, localeDirection, locales } from "@/lib/i18n/config"; import { getChrome, getHome } from "@/lib/i18n/dictionaries"; import { serializeJsonLd } from "@/lib/json-ld"; import { buildPageMetadata } from "@/lib/page-meta"; import { buildSiteJsonLd } from "@/lib/site-schema"; +import { metadata as notFoundMetadata } from "./not-found"; import "../globals.css"; // Shannon Sans is the one face, as in the GPUI app (`set_theme`). The pinned @@ -57,6 +59,10 @@ export function generateStaticParams() { export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise { const { locale } = await params; + // The layout answers not-found for this; without the guard the home page's + // title, canonical, and hreflang stream into that 404 (`/wp-login.php`). + // Throwing here instead leaves the page with no title at all. + if (!isValidLocale(locale)) return notFoundMetadata; const home = getHome(locale); return buildPageMetadata({ path: "/", @@ -74,6 +80,10 @@ export default async function LocaleLayout({ params: Promise<{ locale: string }>; }) { const { locale } = await params; + // Middleware leaves dotted paths alone, so `/wp-login.php` reaches this + // segment with that "locale". Without this it rendered the home page with + // HTTP 200 and ``. + if (!isValidLocale(locale)) notFound(); const chrome = getChrome(locale); // RTL locales (e.g. ar) set the document direction from the canonical // registry so the browser handles bidirectional layout from the root. @@ -106,9 +116,9 @@ export default async function LocaleLayout({ {chrome.skipToContent} -