Skip to content

Commit 3d6c159

Browse files
authored
fix(landing): graceful not-found handling for blog and library posts and authors (#5661)
1 parent c7151a1 commit 3d6c159

10 files changed

Lines changed: 127 additions & 28 deletions

File tree

apps/sim/app/(landing)/blog/[slug]/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Metadata } from 'next'
2+
import { notFound } from 'next/navigation'
23
import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry'
34
import { BLOG_SECTION, buildPostGraphJsonLd, buildPostMetadata } from '@/lib/blog/seo'
45
import { getBaseUrl } from '@/lib/core/utils/urls'
@@ -18,6 +19,7 @@ export async function generateMetadata({
1819
}): Promise<Metadata> {
1920
const { slug } = await params
2021
const post = await getPostBySlug(slug)
22+
if (!post) return {}
2123
return buildPostMetadata(post)
2224
}
2325

@@ -26,6 +28,7 @@ export const revalidate = 86400
2628
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
2729
const { slug } = await params
2830
const post = await getPostBySlug(slug)
31+
if (!post) notFound()
2932
const related = await getRelatedPosts(slug, 3)
3033

3134
return (

apps/sim/app/(landing)/blog/authors/[id]/page.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Metadata } from 'next'
2+
import { notFound } from 'next/navigation'
23
import { getAllPostMeta } from '@/lib/blog/registry'
34
import { BLOG_SECTION, buildAuthorGraphJsonLd, buildAuthorMetadata } from '@/lib/blog/seo'
45
import { ContentAuthorPage } from '@/app/(landing)/components'
@@ -13,22 +14,24 @@ export async function generateMetadata({
1314
const { id } = await params
1415
const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id))
1516
const author = posts[0]?.authors.find((a) => a.id === id)
17+
if (!author) return {}
1618
return buildAuthorMetadata(id, author)
1719
}
1820

1921
export default async function AuthorPage({ params }: { params: Promise<{ id: string }> }) {
2022
const { id } = await params
2123
const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id))
2224
const author = posts[0]?.authors.find((a) => a.id === id)
25+
if (!author) notFound()
2326

2427
return (
2528
<ContentAuthorPage
2629
basePath={BLOG_SECTION.basePath}
2730
sectionName={BLOG_SECTION.name}
28-
authorName={author?.name}
29-
authorAvatarUrl={author?.avatarUrl}
31+
authorName={author.name}
32+
authorAvatarUrl={author.avatarUrl}
3033
posts={posts}
31-
graphJsonLd={author ? buildAuthorGraphJsonLd(author) : undefined}
34+
graphJsonLd={buildAuthorGraphJsonLd(author)}
3235
/>
3336
)
3437
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ChipLink } from '@sim/emcn'
2+
import type { Metadata } from 'next'
3+
4+
export const metadata: Metadata = {
5+
title: 'Page Not Found',
6+
robots: { index: false, follow: true },
7+
}
8+
9+
export default function BlogAuthorNotFound() {
10+
return (
11+
<main
12+
id='main-content'
13+
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'
14+
>
15+
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
16+
Author not found
17+
</h1>
18+
<p className='text-[var(--text-muted)] text-lg'>
19+
The author you&apos;re looking for doesn&apos;t exist or has been moved.
20+
</p>
21+
<ChipLink variant='primary' href='/blog' className='mt-3'>
22+
Browse blog
23+
</ChipLink>
24+
</main>
25+
)
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ChipLink } from '@sim/emcn'
2+
import type { Metadata } from 'next'
3+
4+
export const metadata: Metadata = {
5+
title: 'Page Not Found',
6+
robots: { index: false, follow: true },
7+
}
8+
9+
export default function BlogNotFound() {
10+
return (
11+
<main
12+
id='main-content'
13+
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'
14+
>
15+
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
16+
Post not found
17+
</h1>
18+
<p className='text-[var(--text-muted)] text-lg'>
19+
The post you&apos;re looking for doesn&apos;t exist or has been moved.
20+
</p>
21+
<ChipLink variant='primary' href='/blog' className='mt-3'>
22+
Browse blog
23+
</ChipLink>
24+
</main>
25+
)
26+
}

apps/sim/app/(landing)/components/content-author-page/content-author-page.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { ChipLink } from '@sim/emcn'
21
import Image from 'next/image'
32
import Link from 'next/link'
43
import type { ContentMeta } from '@/lib/content/schema'
@@ -9,9 +8,9 @@ import { JsonLd } from '@/app/(landing)/components/json-ld'
98
interface ContentAuthorPageProps {
109
/** Route base path, e.g. `/blog` or `/library`. */
1110
basePath: string
12-
/** Section label used in the not-found fallback, e.g. "Blog" or "Library". */
11+
/** Section label used in the back link, e.g. "Blog" or "Library". */
1312
sectionName: string
14-
authorName?: string
13+
authorName: string
1514
authorAvatarUrl?: string
1615
/** Posts already filtered down to this author. */
1716
posts: ContentMeta[]
@@ -32,22 +31,6 @@ export function ContentAuthorPage({
3231
posts,
3332
graphJsonLd,
3433
}: ContentAuthorPageProps) {
35-
if (!authorName) {
36-
return (
37-
<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'>
38-
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
39-
Author not found
40-
</h1>
41-
<p className='text-[var(--text-muted)] text-lg'>
42-
The author you&apos;re looking for doesn&apos;t exist or has been moved.
43-
</p>
44-
<ChipLink variant='primary' href={basePath} className='mt-3'>
45-
Browse {sectionName}
46-
</ChipLink>
47-
</section>
48-
)
49-
}
50-
5134
return (
5235
<>
5336
<section className='bg-[var(--bg)]'>

apps/sim/app/(landing)/library/[slug]/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Metadata } from 'next'
2+
import { notFound } from 'next/navigation'
23
import { getBaseUrl } from '@/lib/core/utils/urls'
34
import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/library/registry'
45
import { buildPostGraphJsonLd, buildPostMetadata, LIBRARY_SECTION } from '@/lib/library/seo'
@@ -18,6 +19,7 @@ export async function generateMetadata({
1819
}): Promise<Metadata> {
1920
const { slug } = await params
2021
const post = await getPostBySlug(slug)
22+
if (!post) return {}
2123
return buildPostMetadata(post)
2224
}
2325

@@ -26,6 +28,7 @@ export const revalidate = 86400
2628
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
2729
const { slug } = await params
2830
const post = await getPostBySlug(slug)
31+
if (!post) notFound()
2932
const related = await getRelatedPosts(slug, 3)
3033

3134
return (

apps/sim/app/(landing)/library/authors/[id]/page.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Metadata } from 'next'
2+
import { notFound } from 'next/navigation'
23
import { getAllPostMeta } from '@/lib/library/registry'
34
import { buildAuthorGraphJsonLd, buildAuthorMetadata, LIBRARY_SECTION } from '@/lib/library/seo'
45
import { ContentAuthorPage } from '@/app/(landing)/components'
@@ -13,22 +14,24 @@ export async function generateMetadata({
1314
const { id } = await params
1415
const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id))
1516
const author = posts[0]?.authors.find((a) => a.id === id)
17+
if (!author) return {}
1618
return buildAuthorMetadata(id, author)
1719
}
1820

1921
export default async function AuthorPage({ params }: { params: Promise<{ id: string }> }) {
2022
const { id } = await params
2123
const posts = (await getAllPostMeta()).filter((p) => p.authors.some((a) => a.id === id))
2224
const author = posts[0]?.authors.find((a) => a.id === id)
25+
if (!author) notFound()
2326

2427
return (
2528
<ContentAuthorPage
2629
basePath={LIBRARY_SECTION.basePath}
2730
sectionName={LIBRARY_SECTION.name}
28-
authorName={author?.name}
29-
authorAvatarUrl={author?.avatarUrl}
31+
authorName={author.name}
32+
authorAvatarUrl={author.avatarUrl}
3033
posts={posts}
31-
graphJsonLd={author ? buildAuthorGraphJsonLd(author) : undefined}
34+
graphJsonLd={buildAuthorGraphJsonLd(author)}
3235
/>
3336
)
3437
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ChipLink } from '@sim/emcn'
2+
import type { Metadata } from 'next'
3+
4+
export const metadata: Metadata = {
5+
title: 'Page Not Found',
6+
robots: { index: false, follow: true },
7+
}
8+
9+
export default function LibraryAuthorNotFound() {
10+
return (
11+
<main
12+
id='main-content'
13+
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'
14+
>
15+
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
16+
Author not found
17+
</h1>
18+
<p className='text-[var(--text-muted)] text-lg'>
19+
The author you&apos;re looking for doesn&apos;t exist or has been moved.
20+
</p>
21+
<ChipLink variant='primary' href='/library' className='mt-3'>
22+
Browse library
23+
</ChipLink>
24+
</main>
25+
)
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ChipLink } from '@sim/emcn'
2+
import type { Metadata } from 'next'
3+
4+
export const metadata: Metadata = {
5+
title: 'Page Not Found',
6+
robots: { index: false, follow: true },
7+
}
8+
9+
export default function LibraryNotFound() {
10+
return (
11+
<main
12+
id='main-content'
13+
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'
14+
>
15+
<h1 className='text-balance text-[40px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>
16+
Post not found
17+
</h1>
18+
<p className='text-[var(--text-muted)] text-lg'>
19+
The post you&apos;re looking for doesn&apos;t exist or has been moved.
20+
</p>
21+
<ChipLink variant='primary' href='/library' className='mt-3'>
22+
Browse library
23+
</ChipLink>
24+
</main>
25+
)
26+
}

apps/sim/lib/content/registry-factory.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface ContentRegistryConfig {
2929

3030
export interface ContentRegistry {
3131
getAllPostMeta: () => Promise<ContentMeta[]>
32-
getPostBySlug: (slug: string) => Promise<ContentPost>
32+
getPostBySlug: (slug: string) => Promise<ContentPost | null>
3333
getAllTags: () => Promise<TagWithCount[]>
3434
getRelatedPosts: (slug: string, limit?: number) => Promise<ContentMeta[]>
3535
getNavPosts: () => Promise<Pick<ContentMeta, 'slug' | 'title' | 'ogImage'>[]>
@@ -225,10 +225,10 @@ export function createContentRegistry(config: ContentRegistryConfig): ContentReg
225225
}
226226
}
227227

228-
async function getPostBySlug(slug: string): Promise<ContentPost> {
228+
async function getPostBySlug(slug: string): Promise<ContentPost | null> {
229229
const meta = await scanFrontmatters()
230230
const found = meta.find((m) => m.slug === slug)
231-
if (!found) throw new Error(`Post not found: ${slug}`)
231+
if (!found) return null
232232
const mdxPath = path.join(contentDir, slug, 'index.mdx')
233233
const raw = await fs.readFile(mdxPath, 'utf-8')
234234
const { content, data } = matter(raw)

0 commit comments

Comments
 (0)