+ We highly recommend using the development build locally when debugging
+ your app since it tracks additional debug info and provides helpful
+ warnings about potential problems in your apps, but if you encounter
+ an exception while using the production build, this page will
+ reassemble the original error message.
+
+
+ */}
+
+ );
+}
diff --git a/src/app/errors/components/error-mdx.tsx b/src/app/errors/components/error-mdx.tsx
new file mode 100644
index 00000000000..50a74d7a3f1
--- /dev/null
+++ b/src/app/errors/components/error-mdx.tsx
@@ -0,0 +1,48 @@
+'use client';
+
+import {Fragment} from 'react';
+import {ErrorDecoderProvider} from '../../../components/_/ErrorDecoderContext';
+import {MDXComponents} from '../../../components/MDX/MDXComponents';
+
+export default function ErrorMDX({
+ content,
+ errorCode,
+ errorMessage,
+}: {
+ content: string;
+ errorCode: string | null;
+ errorMessage: string | null;
+}) {
+ return (
+
+ {JSON.parse(content, reviveNodeOnClient)}
+
+ );
+}
+
+// Deserialize a client React tree from JSON.
+function reviveNodeOnClient(parentPropertyName: unknown, val: any) {
+ if (Array.isArray(val) && val[0] == '$r') {
+ // Assume it's a React element.
+ let Type = val[1];
+ let key = val[2];
+ if (key == null) {
+ key = parentPropertyName; // Index within a parent.
+ }
+ let props = val[3];
+ if (Type === 'wrapper') {
+ Type = Fragment;
+ props = {children: props.children};
+ }
+ if (Type in MDXComponents) {
+ Type = MDXComponents[Type as keyof typeof MDXComponents];
+ }
+ if (!Type) {
+ console.error('Unknown type: ' + Type);
+ Type = Fragment;
+ }
+ return ;
+ } else {
+ return val;
+ }
+}
diff --git a/src/app/errors/page.tsx b/src/app/errors/page.tsx
new file mode 100644
index 00000000000..6b07daf59f7
--- /dev/null
+++ b/src/app/errors/page.tsx
@@ -0,0 +1,28 @@
+import type {Metadata, ResolvingMetadata} from 'next';
+import {fetchReactErrorCodes} from './[errorCode]/lib/fetch-error-codes';
+import ErrorDecoderPage from './components/error-decoder-page';
+
+export default async function ErrorPage() {
+ const errorCodes = await fetchReactErrorCodes();
+
+ return ;
+}
+
+export async function generateMetadata(
+ _: unknown,
+ parent?: ResolvingMetadata
+): Promise {
+ const resolvedParent = await parent;
+ const parentOpenGraph = resolvedParent ? resolvedParent.openGraph : {};
+
+ return {
+ title: 'Minified Error Decoder',
+ alternates: {
+ canonical: `./`,
+ },
+ openGraph: {
+ ...parentOpenGraph,
+ title: 'Minified Error Decoder',
+ },
+ };
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 00000000000..003887cf65d
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,68 @@
+import type {Metadata, Viewport} from 'next';
+import {SharedRootBody, SharedRootHead} from '../components/_/root-layout';
+import {siteConfig} from '../siteConfig';
+import {preload} from 'react-dom';
+
+import '@docsearch/css';
+import '../styles/algolia.css';
+import '../styles/index.css';
+import '../styles/sandpack.css';
+
+export default function RootLayout({children}: React.PropsWithChildren) {
+ [
+ 'https://react.dev/fonts/Source-Code-Pro-Regular.woff2',
+ 'https://react.dev/fonts/Source-Code-Pro-Bold.woff2',
+ 'https://react.dev/fonts/Optimistic_Display_W_Md.woff2',
+ 'https://react.dev/fonts/Optimistic_Display_W_SBd.woff2',
+ 'https://react.dev/fonts/Optimistic_Display_W_Bd.woff2',
+ 'https://react.dev/fonts/Optimistic_Text_W_Md.woff2',
+ 'https://react.dev/fonts/Optimistic_Text_W_Bd.woff2',
+ 'https://react.dev/fonts/Optimistic_Text_W_Rg.woff2',
+ 'https://react.dev/fonts/Optimistic_Text_W_It.woff2',
+ ].forEach((href) => {
+ preload(href, {as: 'font', type: 'font/woff2', crossOrigin: 'anonymous'});
+ });
+
+ return (
+
+
+
+
+ {children}
+
+ );
+}
+
+export const viewport: Viewport = {
+ width: 'device-width',
+ initialScale: 1,
+};
+
+export const metadata: Metadata = {
+ metadataBase: new URL('https://' + getDomain(siteConfig.languageCode)),
+ alternates: {
+ canonical: './',
+ },
+ openGraph: {
+ type: 'website',
+ url: './',
+ images: ['/images/og-default.png'],
+ },
+ twitter: {
+ card: 'summary_large_image',
+ site: '@reactjs',
+ creator: '@reactjs',
+ images: ['/images/og-default.png'],
+ },
+ facebook: {
+ appId: '623268441017527',
+ },
+};
+
+function getDomain(languageCode: string): string {
+ const subdomain = languageCode === 'en' ? '' : languageCode + '.';
+ return subdomain + 'react.dev';
+}
diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx
index fe927251709..752cf5f30ee 100644
--- a/src/components/Layout/Feedback.tsx
+++ b/src/components/Layout/Feedback.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -10,14 +12,13 @@
*/
import {useState} from 'react';
-import {useRouter} from 'next/router';
+import {usePathname} from 'next/navigation';
import cn from 'classnames';
export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) {
- const {asPath} = useRouter();
- const cleanedPath = asPath.split(/[\?\#]/)[0];
+ const pathname = usePathname();
// Reset on route changes.
- return ;
+ return ;
}
const thumbsUpIcon = (
diff --git a/src/components/Layout/HomeContent.js b/src/components/Layout/HomeContent.js
index f9b785db420..251f2cab6d6 100644
--- a/src/components/Layout/HomeContent.js
+++ b/src/components/Layout/HomeContent.js
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx
index aa39fe5fc2b..50b03b31b2d 100644
--- a/src/components/Layout/Page.tsx
+++ b/src/components/Layout/Page.tsx
@@ -9,9 +9,11 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
+'use client';
+
import {Suspense} from 'react';
import * as React from 'react';
-import {useRouter} from 'next/router';
+import {useRouter} from 'next/compat/router';
import {SidebarNav} from './SidebarNav';
import {Footer} from './Footer';
import {Toc} from './Toc';
@@ -28,6 +30,7 @@ import {HomeContent} from './HomeContent';
import {TopNav} from './TopNav';
import cn from 'classnames';
import Head from 'next/head';
+import {usePathname} from 'next/navigation';
import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock');
@@ -43,6 +46,8 @@ interface PageProps {
};
section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown';
languages?: Languages | null;
+ /** Should this being used with the Next.js App Router? */
+ appRouter?: boolean;
}
export function Page({
@@ -52,9 +57,14 @@ export function Page({
meta,
section,
languages = null,
+ appRouter = false,
}: PageProps) {
- const {asPath} = useRouter();
- const cleanedPath = asPath.split(/[\?\#]/)[0];
+ const pagesRouter = useRouter();
+ const pathname = usePathname()!;
+ const cleanedPath = pagesRouter
+ ? pagesRouter.asPath.split(/[\?\#]/)[0]
+ : pathname;
+
const {route, nextRoute, prevRoute, breadcrumbs, order} = getRouteMeta(
cleanedPath,
routeTree
@@ -125,13 +135,15 @@ export function Page({
return (
<>
-
+ {!appRouter && (
+
+ )}
{(isHomePage || isBlogIndex) && (
+ key={cleanedPath}>
{content}
- );
-}
-
function YouTubeIframe(props: any) {
return (
diff --git a/src/components/MDX/Sandpack/CustomPreset.tsx b/src/components/MDX/Sandpack/CustomPreset.tsx
index 4a241c87cbf..25dbbb41bc5 100644
--- a/src/components/MDX/Sandpack/CustomPreset.tsx
+++ b/src/components/MDX/Sandpack/CustomPreset.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
diff --git a/src/components/MDX/Sandpack/NavigationBar.tsx b/src/components/MDX/Sandpack/NavigationBar.tsx
index 3fe743a2d24..6b3ebb941f6 100644
--- a/src/components/MDX/Sandpack/NavigationBar.tsx
+++ b/src/components/MDX/Sandpack/NavigationBar.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
diff --git a/src/components/MDX/Sandpack/runESLint.tsx b/src/components/MDX/Sandpack/runESLint.tsx
index 667b22d7eb2..7c7c908eafd 100644
--- a/src/components/MDX/Sandpack/runESLint.tsx
+++ b/src/components/MDX/Sandpack/runESLint.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
diff --git a/src/components/MDX/Sandpack/useSandpackLint.tsx b/src/components/MDX/Sandpack/useSandpackLint.tsx
index 479b53ee0df..9113bb232bf 100644
--- a/src/components/MDX/Sandpack/useSandpackLint.tsx
+++ b/src/components/MDX/Sandpack/useSandpackLint.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
diff --git a/src/components/MDX/TerminalBlock.tsx b/src/components/MDX/TerminalBlock.tsx
index 0fd0160d665..66a34727d9c 100644
--- a/src/components/MDX/TerminalBlock.tsx
+++ b/src/components/MDX/TerminalBlock.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
diff --git a/src/components/MDX/TocContext.tsx b/src/components/MDX/TocContext.tsx
index 924e6e09eed..296a1b106a8 100644
--- a/src/components/MDX/TocContext.tsx
+++ b/src/components/MDX/TocContext.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index 24b066d70f4..402f03bb722 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -9,12 +9,11 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
-import Head from 'next/head';
import Link from 'next/link';
-import Router from 'next/router';
+import {useRouter} from 'next/navigation';
import {lazy, useEffect} from 'react';
import * as React from 'react';
-import {createPortal} from 'react-dom';
+import {createPortal, preconnect} from 'react-dom';
import {siteConfig} from 'siteConfig';
import type {ComponentType, PropsWithChildren} from 'react';
import type {DocSearchModalProps} from '@docsearch/react/modal';
@@ -118,14 +117,13 @@ export function Search({
},
}: SearchProps) {
useDocSearchKeyboardEvents({isOpen, onOpen, onClose});
+
+ const router = useRouter();
+
+ preconnect(`https://${options.appId}-dsn.algolia.net`);
+
return (
<>
-
-
-
{isOpen &&
createPortal(
{
diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx
index 90604102023..d9a1437ebb2 100644
--- a/src/components/Seo.tsx
+++ b/src/components/Seo.tsx
@@ -11,9 +11,9 @@
import * as React from 'react';
import Head from 'next/head';
-import {withRouter, Router} from 'next/router';
import {siteConfig} from '../siteConfig';
import {finishedTranslations} from 'utils/finishedTranslations';
+import {usePathname} from 'next/navigation';
export interface SeoProps {
title: string;
@@ -34,166 +34,158 @@ function getDomain(languageCode: string): string {
return subdomain + 'react.dev';
}
-export const Seo = withRouter(
- ({
- title,
- titleForTitleTag,
- image = '/images/og-default.png',
- router,
- children,
- isHomePage,
- searchOrder,
- }: SeoProps & {router: Router}) => {
- const siteDomain = getDomain(siteConfig.languageCode);
- const canonicalUrl = `https://${siteDomain}${
- router.asPath.split(/[\?\#]/)[0]
- }`;
- // Allow setting a different title for Google results
- const pageTitle =
- (titleForTitleTag ?? title) + (isHomePage ? '' : ' – React');
- // Twitter's meta parser is not very good.
- const twitterTitle = pageTitle.replace(/[<>]/g, '');
- let description = isHomePage
- ? 'React is the library for web and native user interfaces. Build user interfaces out of individual pieces called components written in JavaScript. React is designed to let you seamlessly combine components written by independent people, teams, and organizations.'
- : 'The library for web and native user interfaces';
- return (
-
-
- {title != null && {pageTitle}}
- {isHomePage && (
- // Let Google figure out a good description for each page.
-
- )}
-
+export function Seo({
+ title,
+ titleForTitleTag,
+ image = '/images/og-default.png',
+ children,
+ isHomePage,
+ searchOrder,
+}: SeoProps) {
+ const pathname = usePathname();
+ const siteDomain = getDomain(siteConfig.languageCode);
+ const canonicalUrl = `https://${siteDomain}${pathname}`;
+ // Allow setting a different title for Google results
+ const pageTitle =
+ (titleForTitleTag ?? title) + (isHomePage ? '' : ' – React');
+ // Twitter's meta parser is not very good.
+ const twitterTitle = pageTitle.replace(/[<>]/g, '');
+ let description = isHomePage
+ ? 'React is the library for web and native user interfaces. Build user interfaces out of individual pieces called components written in JavaScript. React is designed to let you seamlessly combine components written by independent people, teams, and organizations.'
+ : 'The library for web and native user interfaces';
+ return (
+
+
+ {title != null && {pageTitle}}
+ {isHomePage && (
+ // Let Google figure out a good description for each page.
+
+ )}
+
+
+ {finishedTranslations.map((languageCode) => (
- {finishedTranslations.map((languageCode) => (
-
- ))}
-
-
-
- {title != null && (
-
- )}
- {description != null && (
-
- )}
+ ))}
+
+
+
+ {title != null && (
+
+ )}
+ {description != null && (
+ )}
+
+
+
+
+ {title != null && (
+
+ )}
+ {description != null && (
-
-
- {title != null && (
-
- )}
- {description != null && (
-
- )}
-
-
- {searchOrder != null && (
-
- )}
-
-
-
-
-
-
-
-
-
- {children}
-
- );
- }
-);
+ )}
+
+
+ {searchOrder != null && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/src/components/ErrorDecoderContext.tsx b/src/components/_/ErrorDecoderContext.tsx
similarity index 92%
rename from src/components/ErrorDecoderContext.tsx
rename to src/components/_/ErrorDecoderContext.tsx
index 77e9ebf7d5b..c4b209a058a 100644
--- a/src/components/ErrorDecoderContext.tsx
+++ b/src/components/_/ErrorDecoderContext.tsx
@@ -1,3 +1,5 @@
+'use client';
+
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -19,6 +21,8 @@ export const ErrorDecoderContext = createContext<
| typeof notInErrorDecoderContext
>(notInErrorDecoderContext);
+export const ErrorDecoderProvider = ErrorDecoderContext.Provider;
+
export const useErrorDecoderParams = () => {
const params = useContext(ErrorDecoderContext);
diff --git a/src/components/_/README.md b/src/components/_/README.md
new file mode 100644
index 00000000000..8c5baae4928
--- /dev/null
+++ b/src/components/_/README.md
@@ -0,0 +1,7 @@
+# `components/_` folder {/*components_-folder*/}
+
+This folder surves as a temporary location during transition from Next.js Pages Router to Next.js App Router. During this phase, many layout components may be shared bwetween both Next.js Pages Router and Next.js App Router.
+
+Due to the requirements of the Next.js Pages Router, any components under this foldeer must either be shared components or client components. React Server Components are not allowed in this folder.
+
+Once the migration to Next.js App Router is complete, this folder will be removed, and all components will be relocated to their appropriate locations.
diff --git a/src/components/_/root-layout.tsx b/src/components/_/root-layout.tsx
new file mode 100644
index 00000000000..6473e815b26
--- /dev/null
+++ b/src/components/_/root-layout.tsx
@@ -0,0 +1,154 @@
+import type React from 'react';
+
+export function SharedRootHead() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export function SharedRootBody({children}: React.PropsWithChildren) {
+ return (
+
+
+
+ {children}
+
+ );
+}
diff --git a/src/hooks/useHash.ts b/src/hooks/useHash.ts
new file mode 100644
index 00000000000..a64e670b802
--- /dev/null
+++ b/src/hooks/useHash.ts
@@ -0,0 +1,14 @@
+import {useSyncExternalStore} from 'react';
+
+export function useHash() {
+ return useSyncExternalStore(
+ (onChange) => {
+ window.addEventListener('hashchange', onChange);
+ return () => {
+ window.removeEventListener('hashchange', onChange);
+ };
+ },
+ () => window.location.hash,
+ () => null
+ );
+}
diff --git a/src/hooks/usePendingRoute.ts b/src/hooks/usePendingRoute.ts
index 17d7525b4f8..5ea765b0e4b 100644
--- a/src/hooks/usePendingRoute.ts
+++ b/src/hooks/usePendingRoute.ts
@@ -9,14 +9,20 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
-import {useRouter} from 'next/router';
+import {useRouter} from 'next/compat/router';
import {useState, useRef, useEffect} from 'react';
const usePendingRoute = () => {
- const {events} = useRouter();
+ const pagesRouter = useRouter();
const [pendingRoute, setPendingRoute] = useState(null);
const currentRoute = useRef(null);
useEffect(() => {
+ if (!pagesRouter) {
+ return;
+ }
+
+ const {events} = pagesRouter;
+
let routeTransitionTimer: any = null;
const handleRouteChangeStart = (url: string) => {
@@ -40,7 +46,7 @@ const usePendingRoute = () => {
events.off('routeChangeComplete', handleRouteChangeComplete);
clearTimeout(routeTransitionTimer);
};
- }, [events]);
+ }, [pagesRouter]);
return pendingRoute;
};
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
index 21b6f4d4f77..8aba8e3ef35 100644
--- a/src/pages/_document.tsx
+++ b/src/pages/_document.tsx
@@ -11,153 +11,17 @@
import {Html, Head, Main, NextScript} from 'next/document';
import {siteConfig} from '../siteConfig';
+import {SharedRootBody, SharedRootHead} from '../components/_/root-layout';
const MyDocument = () => {
return (
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
);
};
diff --git a/src/pages/errors/[errorCode].tsx b/src/pages/errors/[errorCode].tsx
deleted file mode 100644
index 67466a1d991..00000000000
--- a/src/pages/errors/[errorCode].tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import {Fragment, useMemo} from 'react';
-import {Page} from 'components/Layout/Page';
-import {MDXComponents} from 'components/MDX/MDXComponents';
-import sidebarLearn from 'sidebarLearn.json';
-import type {RouteItem} from 'components/Layout/getRouteMeta';
-import {GetStaticPaths, GetStaticProps, InferGetStaticPropsType} from 'next';
-import {ErrorDecoderContext} from 'components/ErrorDecoderContext';
-import compileMDX from 'utils/compileMDX';
-
-interface ErrorDecoderProps {
- errorCode: string | null;
- errorMessage: string | null;
- content: string;
- toc: string;
- meta: any;
-}
-
-export default function ErrorDecoderPage({
- errorMessage,
- errorCode,
- content,
-}: InferGetStaticPropsType) {
- const parsedContent = useMemo(
- () => JSON.parse(content, reviveNodeOnClient),
- [content]
- );
-
- return (
-
-
-
{parsedContent}
- {/*
-
- We highly recommend using the development build locally when debugging
- your app since it tracks additional debug info and provides helpful
- warnings about potential problems in your apps, but if you encounter
- an exception while using the production build, this page will
- reassemble the original error message.
-