From 985f393a9b19bc8f4cb2f5ee74a534f42596eefc Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Mon, 13 Jul 2026 21:02:20 +0000 Subject: [PATCH] [recovery] Return 404 for non-existent story slugs The stories [slug] route read the markdown file directly in both the page body and generateMetadata without guarding against missing slugs. A crawler probe to /stories/-6/ threw an unhandled ENOENT during generateMetadata, surfacing as a 500 in the Netlify server handler. Mirror the shared [...slug] route: validate the slug against getStorySlugs() and call notFound() in the page, and wrap getMdMetadata in a try/catch that returns basic not-found metadata. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/[locale]/stories/[slug]/page.tsx | 30 +++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/app/[locale]/stories/[slug]/page.tsx b/app/[locale]/stories/[slug]/page.tsx index b129c43ec2b..014576e91d9 100644 --- a/app/[locale]/stories/[slug]/page.tsx +++ b/app/[locale]/stories/[slug]/page.tsx @@ -1,6 +1,11 @@ import { pick } from "lodash" import type { Metadata } from "next" -import { getMessages, setRequestLocale } from "next-intl/server" +import { notFound } from "next/navigation" +import { + getMessages, + getTranslations, + setRequestLocale, +} from "next-intl/server" import I18nProvider from "@/components/I18nProvider" import mdComponents from "@/components/MdComponents" @@ -20,6 +25,11 @@ const StoryPage = async (props: { }) => { const { locale, slug } = await props.params + // Guard against non-existent story slugs (e.g. crawler probes) so they 404 + // instead of throwing an ENOENT when the markdown file is read. + const slugs = await getStorySlugs() + if (!slugs.includes(slug)) notFound() + setRequestLocale(locale) // Story content lives at public/content/stories/{slug}/index.md, so it slots @@ -89,10 +99,20 @@ export async function generateMetadata(props: { }): Promise { const { locale, slug } = await props.params - return await getMdMetadata({ - locale, - slug: ["stories", slug], - }) + try { + return await getMdMetadata({ + locale, + slug: ["stories", slug], + }) + } catch { + // Return basic metadata for invalid paths (mirrors the shared [...slug] route) + const t = await getTranslations("common") + + return { + title: t("page-not-found"), + description: t("page-not-found-description"), + } + } } export default StoryPage