Skip to content

Commit 155e4ac

Browse files
committed
fix: return 404 (not 500) for catch-all paths with no content file
Paths that match a section catch-all but have no backing .md file (e.g. /learn/state, a sidebar header) were 500ing. The fs read threw inside readMarkdownPage's 'use cache' scope, which surfaces as a render error instead of falling through to notFound(). readMarkdownPage now returns PageData | null for a missing file instead of throwing; callers decide notFound(). Removes the now-redundant safeReadPage try/catch wrapper.
1 parent 79d988f commit 155e4ac

3 files changed

Lines changed: 25 additions & 22 deletions

File tree

src/app/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import type {Metadata} from 'next';
9+
import {notFound} from 'next/navigation';
910
import sidebarHome from '../sidebarHome.json';
1011
import type {RouteItem} from 'components/Layout/getRouteMeta';
1112
import {readMarkdownPage} from 'lib/readMarkdownPage';
@@ -14,11 +15,13 @@ import {DocsPage} from './DocsPage';
1415

1516
export async function generateMetadata(): Promise<Metadata> {
1617
const data = await readMarkdownPage([]);
18+
if (!data) return {};
1719
return buildPageMetadata({data, pathname: '/', section: 'home'});
1820
}
1921

2022
export default async function HomePage() {
2123
const data = await readMarkdownPage([]);
24+
if (!data) notFound();
2225
return (
2326
<DocsPage
2427
data={data}

src/app/renderSectionPage.tsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export async function renderSectionPage({
2525
section,
2626
routeTree,
2727
}: RenderArgs) {
28-
const data = await safeReadPage(segments);
28+
const data = await readMarkdownPage(segments);
2929
if (!data) notFound();
3030
const pathname = '/' + segments.join('/');
3131
return (
@@ -47,21 +47,8 @@ export async function sectionPageMetadata({
4747
section: PageSection;
4848
routeTree?: RouteItem;
4949
}): Promise<Metadata> {
50-
const data = await safeReadPage(segments);
50+
const data = await readMarkdownPage(segments);
5151
if (!data) return {};
5252
const pathname = '/' + segments.join('/');
5353
return buildPageMetadata({data, pathname, section, routeTree});
5454
}
55-
56-
async function safeReadPage(segments: string[]) {
57-
try {
58-
return await readMarkdownPage(segments);
59-
} catch (err) {
60-
console.error(
61-
'[renderSectionPage] readMarkdownPage failed for',
62-
segments,
63-
err
64-
);
65-
return null;
66-
}
67-
}

src/lib/readMarkdownPage.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,35 @@ const ROOT = path.join(process.cwd(), 'src/content');
2222

2323
/**
2424
* Read and compile an MDX page from src/content. Resolves either
25-
* `<segments>.md` or `<segments>/index.md`. Throws if neither exists.
25+
* `<segments>.md` or `<segments>/index.md`. Returns null when neither exists.
2626
*
2727
* Cached at this layer (keyed on `segments`) so the page render and its
2828
* `generateMetadata` share one compile, and so callers don't each need
2929
* their own `'use cache'`. Content only changes on deploy, so `'max'`.
30+
*
31+
* Returns null (rather than throwing) for a missing file: throwing inside a
32+
* `'use cache'` scope surfaces as a render error instead of letting callers
33+
* fall through to `notFound()`.
3034
*/
31-
export async function readMarkdownPage(segments: string[]): Promise<PageData> {
35+
export async function readMarkdownPage(
36+
segments: string[]
37+
): Promise<PageData | null> {
3238
'use cache';
3339
cacheLife('max');
3440
const routePath = segments.join('/') || 'index';
35-
let mdx: string;
36-
try {
37-
mdx = await fs.readFile(path.join(ROOT, routePath + '.md'), 'utf8');
38-
} catch {
39-
mdx = await fs.readFile(path.join(ROOT, routePath, 'index.md'), 'utf8');
41+
let mdx: string | null = null;
42+
for (const candidate of [
43+
path.join(ROOT, routePath + '.md'),
44+
path.join(ROOT, routePath, 'index.md'),
45+
]) {
46+
try {
47+
mdx = await fs.readFile(candidate, 'utf8');
48+
break;
49+
} catch {
50+
// Try next candidate.
51+
}
4052
}
53+
if (mdx == null) return null;
4154
const {toc, content, meta, languages} = await compileMDX(mdx, routePath, {});
4255
return {toc, content, meta, languages};
4356
}

0 commit comments

Comments
 (0)