Skip to content

Commit eecf428

Browse files
committed
Refactor: migrate error decoder to App Router
1 parent 5ca8e8b commit eecf428

File tree

21 files changed

+668
-574
lines changed

21 files changed

+668
-574
lines changed

next-env.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
/// <reference types="next/navigation-types/compat/navigation" />
4+
/// <reference path="./.next/types/routes.d.ts" />
35

46
// NOTE: This file should not be edited
5-
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
7+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const nextConfig = {
7373
return config;
7474
},
7575
serverExternalPackages: [
76+
'eslint',
7677
'@babel/core',
7778
'@babel/preset-react',
7879
'@babel/plugin-transform-modules-commonjs',

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"classnames": "^2.2.6",
3636
"debounce": "^1.2.1",
3737
"github-slugger": "^1.3.0",
38-
"next": "15.1.11",
38+
"next": "^15.4.0",
3939
"next-remote-watch": "^1.0.0",
4040
"parse-numeric-range": "^1.2.0",
4141
"react": "^19.0.0",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {unstable_cache} from 'next/cache';
2+
3+
export const fetchReactErrorCodes = unstable_cache(async () => {
4+
return (
5+
await fetch(
6+
'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'
7+
)
8+
).json() as Promise<{[key: string]: string}>;
9+
}, ['react-error-codes']);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {unstable_cache} from 'next/cache';
2+
import {notFound} from 'next/navigation';
3+
import ErrorDecoderPage from '../components/error-decoder-page';
4+
import type {Metadata, ResolvingMetadata} from 'next';
5+
6+
interface ErrorDecoderPageProps {
7+
params: Promise<{errorCode: string}>;
8+
}
9+
10+
export default async function ErrorPage({params}: ErrorDecoderPageProps) {
11+
const {errorCode} = await params;
12+
const errorCodes = await fetchReactErrorCodes();
13+
14+
if (errorCode && !(errorCode in errorCodes)) {
15+
notFound();
16+
}
17+
18+
return <ErrorDecoderPage errorCode={errorCode} errorCodes={errorCodes} />;
19+
}
20+
21+
const fetchReactErrorCodes = unstable_cache(async () => {
22+
return (
23+
await fetch(
24+
'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'
25+
)
26+
).json() as Promise<{[key: string]: string}>;
27+
}, ['react-error-codes']);
28+
29+
export async function generateStaticParams(): Promise<
30+
Array<{errorCode: string}>
31+
> {
32+
return Object.keys(await fetchReactErrorCodes()).map((code) => ({
33+
errorCode: code,
34+
}));
35+
}
36+
37+
export async function generateMetadata(
38+
{params}: ErrorDecoderPageProps,
39+
parent?: ResolvingMetadata
40+
): Promise<Metadata> {
41+
const {errorCode} = await params;
42+
const resolvedParent = await parent;
43+
const parentOpenGraph = resolvedParent ? resolvedParent.openGraph : {};
44+
45+
return {
46+
title: `Minified React error #${errorCode}`,
47+
alternates: {
48+
canonical: `./`,
49+
},
50+
openGraph: {
51+
...parentOpenGraph,
52+
title: `Minified React error #${errorCode}`,
53+
},
54+
};
55+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import fsp from 'node:fs/promises';
2+
import fs from 'node:fs';
3+
import {Page} from '../../../components/Layout/Page';
4+
import sidebarLearn from 'sidebarLearn.json';
5+
import type {RouteItem} from '../../../components/Layout/getRouteMeta';
6+
import {notFound} from 'next/navigation';
7+
import {join} from 'node:path';
8+
import compileMDX from '../../../utils/compileMDX';
9+
import ErrorMDX from './error-mdx';
10+
11+
export default async function ErrorDecoderPage({
12+
errorCode,
13+
errorCodes,
14+
}: {
15+
errorCode: string | null;
16+
errorCodes: {[key: string]: string};
17+
}) {
18+
if (errorCode && !errorCodes[errorCode]) {
19+
notFound();
20+
}
21+
22+
const rootDir = join(process.cwd(), 'src', 'content', 'errors');
23+
let path = errorCode || 'index';
24+
let mdx;
25+
if (fs.existsSync(join(rootDir, path + '.md'))) {
26+
mdx = await fsp.readFile(join(rootDir, path + '.md'), 'utf8');
27+
} else {
28+
mdx = await fsp.readFile(join(rootDir, 'generic.md'), 'utf8');
29+
}
30+
31+
const {content} = await compileMDX(mdx, path, {code: errorCode, errorCodes});
32+
const errorMessage = errorCode ? errorCodes[errorCode] : null;
33+
34+
return (
35+
<Page
36+
toc={[]}
37+
meta={{
38+
title: errorCode
39+
? 'Minified React error #' + errorCode
40+
: 'Minified Error Decoder',
41+
}}
42+
routeTree={sidebarLearn as RouteItem}
43+
section="unknown"
44+
appRouter>
45+
<div>
46+
<ErrorMDX
47+
content={content}
48+
errorCode={errorCode}
49+
errorMessage={errorMessage}
50+
/>
51+
</div>
52+
{/* <MaxWidth>
53+
<P>
54+
We highly recommend using the development build locally when debugging
55+
your app since it tracks additional debug info and provides helpful
56+
warnings about potential problems in your apps, but if you encounter
57+
an exception while using the production build, this page will
58+
reassemble the original error message.
59+
</P>
60+
<ErrorDecoder />
61+
</MaxWidth> */}
62+
</Page>
63+
);
64+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use client';
2+
3+
import {Fragment} from 'react';
4+
import {ErrorDecoderProvider} from '../../../components/_/ErrorDecoderContext';
5+
import {MDXComponents} from '../../../components/MDX/MDXComponents';
6+
7+
export default function ErrorMDX({
8+
content,
9+
errorCode,
10+
errorMessage,
11+
}: {
12+
content: string;
13+
errorCode: string | null;
14+
errorMessage: string | null;
15+
}) {
16+
return (
17+
<ErrorDecoderProvider value={{errorMessage, errorCode}}>
18+
{JSON.parse(content, reviveNodeOnClient)}
19+
</ErrorDecoderProvider>
20+
);
21+
}
22+
23+
// Deserialize a client React tree from JSON.
24+
function reviveNodeOnClient(parentPropertyName: unknown, val: any) {
25+
if (Array.isArray(val) && val[0] == '$r') {
26+
// Assume it's a React element.
27+
let Type = val[1];
28+
let key = val[2];
29+
if (key == null) {
30+
key = parentPropertyName; // Index within a parent.
31+
}
32+
let props = val[3];
33+
if (Type === 'wrapper') {
34+
Type = Fragment;
35+
props = {children: props.children};
36+
}
37+
if (Type in MDXComponents) {
38+
Type = MDXComponents[Type as keyof typeof MDXComponents];
39+
}
40+
if (!Type) {
41+
console.error('Unknown type: ' + Type);
42+
Type = Fragment;
43+
}
44+
return <Type key={key} {...props} />;
45+
} else {
46+
return val;
47+
}
48+
}

src/app/errors/page.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type {Metadata, ResolvingMetadata} from 'next';
2+
import {fetchReactErrorCodes} from './[errorCode]/lib/fetch-error-codes';
3+
import ErrorDecoderPage from './components/error-decoder-page';
4+
5+
export default async function ErrorPage() {
6+
const errorCodes = await fetchReactErrorCodes();
7+
8+
return <ErrorDecoderPage errorCode={null} errorCodes={errorCodes} />;
9+
}
10+
11+
export async function generateMetadata(
12+
_: unknown,
13+
parent?: ResolvingMetadata
14+
): Promise<Metadata> {
15+
const resolvedParent = await parent;
16+
const parentOpenGraph = resolvedParent ? resolvedParent.openGraph : {};
17+
18+
return {
19+
title: 'Minified Error Decoder',
20+
alternates: {
21+
canonical: `./`,
22+
},
23+
openGraph: {
24+
...parentOpenGraph,
25+
title: 'Minified Error Decoder',
26+
},
27+
};
28+
}

src/app/layout.tsx

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,68 @@
1+
import type {Metadata, Viewport} from 'next';
12
import {SharedRootBody, SharedRootHead} from '../components/_/root-layout';
23
import {siteConfig} from '../siteConfig';
4+
import {preload} from 'react-dom';
5+
6+
import '@docsearch/css';
7+
import '../styles/algolia.css';
8+
import '../styles/index.css';
9+
import '../styles/sandpack.css';
310

411
export default function RootLayout({children}: React.PropsWithChildren) {
12+
[
13+
'https://react.dev/fonts/Source-Code-Pro-Regular.woff2',
14+
'https://react.dev/fonts/Source-Code-Pro-Bold.woff2',
15+
'https://react.dev/fonts/Optimistic_Display_W_Md.woff2',
16+
'https://react.dev/fonts/Optimistic_Display_W_SBd.woff2',
17+
'https://react.dev/fonts/Optimistic_Display_W_Bd.woff2',
18+
'https://react.dev/fonts/Optimistic_Text_W_Md.woff2',
19+
'https://react.dev/fonts/Optimistic_Text_W_Bd.woff2',
20+
'https://react.dev/fonts/Optimistic_Text_W_Rg.woff2',
21+
'https://react.dev/fonts/Optimistic_Text_W_It.woff2',
22+
].forEach((href) => {
23+
preload(href, {as: 'font', type: 'font/woff2', crossOrigin: 'anonymous'});
24+
});
25+
526
return (
6-
<html lang={siteConfig.languageCode} dir={siteConfig.isRTL ? 'rtl' : 'ltr'}>
27+
<html
28+
lang={siteConfig.languageCode}
29+
dir={siteConfig.isRTL ? 'rtl' : 'ltr'}
30+
suppressHydrationWarning>
731
<head>
832
<SharedRootHead />
933
</head>
1034
<SharedRootBody>{children}</SharedRootBody>
1135
</html>
1236
);
1337
}
38+
39+
export const viewport: Viewport = {
40+
width: 'device-width',
41+
initialScale: 1,
42+
};
43+
44+
export const metadata: Metadata = {
45+
metadataBase: new URL('https://' + getDomain(siteConfig.languageCode)),
46+
alternates: {
47+
canonical: './',
48+
},
49+
openGraph: {
50+
type: 'website',
51+
url: './',
52+
images: ['/images/og-default.png'],
53+
},
54+
twitter: {
55+
card: 'summary_large_image',
56+
site: '@reactjs',
57+
creator: '@reactjs',
58+
images: ['/images/og-default.png'],
59+
},
60+
facebook: {
61+
appId: '623268441017527',
62+
},
63+
};
64+
65+
function getDomain(languageCode: string): string {
66+
const subdomain = languageCode === 'en' ? '' : languageCode + '.';
67+
return subdomain + 'react.dev';
68+
}

src/components/Layout/Sidebar/SidebarRouteTree.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
'use client';
2+
13
/**
24
* Copyright (c) Meta Platforms, Inc. and affiliates.
35
*

0 commit comments

Comments
 (0)