@@ -232,22 +460,7 @@ function Image(props: any) {
return
;
}
-function annotateMDXComponents(
- components: Record
-): Record {
- return Object.entries(components).reduce((acc, [key, Component]) => {
- acc[key] = (props) => ;
- acc[key].displayName = `Annotated(${key})`; // Optional, for debugging
- return acc;
- }, {} as Record);
-}
-
-export const MDXComponentsToc = annotateMDXComponents({
- a: Link,
- code: InlineCode,
-});
-
-export const MDXComponents = annotateMDXComponents({
+export const MDXComponents = {
p: P,
strong: Strong,
blockquote: Blockquote,
@@ -316,4 +529,11 @@ export const MDXComponents = annotateMDXComponents({
CodeStep,
YouTubeIframe,
ErrorDecoder,
-});
+};
+
+for (let key in MDXComponents) {
+ if (MDXComponents.hasOwnProperty(key)) {
+ const MDXComponent: any = (MDXComponents as any)[key];
+ MDXComponent.mdxName = key;
+ }
+}
diff --git a/src/components/MDX/PackageImport.tsx b/src/components/MDX/PackageImport.tsx
index a4d5fa1405d..5e2da820e55 100644
--- a/src/components/MDX/PackageImport.tsx
+++ b/src/components/MDX/PackageImport.tsx
@@ -12,10 +12,10 @@ interface PackageImportProps {
export function PackageImport({children}: PackageImportProps) {
const terminal = Children.toArray(children).filter((child: any) => {
- return child.props?.['data-mdx-name'] !== 'pre';
+ return child.type?.mdxName !== 'pre';
});
const code = Children.toArray(children).map((child: any, i: number) => {
- if (child.props?.['data-mdx-name'] === 'pre') {
+ if (child.type?.mdxName === 'pre') {
return (
) => (
-
-);
-
-export const Strong = (strong: HTMLAttributes) => (
-
-);
-
-export const OL = (p: HTMLAttributes) => (
-
-);
-export const LI = (p: HTMLAttributes) => (
-
-);
-export const UL = (p: HTMLAttributes) => (
-
-);
-
-export const Divider = () => (
-
-);
diff --git a/src/components/MDX/Sandpack/CustomPreset.tsx b/src/components/MDX/Sandpack/CustomPreset.tsx
index f95d3270ac7..7d6e566d270 100644
--- a/src/components/MDX/Sandpack/CustomPreset.tsx
+++ b/src/components/MDX/Sandpack/CustomPreset.tsx
@@ -1,5 +1,3 @@
-'use client';
-
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/Sandpack/SandpackRoot.tsx b/src/components/MDX/Sandpack/SandpackRoot.tsx
index 1084ea64747..67f40d0b3b3 100644
--- a/src/components/MDX/Sandpack/SandpackRoot.tsx
+++ b/src/components/MDX/Sandpack/SandpackRoot.tsx
@@ -1,5 +1,3 @@
-'use client';
-
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/Sandpack/createFileMap.ts b/src/components/MDX/Sandpack/createFileMap.ts
index 07bdcd377f4..193b07be825 100644
--- a/src/components/MDX/Sandpack/createFileMap.ts
+++ b/src/components/MDX/Sandpack/createFileMap.ts
@@ -12,22 +12,19 @@ export const SUPPORTED_FILES = [AppJSPath, StylesCSSPath];
export const createFileMap = (codeSnippets: any) => {
return codeSnippets.reduce(
(result: Record, codeSnippet: React.ReactElement) => {
- // TODO: actually fix this
+ if (
+ (codeSnippet.type as any).mdxName !== 'pre' &&
+ codeSnippet.type !== 'pre'
+ ) {
+ return result;
+ }
const {props} = (
codeSnippet.props as PropsWithChildren<{
children: ReactElement<
- HTMLAttributes & {
- meta?: string;
- 'data-mdx-name'?: string;
- }
+ HTMLAttributes & {meta?: string}
>;
}>
).children;
-
- if (props?.['data-mdx-name'] !== 'code') {
- return result;
- }
-
let filePath; // path in the folder structure
let fileHidden = false; // if the file is available as a tab
let fileActive = false; // if the file tab is shown by default
diff --git a/src/components/MDX/Sandpack/index.tsx b/src/components/MDX/Sandpack/index.tsx
index d90facfe80e..6755ba8de69 100644
--- a/src/components/MDX/Sandpack/index.tsx
+++ b/src/components/MDX/Sandpack/index.tsx
@@ -1,5 +1,3 @@
-'use client';
-
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/SandpackWithHTMLOutput.tsx b/src/components/MDX/SandpackWithHTMLOutput.tsx
index 041d7bf9bff..51ce28dc149 100644
--- a/src/components/MDX/SandpackWithHTMLOutput.tsx
+++ b/src/components/MDX/SandpackWithHTMLOutput.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import {Children, memo} from 'react';
import InlineCode from './InlineCode';
import Sandpack from './Sandpack';
diff --git a/src/components/MDX/TerminalBlock.tsx b/src/components/MDX/TerminalBlock.tsx
index 73a10216712..47529271619 100644
--- a/src/components/MDX/TerminalBlock.tsx
+++ b/src/components/MDX/TerminalBlock.tsx
@@ -1,5 +1,3 @@
-'use client';
-
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
diff --git a/src/components/MDX/TocContext.tsx b/src/components/MDX/TocContext.tsx
index cc7080a8bcc..8aeead370e9 100644
--- a/src/components/MDX/TocContext.tsx
+++ b/src/components/MDX/TocContext.tsx
@@ -7,7 +7,7 @@ import type {ReactNode} from 'react';
export type TocItem = {
url: string;
- node: ReactNode;
+ text: ReactNode;
depth: number;
};
export type Toc = Array;
diff --git a/src/components/SafariScrollHandler.tsx b/src/components/SafariScrollHandler.tsx
deleted file mode 100644
index 2cb3e4037a3..00000000000
--- a/src/components/SafariScrollHandler.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-'use client';
-
-import {useEffect} from 'react';
-
-export function ScrollHandler() {
- useEffect(() => {
- // Taken from StackOverflow. Trying to detect both Safari desktop and mobile.
- const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
- if (isSafari) {
- // This is kind of a lie.
- // We still rely on the manual Next.js scrollRestoration logic.
- // However, we *also* don't want Safari grey screen during the back swipe gesture.
- // Seems like it doesn't hurt to enable auto restore *and* Next.js logic at the same time.
- history.scrollRestoration = 'auto';
- } else {
- // For other browsers, let Next.js set scrollRestoration to 'manual'.
- // It seems to work better for Chrome and Firefox which don't animate the back swipe.
- }
- }, []);
-
- return null;
-}
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index 3ff5c188131..c7401487b75 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -4,7 +4,7 @@
import Head from 'next/head';
import Link from 'next/link';
-import {useRouter} from 'next/navigation';
+import Router from 'next/router';
import {lazy, useEffect} from 'react';
import * as React from 'react';
import {createPortal} from 'react-dom';
@@ -111,7 +111,6 @@ export function Search({
},
}: SearchProps) {
useDocSearchKeyboardEvents({isOpen, onOpen, onClose});
- const router = useRouter();
return (
<>
@@ -128,7 +127,7 @@ export function Search({
onClose={onClose}
navigator={{
navigate({itemUrl}: any) {
- router.push(itemUrl);
+ Router.push(itemUrl);
},
}}
transformItems={(items: any[]) => {
diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx
new file mode 100644
index 00000000000..628085744d7
--- /dev/null
+++ b/src/components/Seo.tsx
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ */
+
+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';
+
+export interface SeoProps {
+ title: string;
+ titleForTitleTag: undefined | string;
+ description?: string;
+ image?: string;
+ // jsonld?: JsonLDType | Array;
+ children?: React.ReactNode;
+ isHomePage: boolean;
+ searchOrder?: number;
+}
+
+// If you are a maintainer of a language fork,
+// deployedTranslations has been moved to src/utils/finishedTranslations.ts.
+
+function getDomain(languageCode: string): string {
+ const subdomain = languageCode === 'en' ? '' : languageCode + '.';
+ 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.
+
+ )}
+
+
+ {finishedTranslations.map((languageCode) => (
+
+ ))}
+
+
+
+ {title != null && (
+
+ )}
+ {description != null && (
+
+ )}
+
+
+
+
+ {title != null && (
+
+ )}
+ {description != null && (
+
+ )}
+
+
+ {searchOrder != null && (
+
+ )}
+
+
+
+
+
+
+
+
+ {children}
+
+ );
+ }
+);
diff --git a/src/components/ThemeScript.jsx b/src/components/ThemeScript.jsx
deleted file mode 100644
index 66034557c27..00000000000
--- a/src/components/ThemeScript.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-function ThemeInlineScript() {
- function setTheme(newTheme) {
- window.__theme = newTheme;
- if (newTheme === 'dark') {
- document.documentElement.classList.add('dark');
- } else if (newTheme === 'light') {
- document.documentElement.classList.remove('dark');
- }
- }
-
- var preferredTheme;
- try {
- preferredTheme = localStorage.getItem('theme');
- } catch (err) {}
-
- window.__setPreferredTheme = function (newTheme) {
- preferredTheme = newTheme;
- setTheme(newTheme);
- try {
- localStorage.setItem('theme', newTheme);
- } catch (err) {}
- };
-
- var initialTheme = preferredTheme;
- var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
-
- if (!initialTheme) {
- initialTheme = darkQuery.matches ? 'dark' : 'light';
- }
- setTheme(initialTheme);
-
- darkQuery.addEventListener('change', function (e) {
- if (!preferredTheme) {
- setTheme(e.matches ? 'dark' : 'light');
- }
- });
-
- document.documentElement.classList.add(
- window.navigator.platform.includes('Mac') ? 'platform-mac' : 'platform-win'
- );
-}
-
-export function ThemeScript() {
- return (
-
- );
-}
diff --git a/src/components/UwuScript.jsx b/src/components/UwuScript.jsx
deleted file mode 100644
index d2c26585d5e..00000000000
--- a/src/components/UwuScript.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-function UwuInlineScript() {
- try {
- let logShown = false;
- function setUwu(isUwu) {
- try {
- if (isUwu) {
- localStorage.setItem('uwu', true);
- document.documentElement.classList.add('uwu');
- if (!logShown) {
- console.log('uwu mode! turn off with ?uwu=0');
- console.log(
- 'logo credit to @sawaratsuki1004 via https://github.com/SAWARATSUKI/ServiceLogos'
- );
- logShown = true;
- }
- } else {
- localStorage.removeItem('uwu');
- document.documentElement.classList.remove('uwu');
- console.log('uwu mode off. turn on with ?uwu');
- }
- } catch (err) {}
- }
- window.__setUwu = setUwu;
- function checkQueryParam() {
- const params = new URLSearchParams(window.location.search);
- const value = params.get('uwu');
- switch (value) {
- case '':
- case 'true':
- case '1':
- return true;
- case 'false':
- case '0':
- return false;
- default:
- return null;
- }
- }
- function checkLocalStorage() {
- try {
- return localStorage.getItem('uwu') === 'true';
- } catch (err) {
- return false;
- }
- }
- const uwuQueryParam = checkQueryParam();
- if (uwuQueryParam != null) {
- setUwu(uwuQueryParam);
- } else if (checkLocalStorage()) {
- document.documentElement.classList.add('uwu');
- }
- } catch (err) {}
-}
-
-export function UwuScript() {
- return (
-
- );
-}
diff --git a/src/content/community/docs-contributors.md b/src/content/community/docs-contributors.md
index 4f456784631..27b32a18f76 100644
--- a/src/content/community/docs-contributors.md
+++ b/src/content/community/docs-contributors.md
@@ -38,6 +38,5 @@ React documentation is written and maintained by the [React team](/community/tea
* [Rick Hanlon](https://twitter.com/rickhanlonii): site development
* [Harish Kumar](https://www.strek.in/): development and maintenance
* [Luna Ruan](https://twitter.com/lunaruan): sandbox improvements
-* [Jimmy Lai](https://twitter.com/feedthejim): site development
We'd also like to thank countless alpha testers and community members who gave us feedback along the way.
diff --git a/src/content/versions.md b/src/content/versions.md
index 8956e80e8d7..8530f632476 100644
--- a/src/content/versions.md
+++ b/src/content/versions.md
@@ -298,4 +298,4 @@ See the first blog post: [Why did we build React?](https://legacy.reactjs.org/bl
React was open sourced at Facebook Seattle in 2013:
-VIDEO
+VIDEO
diff --git a/src/hooks/usePendingRoute.ts b/src/hooks/usePendingRoute.ts
index 2e3f7c6f691..229a36e64c4 100644
--- a/src/hooks/usePendingRoute.ts
+++ b/src/hooks/usePendingRoute.ts
@@ -2,9 +2,40 @@
* Copyright (c) Facebook, Inc. and its affiliates.
*/
+import {useRouter} from 'next/router';
+import {useState, useRef, useEffect} from 'react';
+
const usePendingRoute = () => {
- // TODO: @feedthejim - Implement usePendingRoute when App Router supports tapping into the transition state
- return null;
+ const {events} = useRouter();
+ const [pendingRoute, setPendingRoute] = useState(null);
+ const currentRoute = useRef(null);
+ useEffect(() => {
+ let routeTransitionTimer: any = null;
+
+ const handleRouteChangeStart = (url: string) => {
+ clearTimeout(routeTransitionTimer);
+ routeTransitionTimer = setTimeout(() => {
+ if (currentRoute.current !== url) {
+ currentRoute.current = url;
+ setPendingRoute(url);
+ }
+ }, 100);
+ };
+ const handleRouteChangeComplete = () => {
+ setPendingRoute(null);
+ clearTimeout(routeTransitionTimer);
+ };
+ events.on('routeChangeStart', handleRouteChangeStart);
+ events.on('routeChangeComplete', handleRouteChangeComplete);
+
+ return () => {
+ events.off('routeChangeStart', handleRouteChangeStart);
+ events.off('routeChangeComplete', handleRouteChangeComplete);
+ clearTimeout(routeTransitionTimer);
+ };
+ }, [events]);
+
+ return pendingRoute;
};
export default usePendingRoute;
diff --git a/src/instrumentation.js b/src/instrumentation.js
deleted file mode 100644
index 94c964cea2a..00000000000
--- a/src/instrumentation.js
+++ /dev/null
@@ -1,34 +0,0 @@
-export function register() {
- if (
- process.env.NODE_ENV === 'development' &&
- process.env.NEXT_RUNTIME === 'nodejs'
- ) {
- // watch for changes in the ./src/content directory
- // and trigger an HMR update when a change is detected via a custom WebSocket setup
- const chokidar = require('chokidar');
- const path = require('path');
- const ws = require('ws');
-
- const wsServer = new ws.Server({
- port: 3001,
- });
-
- function triggerRefresh() {
- wsServer.clients.forEach((client) => {
- if (client.readyState === WebSocket.OPEN) {
- client.send(JSON.stringify({event: 'refresh'}));
- }
- });
- }
-
- // the process is in .next so we need to go up two level
- const contentDir = path.resolve(__dirname, '../../src/content');
- const watcher = chokidar.watch(contentDir, {
- ignoreInitial: true,
- });
-
- watcher.on('all', () => {
- triggerRefresh();
- });
- }
-}
diff --git a/src/app/not-found.tsx b/src/pages/404.js
similarity index 60%
rename from src/app/not-found.tsx
rename to src/pages/404.js
index 1ea86a10623..2a88fc29a4e 100644
--- a/src/app/not-found.tsx
+++ b/src/pages/404.js
@@ -5,19 +5,12 @@
import {Page} from 'components/Layout/Page';
import {MDXComponents} from 'components/MDX/MDXComponents';
import sidebarLearn from '../sidebarLearn.json';
-import {RouteItem} from 'components/Layout/getRouteMeta';
-import {generateMetadata as generateSeoMetadata} from 'utils/generateMetadata';
const {Intro, MaxWidth, p: P, a: A} = MDXComponents;
export default function NotFound() {
return (
-
+
This page doesn’t exist.
@@ -34,11 +27,3 @@ export default function NotFound() {
);
}
-
-export async function generateMetadata({}: {}) {
- return generateSeoMetadata({
- title: 'Not Found',
- isHomePage: false,
- path: '/404',
- });
-}
diff --git a/src/app/error.tsx b/src/pages/500.js
similarity index 61%
rename from src/app/error.tsx
rename to src/pages/500.js
index 31725c447c3..b043e35b262 100644
--- a/src/app/error.tsx
+++ b/src/pages/500.js
@@ -1,5 +1,3 @@
-'use client';
-
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
@@ -7,18 +5,14 @@
import {Page} from 'components/Layout/Page';
import {MDXComponents} from 'components/MDX/MDXComponents';
import sidebarLearn from '../sidebarLearn.json';
-import {RouteItem} from 'components/Layout/getRouteMeta';
-import {generateMetadata as generateSeoMetadata} from 'utils/generateMetadata';
const {Intro, MaxWidth, p: P, a: A} = MDXComponents;
-export default function Error() {
+export default function NotFound() {
return (
@@ -35,11 +29,3 @@ export default function Error() {
);
}
-
-export async function generateMetadata({}: {}) {
- return generateSeoMetadata({
- title: 'Something Went Wrong',
- isHomePage: false,
- path: '/500',
- });
-}
diff --git a/src/pages/[[...markdownPath]].js b/src/pages/[[...markdownPath]].js
new file mode 100644
index 00000000000..bef4508df06
--- /dev/null
+++ b/src/pages/[[...markdownPath]].js
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ */
+
+import {Fragment, useMemo} from 'react';
+import {useRouter} from 'next/router';
+import {Page} from 'components/Layout/Page';
+import sidebarHome from '../sidebarHome.json';
+import sidebarLearn from '../sidebarLearn.json';
+import sidebarReference from '../sidebarReference.json';
+import sidebarCommunity from '../sidebarCommunity.json';
+import sidebarBlog from '../sidebarBlog.json';
+import {MDXComponents} from 'components/MDX/MDXComponents';
+import compileMDX from 'utils/compileMDX';
+import {generateRssFeed} from '../utils/rss';
+
+export default function Layout({content, toc, meta, languages}) {
+ const parsedContent = useMemo(
+ () => JSON.parse(content, reviveNodeOnClient),
+ [content]
+ );
+ const parsedToc = useMemo(() => JSON.parse(toc, reviveNodeOnClient), [toc]);
+ const section = useActiveSection();
+ let routeTree;
+ switch (section) {
+ case 'home':
+ case 'unknown':
+ routeTree = sidebarHome;
+ break;
+ case 'learn':
+ routeTree = sidebarLearn;
+ break;
+ case 'reference':
+ routeTree = sidebarReference;
+ break;
+ case 'community':
+ routeTree = sidebarCommunity;
+ break;
+ case 'blog':
+ routeTree = sidebarBlog;
+ break;
+ }
+ return (
+
+ {parsedContent}
+
+ );
+}
+
+function useActiveSection() {
+ const {asPath} = useRouter();
+ const cleanedPath = asPath.split(/[\?\#]/)[0];
+ if (cleanedPath === '/') {
+ return 'home';
+ } else if (cleanedPath.startsWith('/reference')) {
+ return 'reference';
+ } else if (asPath.startsWith('/learn')) {
+ return 'learn';
+ } else if (asPath.startsWith('/community')) {
+ return 'community';
+ } else if (asPath.startsWith('/blog')) {
+ return 'blog';
+ } else {
+ return 'unknown';
+ }
+}
+
+// Deserialize a client React tree from JSON.
+function reviveNodeOnClient(parentPropertyName, val) {
+ 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];
+ }
+ if (!Type) {
+ console.error('Unknown type: ' + Type);
+ Type = Fragment;
+ }
+ return ;
+ } else {
+ return val;
+ }
+}
+
+// Put MDX output into JSON for client.
+export async function getStaticProps(context) {
+ generateRssFeed();
+ const fs = require('fs');
+ const rootDir = process.cwd() + '/src/content/';
+
+ // Read MDX from the file.
+ let path = (context.params.markdownPath || []).join('/') || 'index';
+ let mdx;
+ try {
+ mdx = fs.readFileSync(rootDir + path + '.md', 'utf8');
+ } catch {
+ mdx = fs.readFileSync(rootDir + path + '/index.md', 'utf8');
+ }
+
+ const {toc, content, meta, languages} = await compileMDX(mdx, path, {});
+ return {
+ props: {
+ toc,
+ content,
+ meta,
+ languages,
+ },
+ };
+}
+
+// Collect all MDX files for static generation.
+export async function getStaticPaths() {
+ const {promisify} = require('util');
+ const {resolve} = require('path');
+ const fs = require('fs');
+ const readdir = promisify(fs.readdir);
+ const stat = promisify(fs.stat);
+ const rootDir = process.cwd() + '/src/content';
+
+ // Find all MD files recursively.
+ async function getFiles(dir) {
+ const subdirs = await readdir(dir);
+ const files = await Promise.all(
+ subdirs.map(async (subdir) => {
+ const res = resolve(dir, subdir);
+ return (await stat(res)).isDirectory()
+ ? getFiles(res)
+ : res.slice(rootDir.length + 1);
+ })
+ );
+ return (
+ files
+ .flat()
+ // ignores `errors/*.md`, they will be handled by `pages/errors/[errorCode].tsx`
+ .filter((file) => file.endsWith('.md') && !file.startsWith('errors/'))
+ );
+ }
+
+ // 'foo/bar/baz.md' -> ['foo', 'bar', 'baz']
+ // 'foo/bar/qux/index.md' -> ['foo', 'bar', 'qux']
+ function getSegments(file) {
+ let segments = file.slice(0, -3).replace(/\\/g, '/').split('/');
+ if (segments[segments.length - 1] === 'index') {
+ segments.pop();
+ }
+ return segments;
+ }
+
+ const files = await getFiles(rootDir);
+
+ const paths = files.map((file) => ({
+ params: {
+ markdownPath: getSegments(file),
+ // ^^^ CAREFUL HERE.
+ // If you rename markdownPath, update patches/next-remote-watch.patch too.
+ // Otherwise you'll break Fast Refresh for all MD files.
+ },
+ }));
+
+ return {
+ paths: paths,
+ fallback: false,
+ };
+}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
new file mode 100644
index 00000000000..5431f87cc9e
--- /dev/null
+++ b/src/pages/_app.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ */
+
+import {useEffect} from 'react';
+import {AppProps} from 'next/app';
+import {useRouter} from 'next/router';
+
+import '@docsearch/css';
+import '../styles/algolia.css';
+import '../styles/index.css';
+import '../styles/sandpack.css';
+
+if (typeof window !== 'undefined') {
+ const terminationEvent = 'onpagehide' in window ? 'pagehide' : 'unload';
+ window.addEventListener(terminationEvent, function () {
+ // @ts-ignore
+ gtag('event', 'timing', {
+ event_label: 'JS Dependencies',
+ event: 'unload',
+ });
+ });
+}
+
+export default function MyApp({Component, pageProps}: AppProps) {
+ const router = useRouter();
+
+ useEffect(() => {
+ // Taken from StackOverflow. Trying to detect both Safari desktop and mobile.
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
+ if (isSafari) {
+ // This is kind of a lie.
+ // We still rely on the manual Next.js scrollRestoration logic.
+ // However, we *also* don't want Safari grey screen during the back swipe gesture.
+ // Seems like it doesn't hurt to enable auto restore *and* Next.js logic at the same time.
+ history.scrollRestoration = 'auto';
+ } else {
+ // For other browsers, let Next.js set scrollRestoration to 'manual'.
+ // It seems to work better for Chrome and Firefox which don't animate the back swipe.
+ }
+ }, []);
+
+ useEffect(() => {
+ const handleRouteChange = (url: string) => {
+ const cleanedUrl = url.split(/[\?\#]/)[0];
+ // @ts-ignore
+ gtag('event', 'pageview', {
+ event_label: cleanedUrl,
+ });
+ };
+ router.events.on('routeChangeComplete', handleRouteChange);
+ return () => {
+ router.events.off('routeChangeComplete', handleRouteChange);
+ };
+ }, [router.events]);
+
+ return ;
+}
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
new file mode 100644
index 00000000000..6849df35d6a
--- /dev/null
+++ b/src/pages/_document.tsx
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ */
+
+import {Html, Head, Main, NextScript} from 'next/document';
+import {siteConfig} from '../siteConfig';
+
+const MyDocument = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default MyDocument;
diff --git a/src/pages/errors/[errorCode].tsx b/src/pages/errors/[errorCode].tsx
new file mode 100644
index 00000000000..de9eab5bb56
--- /dev/null
+++ b/src/pages/errors/[errorCode].tsx
@@ -0,0 +1,153 @@
+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.
+
+
+ */}
+
+
+ );
+}
+
+// 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;
+ }
+}
+
+/**
+ * Next.js Page Router doesn't have a way to cache specific data fetching request.
+ * But since Next.js uses limited number of workers, keep "cachedErrorCodes" as a
+ * module level memory cache can reduce the number of requests down to once per worker.
+ *
+ * TODO: use `next/unstable_cache` when migrating to Next.js App Router
+ */
+let cachedErrorCodes: Record | null = null;
+
+export const getStaticProps: GetStaticProps = async ({
+ params,
+}) => {
+ const errorCodes: {[key: string]: string} = (cachedErrorCodes ||= await (
+ await fetch(
+ 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'
+ )
+ ).json());
+
+ const code = typeof params?.errorCode === 'string' ? params?.errorCode : null;
+ if (code && !errorCodes[code]) {
+ return {
+ notFound: true,
+ };
+ }
+
+ const fs = require('fs');
+ const rootDir = process.cwd() + '/src/content/errors';
+
+ // Read MDX from the file.
+ let path = params?.errorCode || 'index';
+ let mdx;
+ try {
+ mdx = fs.readFileSync(rootDir + '/' + path + '.md', 'utf8');
+ } catch {
+ // if [errorCode].md is not found, fallback to generic.md
+ mdx = fs.readFileSync(rootDir + '/generic.md', 'utf8');
+ }
+
+ const {content, toc, meta} = await compileMDX(mdx, path, {code, errorCodes});
+
+ return {
+ props: {
+ content,
+ toc,
+ meta,
+ errorCode: code,
+ errorMessage: code ? errorCodes[code] : null,
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ /**
+ * Fetch error codes from GitHub
+ */
+ const errorCodes = (cachedErrorCodes ||= await (
+ await fetch(
+ 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json'
+ )
+ ).json());
+
+ const paths = Object.keys(errorCodes).map((code) => ({
+ params: {
+ errorCode: code,
+ },
+ }));
+
+ return {
+ paths,
+ fallback: 'blocking',
+ };
+};
diff --git a/src/pages/errors/index.tsx b/src/pages/errors/index.tsx
new file mode 100644
index 00000000000..d7742f03a30
--- /dev/null
+++ b/src/pages/errors/index.tsx
@@ -0,0 +1,3 @@
+import ErrorDecoderPage from './[errorCode]';
+export default ErrorDecoderPage;
+export {getStaticProps} from './[errorCode]';
diff --git a/src/styles/index.css b/src/styles/index.css
index 28d8473ac5b..2811110920b 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -19,7 +19,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -27,7 +28,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_W_MdIt.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_W_MdIt.woff2')
+ format('woff2');
font-weight: 500;
font-style: italic;
font-display: swap;
@@ -35,7 +37,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_W_SBd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_W_SBd.woff2')
+ format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -43,7 +46,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_W_SBdIt.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_W_SBdIt.woff2')
+ format('woff2');
font-weight: 600;
font-style: italic;
font-display: swap;
@@ -51,7 +55,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -59,7 +64,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_W_BdIt.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_W_BdIt.woff2')
+ format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
@@ -67,7 +73,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_W_Rg.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_W_Rg.woff2')
+ format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -75,7 +82,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_W_It.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_W_It.woff2')
+ format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
@@ -83,7 +91,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -91,7 +100,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_W_MdIt.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_W_MdIt.woff2')
+ format('woff2');
font-weight: 500;
font-style: italic;
font-display: swap;
@@ -99,7 +109,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -107,7 +118,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_W_BdIt.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_W_BdIt.woff2')
+ format('woff2');
font-weight: 700;
font-style: italic;
font-display: swap;
@@ -117,7 +129,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Arbc_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Arbc_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -126,7 +139,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Arbc_W_SBd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Arbc_W_SBd.woff2')
+ format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -135,7 +149,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Arbc_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Arbc_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -144,7 +159,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Arbc_W_Rg.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Arbc_W_Rg.woff2')
+ format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -153,7 +169,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Arbc_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Arbc_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -162,7 +179,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Arbc_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Arbc_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -173,7 +191,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Cyrl_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Cyrl_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -182,7 +201,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Cyrl_W_SBd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Cyrl_W_SBd.woff2')
+ format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -191,7 +211,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Cyrl_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Cyrl_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -200,7 +221,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Cyrl_W_Rg.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Cyrl_W_Rg.woff2')
+ format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -209,7 +231,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Cyrl_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Cyrl_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -218,7 +241,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Cyrl_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Cyrl_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -229,7 +253,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Deva_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Deva_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -239,7 +264,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Deva_W_SBd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Deva_W_SBd.woff2')
+ format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -249,7 +275,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Deva_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Deva_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -259,7 +286,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Deva_W_Rg.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Deva_W_Rg.woff2')
+ format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -269,7 +297,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Deva_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Deva_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -279,7 +308,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Deva_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Deva_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -291,7 +321,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Viet_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Viet_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -300,7 +331,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Viet_W_SBd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Viet_W_SBd.woff2')
+ format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
@@ -309,7 +341,8 @@
@font-face {
font-family: 'Optimistic Display';
- src: url('/fonts/Optimistic_Display_Viet_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Display_Viet_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
@@ -318,7 +351,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Viet_W_Rg.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Viet_W_Rg.woff2')
+ format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
@@ -327,7 +361,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Viet_W_Md.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Viet_W_Md.woff2')
+ format('woff2');
font-weight: 500;
font-style: normal;
font-display: swap;
@@ -336,7 +371,8 @@
@font-face {
font-family: 'Optimistic Text';
- src: url('/fonts/Optimistic_Text_Viet_W_Bd.woff2') format('woff2');
+ src: url('https://react.dev/fonts/Optimistic_Text_Viet_W_Bd.woff2')
+ format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
diff --git a/src/utils/compileMDX.ts b/src/utils/compileMDX.ts
new file mode 100644
index 00000000000..be770c29afb
--- /dev/null
+++ b/src/utils/compileMDX.ts
@@ -0,0 +1,168 @@
+import {LanguageItem} from 'components/MDX/LanguagesContext';
+import {MDXComponents} from 'components/MDX/MDXComponents';
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~
+const DISK_CACHE_BREAKER = 10;
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+export default async function compileMDX(
+ mdx: string,
+ path: string | string[],
+ params: {[key: string]: any}
+): Promise<{content: string; toc: string; meta: any}> {
+ const fs = require('fs');
+ const {
+ prepareMDX,
+ PREPARE_MDX_CACHE_BREAKER,
+ } = require('../utils/prepareMDX');
+ const mdxComponentNames = Object.keys(MDXComponents);
+
+ // See if we have a cached output first.
+ const {FileStore, stableHash} = require('metro-cache');
+ const store = new FileStore({
+ root: process.cwd() + '/node_modules/.cache/react-docs-mdx/',
+ });
+ const hash = Buffer.from(
+ stableHash({
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // ~~~~ IMPORTANT: Everything that the code below may rely on.
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ mdx,
+ ...params,
+ mdxComponentNames,
+ DISK_CACHE_BREAKER,
+ PREPARE_MDX_CACHE_BREAKER,
+ lockfile: fs.readFileSync(process.cwd() + '/yarn.lock', 'utf8'),
+ })
+ );
+ const cached = await store.get(hash);
+ if (cached) {
+ console.log(
+ 'Reading compiled MDX for /' + path + ' from ./node_modules/.cache/'
+ );
+ return cached;
+ }
+ if (process.env.NODE_ENV === 'production') {
+ console.log(
+ 'Cache miss for MDX for /' + path + ' from ./node_modules/.cache/'
+ );
+ }
+
+ // If we don't add these fake imports, the MDX compiler
+ // will insert a bunch of opaque components we can't introspect.
+ // This will break the prepareMDX() call below.
+ let mdxWithFakeImports =
+ mdx +
+ '\n\n' +
+ mdxComponentNames
+ .map((key) => 'import ' + key + ' from "' + key + '";\n')
+ .join('\n');
+
+ // Turn the MDX we just read into some JS we can execute.
+ const {remarkPlugins} = require('../../plugins/markdownToHtml');
+ const {compile: compileMdx} = await import('@mdx-js/mdx');
+ const visit = (await import('unist-util-visit')).default;
+ const jsxCode = await compileMdx(mdxWithFakeImports, {
+ remarkPlugins: [
+ ...remarkPlugins,
+ (await import('remark-gfm')).default,
+ (await import('remark-frontmatter')).default,
+ ],
+ rehypePlugins: [
+ // Support stuff like ```js App.js {1-5} active by passing it through.
+ function rehypeMetaAsAttributes() {
+ return (tree) => {
+ visit(tree, 'element', (node) => {
+ if (
+ // @ts-expect-error -- tagName is a valid property
+ node.tagName === 'code' &&
+ node.data &&
+ node.data.meta
+ ) {
+ // @ts-expect-error -- properties is a valid property
+ node.properties.meta = node.data.meta;
+ }
+ });
+ };
+ },
+ ],
+ });
+ const {transform} = require('@babel/core');
+ const jsCode = await transform(jsxCode, {
+ plugins: ['@babel/plugin-transform-modules-commonjs'],
+ presets: ['@babel/preset-react'],
+ }).code;
+
+ // Prepare environment for MDX.
+ let fakeExports = {};
+ const fakeRequire = (name: string) => {
+ if (name === 'react/jsx-runtime') {
+ return require('react/jsx-runtime');
+ } else {
+ // For each fake MDX import, give back the string component name.
+ // It will get serialized later.
+ return name;
+ }
+ };
+ const evalJSCode = new Function('require', 'exports', jsCode);
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // THIS IS A BUILD-TIME EVAL. NEVER DO THIS WITH UNTRUSTED MDX (LIKE FROM CMS)!!!
+ // In this case it's okay because anyone who can edit our MDX can also edit this file.
+ evalJSCode(fakeRequire, fakeExports);
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ // @ts-expect-error -- default exports is existed after eval
+ const reactTree = fakeExports.default({});
+
+ // Pre-process MDX output and serialize it.
+ let {toc, children} = prepareMDX(reactTree.props.children);
+ if (path === 'index') {
+ toc = [];
+ }
+
+ // Parse Frontmatter headers from MDX.
+ const fm = require('gray-matter');
+ const meta = fm(mdx).data;
+
+ // Load the list of translated languages conditionally.
+ let languages: Array | null = null;
+ if (typeof path === 'string' && path.endsWith('/translations')) {
+ languages = await (
+ await fetch(
+ 'https://raw.githubusercontent.com/reactjs/translations.react.dev/main/langs/langs.json'
+ )
+ ).json(); // { code: string; name: string; enName: string}[]
+ }
+
+ const output = {
+ content: JSON.stringify(children, stringifyNodeOnServer),
+ toc: JSON.stringify(toc, stringifyNodeOnServer),
+ meta,
+ languages,
+ };
+
+ // Serialize a server React tree node to JSON.
+ function stringifyNodeOnServer(key: unknown, val: any) {
+ if (
+ val != null &&
+ val.$$typeof === Symbol.for('react.transitional.element')
+ ) {
+ // Remove fake MDX props.
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const {mdxType, originalType, parentName, ...cleanProps} = val.props;
+ return [
+ '$r',
+ typeof val.type === 'string' ? val.type : mdxType,
+ val.key,
+ cleanProps,
+ ];
+ } else {
+ return val;
+ }
+ }
+
+ // Cache it on the disk.
+ await store.set(hash, output);
+ return output;
+}
diff --git a/src/utils/generateMDX.tsx b/src/utils/generateMDX.tsx
deleted file mode 100644
index 4d24e5f7ac1..00000000000
--- a/src/utils/generateMDX.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import fs from 'fs';
-import {FileStore, stableHash} from 'metro-cache';
-import grayMatter from 'gray-matter';
-import {compile, run} from '@mdx-js/mdx';
-import * as runtime from 'react/jsx-runtime';
-import {remarkPlugins} from '../../plugins/markdownToHtml';
-import remarkGfm from 'remark-gfm';
-import remarkFrontmatter from 'remark-frontmatter';
-import {MDXComponents, MDXComponentsToc} from '../components/MDX/MDXComponents';
-import {MaxWidthWrapperPlugin} from './mdx/MaxWidthWrapperPlugin';
-import {ExtractedTOC, TOCExtractorPlugin} from './mdx/TOCExtractorPlugin';
-import {MetaAttributesPlugin} from './mdx/MetaAttributesPlugin';
-
-const DISK_CACHE_BREAKER = 13;
-const CACHE_PATH = `${process.cwd()}/node_modules/.cache/react-docs-mdx/`;
-const LOCKFILE_PATH = `${process.cwd()}/yarn.lock`;
-
-type Params = {[key: string]: any};
-type MDXResult = {
- content: React.ReactNode;
- toc: ExtractedTOC[];
- meta: any;
-};
-
-type CachedResult = {
- code: string;
- toc: ExtractedTOC[];
- meta: any;
-};
-
-async function readFromCache(
- store: FileStore,
- hash: Buffer,
- path: string | string[]
-): Promise {
- try {
- const cached = await store.get(hash);
- if (cached) {
- return JSON.parse(cached.toString());
- }
- } catch (error) {
- console.warn(`Cache read failed for /${path}:`, error);
- }
- return null;
-}
-
-async function writeToCache(
- store: FileStore,
- hash: Buffer,
- result: CachedResult,
- path: string | string[]
-): Promise {
- try {
- await store.set(hash, Buffer.from(JSON.stringify(result)));
- } catch (error) {
- console.warn(`Cache write failed for /${path}:`, error);
- }
-}
-
-export async function generateMDX(
- mdx: string,
- path: string | string[],
- params: Params
-): Promise {
- const store = new FileStore({root: CACHE_PATH});
- const hash = Buffer.from(
- stableHash({
- ...params,
- mdx,
- mdxComponentNames: Object.keys(MDXComponents),
- DISK_CACHE_BREAKER,
- lockfile: fs.readFileSync(LOCKFILE_PATH, 'utf8'),
- })
- );
-
- const cachedResult = await readFromCache(store, hash, path);
- if (cachedResult) {
- const {code, meta, toc} = cachedResult;
- const {default: MDXContent} = await run(code, {
- ...runtime,
- baseUrl: import.meta.url,
- });
- const tocWithMDX = await getTransformedToc(toc);
- return {
- content: ,
- toc: tocWithMDX,
- meta,
- };
- }
-
- const compiled = await compile(mdx, {
- remarkPlugins: [
- ...remarkPlugins,
- remarkGfm,
- remarkFrontmatter,
- MaxWidthWrapperPlugin,
- TOCExtractorPlugin,
- ],
- rehypePlugins: [MetaAttributesPlugin],
- outputFormat: 'function-body',
- });
-
- const {data: meta} = grayMatter(mdx);
- const toc = compiled.data.toc as ExtractedTOC[];
- const result: CachedResult = {
- code: String(compiled),
- toc,
- meta,
- };
-
- await writeToCache(store, hash, result, path);
-
- const tocWithMDX = await getTransformedToc(toc);
-
- const {default: MDXContent} = await run(result.code, {
- ...runtime,
- baseUrl: import.meta.url,
- });
-
- return {
- content: ,
- toc: tocWithMDX,
- meta: result.meta,
- };
-}
-
-async function getTransformedToc(toc: ExtractedTOC[]): Promise {
- return await Promise.all(
- toc.map(async (item) => {
- if (typeof item.node !== 'string') {
- return item;
- }
-
- const compiled = await compile(item.node, {
- outputFormat: 'function-body',
- });
-
- const {default: MDXContent} = await run(compiled, {
- ...runtime,
- baseUrl: import.meta.url,
- });
-
- item.node = ;
-
- return item;
- })
- );
-}
diff --git a/src/utils/generateMetadata.ts b/src/utils/generateMetadata.ts
deleted file mode 100644
index e45b10daba9..00000000000
--- a/src/utils/generateMetadata.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import {Metadata} from 'next';
-import {siteConfig} from '../siteConfig';
-import {finishedTranslations} from 'utils/finishedTranslations';
-
-export interface SeoProps {
- title: string;
- titleForTitleTag?: string;
- description?: string;
- image?: string;
- isHomePage: boolean;
- searchOrder?: number;
- path: string;
-}
-
-function getDomain(languageCode: string): string {
- const subdomain = languageCode === 'en' ? '' : languageCode + '.';
- return subdomain + 'react.dev';
-}
-export function generateMetadata({
- title,
- titleForTitleTag,
- image,
- isHomePage,
- description: customDescription,
- searchOrder,
- path,
-}: SeoProps): Metadata {
- const siteDomain = getDomain(siteConfig.languageCode);
- const canonicalUrl = `https://${siteDomain}${path.split(/[\?\#]/)[0]}`;
-
- const pageTitle =
- (titleForTitleTag ?? title) + (isHomePage ? '' : ' – React');
- const twitterTitle = pageTitle.replace(/[<>]/g, '');
-
- const description = isHomePage
- ? 'React is the library for web and native user interfaces...'
- : customDescription ?? 'The library for web and native user interfaces';
-
- const alternateLanguages = {
- 'x-default': canonicalUrl.replace(siteDomain, getDomain('en')),
- ...Object.fromEntries(
- finishedTranslations.map((languageCode) => [
- languageCode,
- canonicalUrl.replace(siteDomain, getDomain(languageCode)),
- ])
- ),
- };
-
- return {
- title: pageTitle,
- description: isHomePage ? description : undefined,
- alternates: {
- canonical: canonicalUrl,
- languages: alternateLanguages,
- },
- openGraph: {
- title: pageTitle,
- description,
- url: canonicalUrl,
- images: [
- {url: `https://${siteDomain}${image || '/images/og-default.png'}`},
- ],
- },
- twitter: {
- title: twitterTitle,
- description,
- images: [`https://${siteDomain}${image || '/images/og-default.png'}`],
- },
- other: {
- ...(searchOrder != null && {
- 'algolia-search-order': searchOrder.toString(),
- }),
- },
- };
-}
diff --git a/src/utils/mdx/MaxWidthWrapperPlugin.ts b/src/utils/mdx/MaxWidthWrapperPlugin.ts
deleted file mode 100644
index 22f1a022f3a..00000000000
--- a/src/utils/mdx/MaxWidthWrapperPlugin.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import {Root, RootContent} from 'mdast';
-import {u} from 'unist-builder';
-
-export function MaxWidthWrapperPlugin() {
- const fullWidthTypes = [
- 'Sandpack',
- 'FullWidth',
- 'Illustration',
- 'IllustrationBlock',
- 'Challenges',
- 'Recipes',
- ];
-
- return (tree: Root) => {
- const newChildren: RootContent[] = [];
- let wrapQueue: RootContent[] = [];
-
- function flushWrapper() {
- if (wrapQueue.length > 0) {
- newChildren.push(
- u('mdxJsxFlowElement', {
- name: 'MaxWidth',
- attributes: [],
- children: wrapQueue,
- } as any)
- );
- wrapQueue = [];
- }
- }
-
- for (const node of tree.children) {
- if (
- // @ts-expect-error
- fullWidthTypes.includes(node.name) &&
- // @ts-expect-error: mdxJsxFlowElement
- node.type === 'mdxJsxFlowElement'
- ) {
- flushWrapper();
- newChildren.push(node);
- } else {
- wrapQueue.push(node);
- }
- }
- flushWrapper();
-
- tree.children = newChildren;
- };
-}
diff --git a/src/utils/mdx/MetaAttributesPlugin.ts b/src/utils/mdx/MetaAttributesPlugin.ts
deleted file mode 100644
index f708fd36899..00000000000
--- a/src/utils/mdx/MetaAttributesPlugin.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {Root} from 'mdast';
-import visit from 'unist-util-visit';
-
-// Support stuff like ```js App.js {1-5} active by passing it through.
-export function MetaAttributesPlugin() {
- return (tree: Root) => {
- visit(tree, 'element', (node) => {
- if (
- // @ts-expect-error -- tagName is a valid property
- node.tagName === 'code' &&
- node.data &&
- node.data.meta
- ) {
- // @ts-expect-error -- properties is a valid property
- node.properties.meta = node.data.meta;
- }
- });
- };
-}
diff --git a/src/utils/mdx/TOCExtractorPlugin.ts b/src/utils/mdx/TOCExtractorPlugin.ts
deleted file mode 100644
index dd8e4d64401..00000000000
--- a/src/utils/mdx/TOCExtractorPlugin.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import visit from 'unist-util-visit';
-import {Node} from 'unist';
-
-interface HeadingNode extends Node {
- type: 'heading';
- depth: number;
- children: Array<{
- type: string;
- value?: string;
- }>;
- data?: {
- hProperties?: {
- id?: string;
- };
- };
-}
-
-interface MDXJsxFlowElementNode extends Node {
- type: 'mdxJsxFlowElement';
- name: string;
- attributes?: Array<{
- name: string;
- value?: string;
- }>;
-}
-
-export interface ExtractedTOC {
- url: string;
- node: string | React.ReactNode;
- depth: number;
-}
-
-interface PluginOptions {
- maxDepth?: number;
-}
-
-export function TOCExtractorPlugin({maxDepth = 3}: PluginOptions = {}) {
- return (tree: Node, file: any) => {
- const toc: ExtractedTOC[] = [];
-
- visit(tree, (node: Node) => {
- // Standard markdown headings
- if (node.type === 'heading') {
- const headingNode = node as HeadingNode;
- if (headingNode.depth > maxDepth) {
- return;
- }
-
- const mdxSource = file.value
- .slice(
- // @ts-ignore
- node.children[0].position!.start.offset,
- // @ts-ignore
- node.children[0].position!.end.offset
- )
- .trim();
-
- const text = headingNode.children
- .filter((child) => child.type === 'text' && child.value)
- .map((child) => child.value!)
- .join('');
-
- const id =
- headingNode.data?.hProperties?.id ||
- text.toLowerCase().replace(/\s+/g, '-');
-
- toc.push({
- depth: headingNode.depth,
- node: mdxSource,
- url: `#${id}`,
- });
- }
-
- // MDX custom components (e.g., )
- else if (node.type === 'mdxJsxFlowElement') {
- const mdxNode = node as MDXJsxFlowElementNode;
- switch (mdxNode.name) {
- case 'TeamMember': {
- let name = 'Team Member';
- let permalink = 'team-member';
-
- if (Array.isArray(mdxNode.attributes)) {
- for (const attr of mdxNode.attributes) {
- if (attr.name === 'name' && attr.value) {
- name = attr.value;
- } else if (attr.name === 'permalink' && attr.value) {
- permalink = attr.value;
- }
- }
- }
-
- toc.push({
- url: `#${permalink}`,
- depth: 3,
- node: name,
- });
- break;
- }
-
- case 'Challenges':
- toc.push({
- url: '#challenges',
- depth: 2,
- node: 'Challenges',
- });
- break;
- case 'Recap':
- toc.push({
- url: '#recap',
- depth: 2,
- node: 'Recap',
- });
- break;
- default:
- break;
- }
- }
- });
-
- // Insert "Overview" at the top if there's at least one heading
- if (toc.length > 0) {
- toc.unshift({
- url: '#',
- node: 'Overview',
- depth: 2,
- });
- }
-
- file.data.toc = toc;
- };
-}
diff --git a/src/utils/prepareMDX.js b/src/utils/prepareMDX.js
new file mode 100644
index 00000000000..20a22577dc4
--- /dev/null
+++ b/src/utils/prepareMDX.js
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ */
+
+import {Children} from 'react';
+
+// TODO: This logic could be in MDX plugins instead.
+
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export const PREPARE_MDX_CACHE_BREAKER = 3;
+// !!! IMPORTANT !!! Bump this if you change any logic.
+// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+export function prepareMDX(rawChildren) {
+ const toc = getTableOfContents(rawChildren, /* depth */ 10);
+ const children = wrapChildrenInMaxWidthContainers(rawChildren);
+ return {toc, children};
+}
+
+function wrapChildrenInMaxWidthContainers(children) {
+ // Auto-wrap everything except a few types into
+ // wrappers. Keep reusing the same
+ // wrapper as long as we can until we meet
+ // a full-width section which interrupts it.
+ let fullWidthTypes = [
+ 'Sandpack',
+ 'FullWidth',
+ 'Illustration',
+ 'IllustrationBlock',
+ 'Challenges',
+ 'Recipes',
+ ];
+ let wrapQueue = [];
+ let finalChildren = [];
+ function flushWrapper(key) {
+ if (wrapQueue.length > 0) {
+ const Wrapper = 'MaxWidth';
+ finalChildren.push({wrapQueue} );
+ wrapQueue = [];
+ }
+ }
+ function handleChild(child, key) {
+ if (child == null) {
+ return;
+ }
+ if (typeof child !== 'object') {
+ wrapQueue.push(child);
+ return;
+ }
+ if (fullWidthTypes.includes(child.type)) {
+ flushWrapper(key);
+ finalChildren.push(child);
+ } else {
+ wrapQueue.push(child);
+ }
+ }
+ Children.forEach(children, handleChild);
+ flushWrapper('last');
+ return finalChildren;
+}
+
+function getTableOfContents(children, depth) {
+ const anchors = [];
+ extractHeaders(children, depth, anchors);
+ if (anchors.length > 0) {
+ anchors.unshift({
+ url: '#',
+ text: 'Overview',
+ depth: 2,
+ });
+ }
+ return anchors;
+}
+
+const headerTypes = new Set([
+ 'h1',
+ 'h2',
+ 'h3',
+ 'Challenges',
+ 'Recap',
+ 'TeamMember',
+]);
+function extractHeaders(children, depth, out) {
+ for (const child of Children.toArray(children)) {
+ if (child.type && headerTypes.has(child.type)) {
+ let header;
+ if (child.type === 'Challenges') {
+ header = {
+ url: '#challenges',
+ depth: 2,
+ text: 'Challenges',
+ };
+ } else if (child.type === 'Recap') {
+ header = {
+ url: '#recap',
+ depth: 2,
+ text: 'Recap',
+ };
+ } else if (child.type === 'TeamMember') {
+ header = {
+ url: '#' + child.props.permalink,
+ depth: 3,
+ text: child.props.name,
+ };
+ } else {
+ header = {
+ url: '#' + child.props.id,
+ depth: (child.type && parseInt(child.type.replace('h', ''), 0)) ?? 0,
+ text: child.props.children,
+ };
+ }
+ out.push(header);
+ } else if (child.children && depth > 0) {
+ extractHeaders(child.children, depth - 1, out);
+ }
+ }
+}
diff --git a/src/utils/rss.js b/src/utils/rss.js
new file mode 100644
index 00000000000..29e5511ea5b
--- /dev/null
+++ b/src/utils/rss.js
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ */
+const Feed = require('rss');
+const fs = require('fs');
+const path = require('path');
+const matter = require('gray-matter');
+
+const getAllFiles = function (dirPath, arrayOfFiles) {
+ const files = fs.readdirSync(dirPath);
+
+ arrayOfFiles = arrayOfFiles || [];
+
+ files.forEach(function (file) {
+ if (fs.statSync(dirPath + '/' + file).isDirectory()) {
+ arrayOfFiles = getAllFiles(dirPath + '/' + file, arrayOfFiles);
+ } else {
+ arrayOfFiles.push(path.join(dirPath, '/', file));
+ }
+ });
+
+ return arrayOfFiles;
+};
+
+exports.generateRssFeed = function () {
+ const feed = new Feed({
+ title: 'React Blog',
+ description:
+ 'This blog is the official source for the updates from the React team. Anything important, including release notes or deprecation notices, will be posted here first.',
+ feed_url: 'https://react.dev/rss.xml',
+ site_url: 'https://react.dev/',
+ language: 'en',
+ favicon: 'https://react.dev/favicon.ico',
+ pubDate: new Date(),
+ generator: 'react.dev rss module',
+ });
+
+ const dirPath = path.join(process.cwd(), 'src/content/blog');
+ const filesByOldest = getAllFiles(dirPath);
+ const files = filesByOldest.reverse();
+
+ for (const filePath of files) {
+ const id = path.basename(filePath);
+ if (id !== 'index.md') {
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const {data} = matter(content);
+ const slug = filePath.split('/').slice(-4).join('/').replace('.md', '');
+
+ if (data.title == null || data.title.trim() === '') {
+ throw new Error(
+ `${id}: Blog posts must include a title in the metadata, for RSS feeds`
+ );
+ }
+ if (data.author == null || data.author.trim() === '') {
+ throw new Error(
+ `${id}: Blog posts must include an author in the metadata, for RSS feeds`
+ );
+ }
+ if (data.date == null || data.date.trim() === '') {
+ throw new Error(
+ `${id}: Blog posts must include a date in the metadata, for RSS feeds`
+ );
+ }
+ if (data.description == null || data.description.trim() === '') {
+ throw new Error(
+ `${id}: Blog posts must include a description in the metadata, for RSS feeds`
+ );
+ }
+
+ feed.item({
+ id,
+ title: data.title,
+ author: data.author || '',
+ date: new Date(data.date),
+ url: `https://react.dev/blog/${slug}`,
+ description: data.description,
+ });
+ }
+ }
+
+ fs.writeFileSync('./public/rss.xml', feed.xml({indent: true}));
+};
diff --git a/tailwind.config.js b/tailwind.config.js
index f709aba7d8f..f31a2451677 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -9,7 +9,6 @@ module.exports = {
content: [
'./src/components/**/*.{js,ts,jsx,tsx}',
'./src/pages/**/*.{js,ts,jsx,tsx}',
- './src/app/**/*.{js,ts,jsx,tsx}',
'./src/styles/**/*.{js,ts,jsx,tsx}',
],
darkMode: 'class',
diff --git a/tsconfig.json b/tsconfig.json
index 983db114c27..440b38510fa 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,12 +1,16 @@
{
"compilerOptions": {
"target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noImplicitAny": true,
- "noImplicitReturns": false,
+ "noImplicitReturns": true,
"noImplicitThis": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
@@ -18,20 +22,14 @@
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "src",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ]
+ "incremental": true
},
"include": [
"next-env.d.ts",
"src/**/*.ts",
- "src/**/*.tsx",
- ".next/types/**/*.ts",
- "types.d.ts",
- "src/components/UwuScript.jsx"
+ "src/**/*.tsx"
],
- "exclude": ["node_modules"]
+ "exclude": [
+ "node_modules"
+ ]
}
diff --git a/types.d.ts b/types.d.ts
deleted file mode 100644
index 72e9cbfa1db..00000000000
--- a/types.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-declare module 'metro-cache' {
- export class FileStore {
- constructor(options: {root: string});
- get(hash: Buffer): Promise;
- set(hash: Buffer, value: Buffer): Promise;
- }
- export function stableHash(obj: any): string;
-}
diff --git a/yarn.lock b/yarn.lock
index 2e33a440cde..a118cbeda5f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1225,10 +1225,10 @@
unist-util-visit "^4.0.0"
vfile "^5.0.0"
-"@next/env@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/env/-/env-15.2.0-canary.33.tgz#5cd769cca64e09564e80817b6b6aeaba472ea9e9"
- integrity sha512-y3EPM+JYKU8t2K+i6bc0QrotEZVGpqu9eVjprj4cfS8QZyZcL54s+W9aGB0TBuGavU9tQdZ50W186+toeMV+hw==
+"@next/env@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-15.1.0.tgz#35b00a5f60ff10dc275182928c325d25c29379ae"
+ integrity sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w==
"@next/eslint-plugin-next@12.0.3":
version "12.0.3"
@@ -1237,45 +1237,45 @@
dependencies:
glob "7.1.7"
-"@next/swc-darwin-arm64@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.0-canary.33.tgz#946b9fa766575baf7577ea21b70105d5fefc86ed"
- integrity sha512-+fCdK2KmR6lWoCTk1fSd5pvbiLZHfZF+D/Xdz3xrXw+pbnBtXWLKQrPT0bCtDseMxD31qcOywq5mAApvI3EGpA==
-
-"@next/swc-darwin-x64@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.0-canary.33.tgz#8878b319cd3d3f90648097d5d76b54b1db02d4f0"
- integrity sha512-GrrU+tSmeBRow+7bnn7i5M96g3tc28hPH5t5Y65qUXGmmrZwGZN1e1d+8QbXPdAGkvjEPcOkUNQuQVpp1qpYPA==
-
-"@next/swc-linux-arm64-gnu@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.0-canary.33.tgz#0bc4f61ccbb4e7424f9acb215f0d7e0674456f5e"
- integrity sha512-8RnGxnUpASHoUf6aHUifmZom5b4Ow5nTdCib/CNYXZ6VLuL5ocvmr+DXs/SKzi9h8OHR7JkLwKXHCcF8WyscSg==
-
-"@next/swc-linux-arm64-musl@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.0-canary.33.tgz#15fa3982f382dc6c51ac17d47111b6525deb7f94"
- integrity sha512-COyE0LzMuLBZSR+Z/TOGilyJPdwSU588Vt0+o8GoECkoDEnjyuO2s2nHa2kDAcEfUEPkhlo0tErU3mF+8AVOTQ==
-
-"@next/swc-linux-x64-gnu@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.0-canary.33.tgz#94d06dcb6f116c470d9d6a7ddda0a29ab23ce80b"
- integrity sha512-3Y9lqJs+ftU9jgbLdCtvAvF8MNJsJYGMH7icb8QMs1+yOyHHbmwkZoElKdjwfUWzQ2sX28ywp73GWq4HbrsoUg==
-
-"@next/swc-linux-x64-musl@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.0-canary.33.tgz#daaa3610648afe7440f3672d11352ecd5109d2df"
- integrity sha512-FS9iA+RkZlhdWGQEKtsplVBXIYZJUn5nsRB+1UY46b3uaL6dDypu13ODaSwYuAwXGgkrZBVF9AFO3y4biBnPlA==
-
-"@next/swc-win32-arm64-msvc@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.0-canary.33.tgz#e704113f42449f30ee0d81337ff9c27177cad75c"
- integrity sha512-Ji9CtBbUx06qvvN/rPohJN2FEFGsUv26F50f2nMRYRwrq3POXDjloGOiRocrjU0ty/cUzCz71qTUfKdmv/ajmg==
-
-"@next/swc-win32-x64-msvc@15.2.0-canary.33":
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.0-canary.33.tgz#d719ea46ba61f3a97678dd0b290147488ea974ba"
- integrity sha512-hjdbGnkwIZ8zN2vlS6lNsEJO37HRtcEGimzfkruBMsi/DwJBqkJvZbNC/XCJy3HFcU58igncqV52p1IPjmAJAw==
+"@next/swc-darwin-arm64@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.0.tgz#30cb89220e719244c9fa7391641e515a078ade46"
+ integrity sha512-ZU8d7xxpX14uIaFC3nsr4L++5ZS/AkWDm1PzPO6gD9xWhFkOj2hzSbSIxoncsnlJXB1CbLOfGVN4Zk9tg83PUw==
+
+"@next/swc-darwin-x64@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.0.tgz#c24c4f5d1016dd161da32049305b0ddddfc80951"
+ integrity sha512-DQ3RiUoW2XC9FcSM4ffpfndq1EsLV0fj0/UY33i7eklW5akPUCo6OX2qkcLXZ3jyPdo4sf2flwAED3AAq3Om2Q==
+
+"@next/swc-linux-arm64-gnu@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.0.tgz#08ed540ecdac74426a624cc7d736dc709244b004"
+ integrity sha512-M+vhTovRS2F//LMx9KtxbkWk627l5Q7AqXWWWrfIzNIaUFiz2/NkOFkxCFyNyGACi5YbA8aekzCLtbDyfF/v5Q==
+
+"@next/swc-linux-arm64-musl@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.0.tgz#dfddbd40087d018266aa92515ec5b3e251efa6dd"
+ integrity sha512-Qn6vOuwaTCx3pNwygpSGtdIu0TfS1KiaYLYXLH5zq1scoTXdwYfdZtwvJTpB1WrLgiQE2Ne2kt8MZok3HlFqmg==
+
+"@next/swc-linux-x64-gnu@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.0.tgz#a7b5373a1b28c0acecbc826a3790139fc0d899e5"
+ integrity sha512-yeNh9ofMqzOZ5yTOk+2rwncBzucc6a1lyqtg8xZv0rH5znyjxHOWsoUtSq4cUTeeBIiXXX51QOOe+VoCjdXJRw==
+
+"@next/swc-linux-x64-musl@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.0.tgz#b82a29903ee2f12d8b64163ddf208ac519869550"
+ integrity sha512-t9IfNkHQs/uKgPoyEtU912MG6a1j7Had37cSUyLTKx9MnUpjj+ZDKw9OyqTI9OwIIv0wmkr1pkZy+3T5pxhJPg==
+
+"@next/swc-win32-arm64-msvc@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.0.tgz#98deae6cb1fccfb6a600e9faa6aa714402a9ab9a"
+ integrity sha512-WEAoHyG14t5sTavZa1c6BnOIEukll9iqFRTavqRVPfYmfegOAd5MaZfXgOGG6kGo1RduyGdTHD4+YZQSdsNZXg==
+
+"@next/swc-win32-x64-msvc@15.1.0":
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.0.tgz#4b04a6a667c41fecdc63db57dd71ca7e84d0946b"
+ integrity sha512-J1YdKuJv9xcixzXR24Dv+4SaDKc2jj31IVUEMdO5xJivMTXuE6MAdIi4qPjSymHuFG8O5wbfWKnhJUcHHpj5CA==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@@ -1656,13 +1656,6 @@
dependencies:
"@types/unist" "*"
-"@types/mdast@^4.0.4":
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6"
- integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==
- dependencies:
- "@types/unist" "*"
-
"@types/mdurl@^1.0.0":
version "1.0.2"
resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz"
@@ -1731,11 +1724,6 @@
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
-"@types/unist@^3.0.0":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
- integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
-
"@typescript-eslint/eslint-plugin@^5.36.2":
version "5.36.2"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz"
@@ -2435,10 +2423,15 @@ camelcase-css@^2.0.1:
resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
-caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001688:
- version "1.0.30001695"
- resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz"
- integrity sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==
+caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001579:
+ version "1.0.30001636"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78"
+ integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==
+
+caniuse-lite@^1.0.30001688:
+ version "1.0.30001692"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz#4585729d95e6b95be5b439da6ab55250cd125bf9"
+ integrity sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==
ccount@^1.0.0:
version "1.1.0"
@@ -2522,13 +2515,6 @@ chokidar@^3.4.0, chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
-chokidar@^4.0.3:
- version "4.0.3"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
- integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
- dependencies:
- readdirp "^4.0.1"
-
ci-info@^3.2.0:
version "3.3.0"
resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz"
@@ -5812,12 +5798,12 @@ next-tick@^1.1.0:
resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz"
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
-next@^15.2.0-canary.33:
- version "15.2.0-canary.33"
- resolved "https://registry.yarnpkg.com/next/-/next-15.2.0-canary.33.tgz#a866edb669618a5a7aac5035895255eb0c9fb189"
- integrity sha512-WF8QLeYkakuYwksdWY/F+Bi8tNJfIbiSYk9hCmldn9sNp1lU3lqI1hrW1ynbcMSaXC+qQEr7yol2OdvVZ4nZYQ==
+next@15.1.0:
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/next/-/next-15.1.0.tgz#be847cf67ac94ae23b57f3ea6d10642f3fc1ad69"
+ integrity sha512-QKhzt6Y8rgLNlj30izdMbxAwjHMFANnLwDwZ+WQh5sMhyt4lEBqDK9QpvWHtIM4rINKPoJ8aiRZKg5ULSybVHw==
dependencies:
- "@next/env" "15.2.0-canary.33"
+ "@next/env" "15.1.0"
"@swc/counter" "0.1.3"
"@swc/helpers" "0.5.15"
busboy "1.6.0"
@@ -5825,14 +5811,14 @@ next@^15.2.0-canary.33:
postcss "8.4.31"
styled-jsx "5.1.6"
optionalDependencies:
- "@next/swc-darwin-arm64" "15.2.0-canary.33"
- "@next/swc-darwin-x64" "15.2.0-canary.33"
- "@next/swc-linux-arm64-gnu" "15.2.0-canary.33"
- "@next/swc-linux-arm64-musl" "15.2.0-canary.33"
- "@next/swc-linux-x64-gnu" "15.2.0-canary.33"
- "@next/swc-linux-x64-musl" "15.2.0-canary.33"
- "@next/swc-win32-arm64-msvc" "15.2.0-canary.33"
- "@next/swc-win32-x64-msvc" "15.2.0-canary.33"
+ "@next/swc-darwin-arm64" "15.1.0"
+ "@next/swc-darwin-x64" "15.1.0"
+ "@next/swc-linux-arm64-gnu" "15.1.0"
+ "@next/swc-linux-arm64-musl" "15.1.0"
+ "@next/swc-linux-x64-gnu" "15.1.0"
+ "@next/swc-linux-x64-musl" "15.1.0"
+ "@next/swc-win32-arm64-msvc" "15.1.0"
+ "@next/swc-win32-x64-msvc" "15.1.0"
sharp "^0.33.5"
nice-try@^1.0.4:
@@ -6814,11 +6800,6 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2"
path-type "^3.0.0"
-readdirp@^4.0.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.1.tgz#bd115327129672dc47f87408f05df9bd9ca3ef55"
- integrity sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==
-
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
@@ -8106,13 +8087,6 @@ unist-builder@^3.0.0:
dependencies:
"@types/unist" "^2.0.0"
-unist-builder@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-4.0.0.tgz#817b326c015a6f9f5e92bb55b8e8bc5e578fe243"
- integrity sha512-wmRFnH+BLpZnTKpc5L7O67Kac89s9HMrtELpnNaE6TAobq5DTZZs5YaTQfAZBA9bFPECx2uVAPO31c+GVug8mg==
- dependencies:
- "@types/unist" "^3.0.0"
-
unist-util-generated@^1.0.0:
version "1.1.6"
resolved "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz"