fix(landing): graceful not-found handling for blog and library posts and authors (#5661)

This commit is contained in:
Waleed
2026-07-13 18:40:12 -07:00
committed by GitHub
parent c7151a1348
commit 3d6c159248
10 changed files with 127 additions and 28 deletions
@@ -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<Metadata> {
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 (
@@ -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 (
<ContentAuthorPage
basePath={BLOG_SECTION.basePath}
sectionName={BLOG_SECTION.name}
authorName={author?.name}
authorAvatarUrl={author?.avatarUrl}
authorName={author.name}
authorAvatarUrl={author.avatarUrl}
posts={posts}
graphJsonLd={author ? buildAuthorGraphJsonLd(author) : undefined}
graphJsonLd={buildAuthorGraphJsonLd(author)}
/>
)
}
@@ -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 (
<main
id='main-content'
className='mx-auto flex min-h-[60vh] w-full max-w-[1460px] flex-col items-center justify-center gap-3 px-20 py-24 text-center max-sm:px-5 max-lg:px-8'
>
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
Author not found
</h1>
<p className='text-[var(--text-muted)] text-lg'>
The author you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<ChipLink variant='primary' href='/blog' className='mt-3'>
Browse blog
</ChipLink>
</main>
)
}
+26
View File
@@ -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 (
<main
id='main-content'
className='mx-auto flex min-h-[60vh] w-full max-w-[1460px] flex-col items-center justify-center gap-3 px-20 py-24 text-center max-sm:px-5 max-lg:px-8'
>
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
Post not found
</h1>
<p className='text-[var(--text-muted)] text-lg'>
The post you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<ChipLink variant='primary' href='/blog' className='mt-3'>
Browse blog
</ChipLink>
</main>
)
}
@@ -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 (
<section className='mx-auto flex min-h-[60vh] w-full max-w-[1460px] flex-col items-center justify-center gap-3 px-20 py-24 text-center max-sm:px-5 max-lg:px-8'>
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
Author not found
</h1>
<p className='text-[var(--text-muted)] text-lg'>
The author you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<ChipLink variant='primary' href={basePath} className='mt-3'>
Browse {sectionName}
</ChipLink>
</section>
)
}
return (
<>
<section className='bg-[var(--bg)]'>
@@ -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<Metadata> {
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 (
@@ -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 (
<ContentAuthorPage
basePath={LIBRARY_SECTION.basePath}
sectionName={LIBRARY_SECTION.name}
authorName={author?.name}
authorAvatarUrl={author?.avatarUrl}
authorName={author.name}
authorAvatarUrl={author.avatarUrl}
posts={posts}
graphJsonLd={author ? buildAuthorGraphJsonLd(author) : undefined}
graphJsonLd={buildAuthorGraphJsonLd(author)}
/>
)
}
@@ -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 (
<main
id='main-content'
className='mx-auto flex min-h-[60vh] w-full max-w-[1460px] flex-col items-center justify-center gap-3 px-20 py-24 text-center max-sm:px-5 max-lg:px-8'
>
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
Author not found
</h1>
<p className='text-[var(--text-muted)] text-lg'>
The author you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<ChipLink variant='primary' href='/library' className='mt-3'>
Browse library
</ChipLink>
</main>
)
}
@@ -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 (
<main
id='main-content'
className='mx-auto flex min-h-[60vh] w-full max-w-[1460px] flex-col items-center justify-center gap-3 px-20 py-24 text-center max-sm:px-5 max-lg:px-8'
>
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
Post not found
</h1>
<p className='text-[var(--text-muted)] text-lg'>
The post you&apos;re looking for doesn&apos;t exist or has been moved.
</p>
<ChipLink variant='primary' href='/library' className='mt-3'>
Browse library
</ChipLink>
</main>
)
}
+3 -3
View File
@@ -29,7 +29,7 @@ export interface ContentRegistryConfig {
export interface ContentRegistry {
getAllPostMeta: () => Promise<ContentMeta[]>
getPostBySlug: (slug: string) => Promise<ContentPost>
getPostBySlug: (slug: string) => Promise<ContentPost | null>
getAllTags: () => Promise<TagWithCount[]>
getRelatedPosts: (slug: string, limit?: number) => Promise<ContentMeta[]>
getNavPosts: () => Promise<Pick<ContentMeta, 'slug' | 'title' | 'ogImage'>[]>
@@ -225,10 +225,10 @@ export function createContentRegistry(config: ContentRegistryConfig): ContentReg
}
}
async function getPostBySlug(slug: string): Promise<ContentPost> {
async function getPostBySlug(slug: string): Promise<ContentPost | null> {
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)