From cc34811d47d4e406a5af0dbd1f341c9189a56437 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Sat, 3 Jan 2026 18:10:09 +0800 Subject: [PATCH 1/7] Refactor: make `compileMDX` works for both App/Pages Router --- next.config.js | 5 +++++ src/utils/compileMDX.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/next.config.js b/next.config.js index fe88a09a0c4..d6ff921c50f 100644 --- a/next.config.js +++ b/next.config.js @@ -72,6 +72,11 @@ const nextConfig = { return config; }, + serverExternalPackages: [ + '@babel/core', + '@babel/preset-react', + '@babel/plugin-transform-modules-commonjs', + ], }; module.exports = nextConfig; diff --git a/src/utils/compileMDX.ts b/src/utils/compileMDX.ts index c312f03fe96..8b0fe816c50 100644 --- a/src/utils/compileMDX.ts +++ b/src/utils/compileMDX.ts @@ -96,9 +96,13 @@ export default async function compileMDX( ], }); const {transform} = require('@babel/core'); + + const babelPluginTransformModulesCommonjs = require('@babel/plugin-transform-modules-commonjs'); + const babelPresetReact = require('@babel/preset-react'); + const jsCode = await transform(jsxCode, { - plugins: ['@babel/plugin-transform-modules-commonjs'], - presets: ['@babel/preset-react'], + plugins: [babelPluginTransformModulesCommonjs], + presets: [babelPresetReact], }).code; // Prepare environment for MDX. From 8f5a12011b06b8053ae13e310adbb63c542fec67 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Sat, 3 Jan 2026 18:11:12 +0800 Subject: [PATCH 2/7] Mark necessary MDX components as Client Components --- src/components/Layout/HomeContent.js | 2 + src/components/MDX/Challenges/Challenges.tsx | 2 +- src/components/MDX/Challenges/index.tsx | 2 + src/components/MDX/ErrorDecoder.tsx | 4 +- .../MDX/Illustration/AuthorCredit.tsx | 28 +++ .../MDX/Illustration/Illustration.tsx | 40 ++++ .../MDX/Illustration/IllustrationBlock.tsx | 56 ++++++ .../MDX/Illustration/IllustrationContext.ts | 9 + src/components/MDX/LanguageList.tsx | 39 ++++ src/components/MDX/LanguagesContext.tsx | 2 + src/components/MDX/List.tsx | 9 + src/components/MDX/MDXComponents.tsx | 173 +----------------- src/components/MDX/Sandpack/CustomPreset.tsx | 2 + src/components/MDX/TerminalBlock.tsx | 2 + src/components/MDX/TocContext.tsx | 2 + 15 files changed, 202 insertions(+), 170 deletions(-) create mode 100644 src/components/MDX/Illustration/AuthorCredit.tsx create mode 100644 src/components/MDX/Illustration/Illustration.tsx create mode 100644 src/components/MDX/Illustration/IllustrationBlock.tsx create mode 100644 src/components/MDX/Illustration/IllustrationContext.ts create mode 100644 src/components/MDX/LanguageList.tsx create mode 100644 src/components/MDX/List.tsx 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/MDX/Challenges/Challenges.tsx b/src/components/MDX/Challenges/Challenges.tsx index 1b5dcfb1f06..eb6fec19d41 100644 --- a/src/components/MDX/Challenges/Challenges.tsx +++ b/src/components/MDX/Challenges/Challenges.tsx @@ -16,7 +16,7 @@ import {H2} from 'components/MDX/Heading'; import {H4} from 'components/MDX/Heading'; import {Challenge} from './Challenge'; import {Navigation} from './Navigation'; -import {useRouter} from 'next/router'; +import {useRouter} from 'next/compat/router'; interface ChallengesProps { children: React.ReactElement[]; diff --git a/src/components/MDX/Challenges/index.tsx b/src/components/MDX/Challenges/index.tsx index 27e3df1ef0d..481f154ac7f 100644 --- a/src/components/MDX/Challenges/index.tsx +++ b/src/components/MDX/Challenges/index.tsx @@ -1,3 +1,5 @@ +'use client'; + /** * Copyright (c) Meta Platforms, Inc. and affiliates. * diff --git a/src/components/MDX/ErrorDecoder.tsx b/src/components/MDX/ErrorDecoder.tsx index 423790198bf..ea9355fd371 100644 --- a/src/components/MDX/ErrorDecoder.tsx +++ b/src/components/MDX/ErrorDecoder.tsx @@ -1,3 +1,5 @@ +'use client'; + /** * Copyright (c) Meta Platforms, Inc. and affiliates. * @@ -6,7 +8,7 @@ */ import {useEffect, useState} from 'react'; -import {useErrorDecoderParams} from '../ErrorDecoderContext'; +import {useErrorDecoderParams} from '../_/ErrorDecoderContext'; import cn from 'classnames'; function replaceArgs( diff --git a/src/components/MDX/Illustration/AuthorCredit.tsx b/src/components/MDX/Illustration/AuthorCredit.tsx new file mode 100644 index 00000000000..2f230ef8cad --- /dev/null +++ b/src/components/MDX/Illustration/AuthorCredit.tsx @@ -0,0 +1,28 @@ +export function AuthorCredit({ + author = 'Rachel Lee Nabors', + authorLink = 'https://nearestnabors.com/', +}: { + author: string; + authorLink: string; +}) { + return ( +
+

+ + Illustrated by{' '} + {authorLink ? ( + + {author} + + ) : ( + author + )} + +

+
+ ); +} diff --git a/src/components/MDX/Illustration/Illustration.tsx b/src/components/MDX/Illustration/Illustration.tsx new file mode 100644 index 00000000000..96ba3a89244 --- /dev/null +++ b/src/components/MDX/Illustration/Illustration.tsx @@ -0,0 +1,40 @@ +'use client'; + +import {useContext} from 'react'; +import {IllustrationContext} from './IllustrationContext'; +import {AuthorCredit} from './AuthorCredit'; + +export function Illustration({ + caption, + src, + alt, + author, + authorLink, +}: { + caption: string; + src: string; + alt: string; + author: string; + authorLink: string; +}) { + const {isInBlock} = useContext(IllustrationContext); + + return ( +
+
+ {alt} + {caption ? ( +
+ {caption} +
+ ) : null} +
+ {!isInBlock && } +
+ ); +} diff --git a/src/components/MDX/Illustration/IllustrationBlock.tsx b/src/components/MDX/Illustration/IllustrationBlock.tsx new file mode 100644 index 00000000000..2538bec6197 --- /dev/null +++ b/src/components/MDX/Illustration/IllustrationBlock.tsx @@ -0,0 +1,56 @@ +import {Children} from 'react'; +import {IllustrationContext} from './IllustrationContext'; +import {AuthorCredit} from './AuthorCredit'; + +const isInBlockTrue = {isInBlock: true}; + +export function IllustrationBlock({ + sequential, + author, + authorLink, + children, +}: { + author: string; + authorLink: string; + sequential: boolean; + children: any; +}) { + const imageInfos = Children.toArray(children).map( + (child: any) => child.props + ); + const images = imageInfos.map((info, index) => ( +
+
+ {info.alt} +
+ {info.caption ? ( +
+ {info.caption} +
+ ) : null} +
+ )); + return ( + +
+ {sequential ? ( +
    + {images.map((x: any, i: number) => ( +
  1. + {x} +
  2. + ))} +
+ ) : ( +
{images}
+ )} + +
+
+ ); +} diff --git a/src/components/MDX/Illustration/IllustrationContext.ts b/src/components/MDX/Illustration/IllustrationContext.ts new file mode 100644 index 00000000000..226d0d4b16f --- /dev/null +++ b/src/components/MDX/Illustration/IllustrationContext.ts @@ -0,0 +1,9 @@ +'use client'; + +import {createContext} from 'react'; + +export const IllustrationContext = createContext<{ + isInBlock?: boolean; +}>({ + isInBlock: false, +}); diff --git a/src/components/MDX/LanguageList.tsx b/src/components/MDX/LanguageList.tsx new file mode 100644 index 00000000000..258003058f0 --- /dev/null +++ b/src/components/MDX/LanguageList.tsx @@ -0,0 +1,39 @@ +'use client'; + +import {useContext} from 'react'; +import {LanguagesContext} from './LanguagesContext'; +import {finishedTranslations} from 'utils/finishedTranslations'; +import Link from 'next/link'; +import {LI, UL} from './List'; + +type TranslationProgress = 'complete' | 'in-progress'; + +export function LanguageList({progress}: {progress: TranslationProgress}) { + const allLanguages = useContext(LanguagesContext) ?? []; + const languages = allLanguages + .filter( + ({code}) => + code !== 'en' && + (progress === 'complete' + ? finishedTranslations.includes(code) + : !finishedTranslations.includes(code)) + ) + .sort((a, b) => a.enName.localeCompare(b.enName)); + return ( + + ); +} diff --git a/src/components/MDX/LanguagesContext.tsx b/src/components/MDX/LanguagesContext.tsx index cd9f8881671..ac37dcc9f25 100644 --- a/src/components/MDX/LanguagesContext.tsx +++ b/src/components/MDX/LanguagesContext.tsx @@ -1,3 +1,5 @@ +'use client'; + /** * Copyright (c) Meta Platforms, Inc. and affiliates. * diff --git a/src/components/MDX/List.tsx b/src/components/MDX/List.tsx new file mode 100644 index 00000000000..fab233fba59 --- /dev/null +++ b/src/components/MDX/List.tsx @@ -0,0 +1,9 @@ +export const OL = (p: React.HTMLAttributes) => ( +
    +); +export const LI = (p: React.HTMLAttributes) => ( +
  1. +); +export const UL = (p: React.HTMLAttributes) => ( +
      +); diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index a32dad27174..29a8e950c33 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -9,7 +9,7 @@ * Copyright (c) Facebook, Inc. and its affiliates. */ -import {Children, useContext, useMemo} from 'react'; +import {useContext, useMemo} from 'react'; import * as React from 'react'; import cn from 'classnames'; import type {HTMLAttributes} from 'react'; @@ -39,12 +39,14 @@ import ButtonLink from 'components/ButtonLink'; import {TocContext} from './TocContext'; import type {Toc, TocItem} from './TocContext'; import {TeamMember} from './TeamMember'; -import {LanguagesContext} from './LanguagesContext'; -import {finishedTranslations} from 'utils/finishedTranslations'; import ErrorDecoder from './ErrorDecoder'; import {IconCanary} from '../Icon/IconCanary'; import {IconExperimental} from 'components/Icon/IconExperimental'; +import {Illustration} from './Illustration/Illustration'; +import {IllustrationBlock} from './Illustration/IllustrationBlock'; +import {LanguageList} from './LanguageList'; +import {LI, OL, UL} from './List'; function CodeStep({children, step}: {children: any; step: number}) { return ( @@ -76,16 +78,6 @@ const Strong = (strong: HTMLAttributes) => ( ); -const OL = (p: HTMLAttributes) => ( -
        -); -const LI = (p: HTMLAttributes) => ( -
      1. -); -const UL = (p: HTMLAttributes) => ( -
          -); - const Divider = () => (
          ); @@ -262,129 +254,6 @@ function Recipes(props: any) { return ; } -function AuthorCredit({ - author = 'Rachel Lee Nabors', - authorLink = 'https://nearestnabors.com/', -}: { - author: string; - authorLink: string; -}) { - return ( -
          -

          - - Illustrated by{' '} - {authorLink ? ( - - {author} - - ) : ( - author - )} - -

          -
          - ); -} - -const IllustrationContext = React.createContext<{ - isInBlock?: boolean; -}>({ - isInBlock: false, -}); - -function Illustration({ - caption, - src, - alt, - author, - authorLink, -}: { - caption: string; - src: string; - alt: string; - author: string; - authorLink: string; -}) { - const {isInBlock} = React.useContext(IllustrationContext); - - return ( -
          -
          - {alt} - {caption ? ( -
          - {caption} -
          - ) : null} -
          - {!isInBlock && } -
          - ); -} - -const isInBlockTrue = {isInBlock: true}; - -function IllustrationBlock({ - sequential, - author, - authorLink, - children, -}: { - author: string; - authorLink: string; - sequential: boolean; - children: any; -}) { - const imageInfos = Children.toArray(children).map( - (child: any) => child.props - ); - const images = imageInfos.map((info, index) => ( -
          -
          - {info.alt} -
          - {info.caption ? ( -
          - {info.caption} -
          - ) : null} -
          - )); - return ( - -
          - {sequential ? ( -
            - {images.map((x: any, i: number) => ( -
          1. - {x} -
          2. - ))} -
          - ) : ( -
          {images}
          - )} - -
          -
          - ); -} - type NestedTocRoot = { item: null; children: Array; @@ -438,38 +307,6 @@ function InlineTocItem({items}: {items: Array}) { ); } -type TranslationProgress = 'complete' | 'in-progress'; - -function LanguageList({progress}: {progress: TranslationProgress}) { - const allLanguages = React.useContext(LanguagesContext) ?? []; - const languages = allLanguages - .filter( - ({code}) => - code !== 'en' && - (progress === 'complete' - ? finishedTranslations.includes(code) - : !finishedTranslations.includes(code)) - ) - .sort((a, b) => a.enName.localeCompare(b.enName)); - return ( -
            - {languages.map(({code, name, enName}) => { - return ( -
          • - - {enName} ({name}) - {' '} - —{' '} - - Contribute - -
          • - ); - })} -
          - ); -} - 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/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. * From 5da0adac9b577f3e920632ce6d5ec9da7b02e637 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Sat, 3 Jan 2026 18:11:53 +0800 Subject: [PATCH 3/7] Refactor: share part of root layout across App/Pages Routers --- src/app/layout.tsx | 13 +++ src/components/_/README.md | 7 ++ src/components/_/root-layout.tsx | 154 +++++++++++++++++++++++++++++++ src/pages/_document.tsx | 144 +---------------------------- 4 files changed, 178 insertions(+), 140 deletions(-) create mode 100644 src/app/layout.tsx create mode 100644 src/components/_/README.md create mode 100644 src/components/_/root-layout.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 00000000000..a0bfd4aaa48 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,13 @@ +import {SharedRootBody, SharedRootHead} from '../components/_/root-layout'; +import {siteConfig} from '../siteConfig'; + +export default function RootLayout({children}: React.PropsWithChildren) { + return ( + + + + + {children} + + ); +} 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 ( + <> + + + + + + + +