From 3c29e20160084356426f7f93c9134a701e880ff1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 13 Jul 2026 18:30:22 -0700 Subject: [PATCH] fix(landing): graceful not-found handling for blog and library posts and authors --- apps/sim/app/(landing)/blog/[slug]/page.tsx | 3 +++ .../app/(landing)/blog/authors/[id]/page.tsx | 9 ++++--- .../app/(landing)/blog/authors/not-found.tsx | 26 +++++++++++++++++++ apps/sim/app/(landing)/blog/not-found.tsx | 26 +++++++++++++++++++ .../content-author-page.tsx | 21 ++------------- .../sim/app/(landing)/library/[slug]/page.tsx | 3 +++ .../(landing)/library/authors/[id]/page.tsx | 9 ++++--- .../(landing)/library/authors/not-found.tsx | 26 +++++++++++++++++++ apps/sim/app/(landing)/library/not-found.tsx | 26 +++++++++++++++++++ apps/sim/lib/content/registry-factory.ts | 6 ++--- 10 files changed, 127 insertions(+), 28 deletions(-) create mode 100644 apps/sim/app/(landing)/blog/authors/not-found.tsx create mode 100644 apps/sim/app/(landing)/blog/not-found.tsx create mode 100644 apps/sim/app/(landing)/library/authors/not-found.tsx create mode 100644 apps/sim/app/(landing)/library/not-found.tsx diff --git a/apps/sim/app/(landing)/blog/[slug]/page.tsx b/apps/sim/app/(landing)/blog/[slug]/page.tsx index 2e8b50b2dd5..472f4aa75a4 100644 --- a/apps/sim/app/(landing)/blog/[slug]/page.tsx +++ b/apps/sim/app/(landing)/blog/[slug]/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next' +import { notFound } from 'next/navigation' import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry' import { BLOG_SECTION, buildPostGraphJsonLd, buildPostMetadata } from '@/lib/blog/seo' import { getBaseUrl } from '@/lib/core/utils/urls' @@ -18,6 +19,7 @@ export async function generateMetadata({ }): Promise { const { slug } = await params const post = await getPostBySlug(slug) + if (!post) return {} return buildPostMetadata(post) } @@ -26,6 +28,7 @@ export const revalidate = 86400 export default async function Page({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await getPostBySlug(slug) + if (!post) notFound() const related = await getRelatedPosts(slug, 3) return ( diff --git a/apps/sim/app/(landing)/blog/authors/[id]/page.tsx b/apps/sim/app/(landing)/blog/authors/[id]/page.tsx index 6d2869eeca5..77aea381d78 100644 --- a/apps/sim/app/(landing)/blog/authors/[id]/page.tsx +++ b/apps/sim/app/(landing)/blog/authors/[id]/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next' +import { notFound } from 'next/navigation' import { getAllPostMeta } from '@/lib/blog/registry' import { BLOG_SECTION, buildAuthorGraphJsonLd, buildAuthorMetadata } from '@/lib/blog/seo' import { ContentAuthorPage } from '@/app/(landing)/components' @@ -13,6 +14,7 @@ export async function generateMetadata({ const { id } = await params const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) const author = posts[0]?.authors.find((a) => a.id === id) + if (!author) return {} return buildAuthorMetadata(id, author) } @@ -20,15 +22,16 @@ export default async function AuthorPage({ params }: { params: Promise<{ id: str const { id } = await params const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) const author = posts[0]?.authors.find((a) => a.id === id) + if (!author) notFound() return ( ) } diff --git a/apps/sim/app/(landing)/blog/authors/not-found.tsx b/apps/sim/app/(landing)/blog/authors/not-found.tsx new file mode 100644 index 00000000000..77b14ef9f97 --- /dev/null +++ b/apps/sim/app/(landing)/blog/authors/not-found.tsx @@ -0,0 +1,26 @@ +import { ChipLink } from '@sim/emcn' +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Page Not Found', + robots: { index: false, follow: true }, +} + +export default function BlogAuthorNotFound() { + return ( +
+

+ Author not found +

+

+ The author you're looking for doesn't exist or has been moved. +

+ + Browse blog + +
+ ) +} diff --git a/apps/sim/app/(landing)/blog/not-found.tsx b/apps/sim/app/(landing)/blog/not-found.tsx new file mode 100644 index 00000000000..70ea50b3f62 --- /dev/null +++ b/apps/sim/app/(landing)/blog/not-found.tsx @@ -0,0 +1,26 @@ +import { ChipLink } from '@sim/emcn' +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Page Not Found', + robots: { index: false, follow: true }, +} + +export default function BlogNotFound() { + return ( +
+

+ Post not found +

+

+ The post you're looking for doesn't exist or has been moved. +

+ + Browse blog + +
+ ) +} diff --git a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx index 43225eb95ca..db9b21b2a99 100644 --- a/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx +++ b/apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx @@ -1,4 +1,3 @@ -import { ChipLink } from '@sim/emcn' import Image from 'next/image' import Link from 'next/link' import type { ContentMeta } from '@/lib/content/schema' @@ -9,9 +8,9 @@ import { JsonLd } from '@/app/(landing)/components/json-ld' interface ContentAuthorPageProps { /** Route base path, e.g. `/blog` or `/library`. */ basePath: string - /** Section label used in the not-found fallback, e.g. "Blog" or "Library". */ + /** Section label used in the back link, e.g. "Blog" or "Library". */ sectionName: string - authorName?: string + authorName: string authorAvatarUrl?: string /** Posts already filtered down to this author. */ posts: ContentMeta[] @@ -32,22 +31,6 @@ export function ContentAuthorPage({ posts, graphJsonLd, }: ContentAuthorPageProps) { - if (!authorName) { - return ( -
-

- Author not found -

-

- The author you're looking for doesn't exist or has been moved. -

- - Browse {sectionName} - -
- ) - } - return ( <>
diff --git a/apps/sim/app/(landing)/library/[slug]/page.tsx b/apps/sim/app/(landing)/library/[slug]/page.tsx index bd8de683cc0..e6901ee428c 100644 --- a/apps/sim/app/(landing)/library/[slug]/page.tsx +++ b/apps/sim/app/(landing)/library/[slug]/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next' +import { notFound } from 'next/navigation' import { getBaseUrl } from '@/lib/core/utils/urls' import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/library/registry' import { buildPostGraphJsonLd, buildPostMetadata, LIBRARY_SECTION } from '@/lib/library/seo' @@ -18,6 +19,7 @@ export async function generateMetadata({ }): Promise { const { slug } = await params const post = await getPostBySlug(slug) + if (!post) return {} return buildPostMetadata(post) } @@ -26,6 +28,7 @@ export const revalidate = 86400 export default async function Page({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await getPostBySlug(slug) + if (!post) notFound() const related = await getRelatedPosts(slug, 3) return ( diff --git a/apps/sim/app/(landing)/library/authors/[id]/page.tsx b/apps/sim/app/(landing)/library/authors/[id]/page.tsx index d417bf8f3a2..7af8169eb9d 100644 --- a/apps/sim/app/(landing)/library/authors/[id]/page.tsx +++ b/apps/sim/app/(landing)/library/authors/[id]/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next' +import { notFound } from 'next/navigation' import { getAllPostMeta } from '@/lib/library/registry' import { buildAuthorGraphJsonLd, buildAuthorMetadata, LIBRARY_SECTION } from '@/lib/library/seo' import { ContentAuthorPage } from '@/app/(landing)/components' @@ -13,6 +14,7 @@ export async function generateMetadata({ const { id } = await params const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) const author = posts[0]?.authors.find((a) => a.id === id) + if (!author) return {} return buildAuthorMetadata(id, author) } @@ -20,15 +22,16 @@ export default async function AuthorPage({ params }: { params: Promise<{ id: str const { id } = await params const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id)) const author = posts[0]?.authors.find((a) => a.id === id) + if (!author) notFound() return ( ) } diff --git a/apps/sim/app/(landing)/library/authors/not-found.tsx b/apps/sim/app/(landing)/library/authors/not-found.tsx new file mode 100644 index 00000000000..9207370e8c1 --- /dev/null +++ b/apps/sim/app/(landing)/library/authors/not-found.tsx @@ -0,0 +1,26 @@ +import { ChipLink } from '@sim/emcn' +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Page Not Found', + robots: { index: false, follow: true }, +} + +export default function LibraryAuthorNotFound() { + return ( +
+

+ Author not found +

+

+ The author you're looking for doesn't exist or has been moved. +

+ + Browse library + +
+ ) +} diff --git a/apps/sim/app/(landing)/library/not-found.tsx b/apps/sim/app/(landing)/library/not-found.tsx new file mode 100644 index 00000000000..9bca5460c33 --- /dev/null +++ b/apps/sim/app/(landing)/library/not-found.tsx @@ -0,0 +1,26 @@ +import { ChipLink } from '@sim/emcn' +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Page Not Found', + robots: { index: false, follow: true }, +} + +export default function LibraryNotFound() { + return ( +
+

+ Post not found +

+

+ The post you're looking for doesn't exist or has been moved. +

+ + Browse library + +
+ ) +} diff --git a/apps/sim/lib/content/registry-factory.ts b/apps/sim/lib/content/registry-factory.ts index 48038beeeef..ca06e5e6b13 100644 --- a/apps/sim/lib/content/registry-factory.ts +++ b/apps/sim/lib/content/registry-factory.ts @@ -29,7 +29,7 @@ export interface ContentRegistryConfig { export interface ContentRegistry { getAllPostMeta: () => Promise - getPostBySlug: (slug: string) => Promise + getPostBySlug: (slug: string) => Promise getAllTags: () => Promise getRelatedPosts: (slug: string, limit?: number) => Promise getNavPosts: () => Promise[]> @@ -225,10 +225,10 @@ export function createContentRegistry(config: ContentRegistryConfig): ContentReg } } - async function getPostBySlug(slug: string): Promise { + async function getPostBySlug(slug: string): Promise { const meta = await scanFrontmatters() const found = meta.find((m) => m.slug === slug) - if (!found) throw new Error(`Post not found: ${slug}`) + if (!found) return null const mdxPath = path.join(contentDir, slug, 'index.mdx') const raw = await fs.readFile(mdxPath, 'utf-8') const { content, data } = matter(raw)