Learning React from
react.dev

A Production Codebase Deep Dive

Stack: Next.js 15 · React 19 · TypeScript · Tailwind CSS · MDX
Pages Router React Compiler Sandpack

What We'll Cover

This deck extracts patterns from the actual react.dev source code. Every example is production-tested, serving millions of developers every month.

  1. Project Architecture — How a real docs site is built
  2. Component Patterns — The "how" of React
  3. Custom Hooks — Reusable logic you can steal
  4. React Context — Multi-level data flow
  5. MDX + React — Content as components
  6. Performance — Lazy loading, memo, transitions
  7. TypeScript Patterns — Real-world generics
  8. Anti-Patterns — What NOT to do
  9. React 19 Features — Compiler, RSC, transitions

Project Architecture

How react.dev is organized

click on bottom arrow to read and left arrow to move to nest topic

Tech Stack

The site runs on a carefully chosen stack that balances performance, maintainability, and author experience. No bloat, no novelty for novelty's sake.

FrameworkNext.js 15.1.11 (Pages Router)
ReactReact 19 + React Compiler
LanguageTypeScript 5.7
StylingTailwind CSS 3 + classnames
ContentMDX (Markdown + JSX)
SearchAlgolia DocSearch
PlaygroundCodeSandbox Sandpack
UI PrimitivesHeadless UI + Radix UI

Directory Structure

Despite serving hundreds of pages, the codebase stays surprisingly flat. The catch-all route and MDX pipeline mean most content lives in plain markdown files, not pages.

src/
├── pages/              # Next.js Pages Router
│   ├── _app.tsx        # Root app component (providers, analytics)
│   ├── _document.tsx   # HTML shell
│   └── [[...markdownPath]].js   # 🎯 Catch-all route for ALL docs!
├── components/
│   ├── Layout/         # Page shell, sidebar, topnav, footer, TOC
│   ├── MDX/            # 50+ custom MDX components
│   └── Icon/           # SVG icon components (30+ icons)
├── hooks/              # Custom React hooks
│   └── usePendingRoute.ts
├── utils/              # Build-time & runtime utilities
├── content/            # All documentation as .md files
└── styles/             # Global CSS, Tailwind

How Content Becomes a Page

React.dev does not render MDX on the client. It compiles everything at build time, serializes the React tree to JSON, and revives it in the browser. This is a custom SSG pipeline that predates React Server Components.

Build Time

  1. Scan src/content/ for all .md files
  2. getStaticPaths() generates all URL paths
  3. getStaticProps() reads each MDX file
  4. Compile MDX → JSX at build time
  5. Serialize React tree to JSON
  6. Cache in node_modules/.cache/

Client Side

  1. JSON props arrive at browser
  2. reviveNodeOnClient() deserializes
  3. String names → real React components
  4. Page component wraps everything
  5. Sidebar + TOC + content all render

The Catch-All Route Explained

One Next.js page file powers every single documentation URL. It maps URL segments to file paths on disk, then compiles the matching MDX into a serializable React tree.

// src/pages/[[...markdownPath]].js

// This ONE file handles every documentation page:
//   /                → markdownPath = undefined      → index.md
//   /learn            → markdownPath = ['learn']      → learn/index.md
//   /reference/react  → markdownPath = ['reference','react'] → reference/react.md

export async function getStaticPaths() {
  // Recursively find ALL .md files in src/content/
  // Converts 'learn/thinking-in-react.md' → { params: { markdownPath: ['learn','thinking-in-react'] } }
}

export async function getStaticProps(context) {
  const path = (context.params.markdownPath || []).join('/') || 'index';
  const mdx = fs.readFileSync(rootDir + path + '.md', 'utf8');
  const { toc, content, meta } = await compileMDX(mdx, path, {});
  return { props: { toc, content, meta } }; // JSON-serialized React tree!
}

Component Patterns

How real React components are structured

click on bottom arrow to read and left arrow to move to nest topic

Pattern 1: Functional Components with TypeScript Props

The codebase never uses React.FC. Instead, every component is a plain function with a named interface. This keeps types close to the code and avoids the implicit children prop that FC injects.

//  THE STANDARD: Interface for props, destructure in signature
interface PageProps {
  children: React.ReactNode;
  toc: Array<TocItem>;
  routeTree: RouteItem;
  meta: { title?: string; description?: string };
  section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown';
}

export function Page({ children, toc, routeTree, meta, section }: PageProps) {
  // ... component logic
  return (/* JSX */);
}
  • Always define props as a named interface
  • Use string literal unions for constrained values
  • Mark optional props with ?
  • Never use React.FC (it's not used anywhere in this codebase!)

Pattern 2: The "Variant Map" Pattern

When a component supports multiple visual variants, a lookup object is cleaner than a chain of ternaries. It also makes TypeScript exhaustiveness checking trivial.

Instead of if/else chains, use lookup objects

//  CLEAN: Configuration as data, not imperative logic
const variantMap = {
  deprecated: {
    title: 'Deprecated',
    Icon: IconWarning,
    containerClasses: 'bg-red-5 dark:bg-red-60',
    textColor: 'text-red-50 dark:text-red-40',
  },
  note: {
    title: 'Note',
    Icon: IconNote,
    containerClasses: 'bg-green-5 dark:bg-green-60',
    textColor: 'text-green-60 dark:text-green-40',
  },
  pitfall: { /* ... */ },
  canary:  { /* ... */ },
  wip:     { /* ... */ },
};

function ExpandableCallout({ children, type = 'note' }) {
  const variant = variantMap[type];
  return (
    <div className={variant.containerClasses}>
      <h3 className={variant.textColor}>
        <variant.Icon /> {variant.title}
      </h3>
      {children}
    </div>
  );
}

Easy to add new variants without touching the component logic

Pattern 3: Self-Contained Icon Components

Rather than importing a massive icon library, each SVG is a standalone component. This guarantees tree-shaking, lets icons inherit color via currentColor, and keeps sizing proportional to text.

// src/components/Icon/IconNote.tsx
// Each icon is its OWN component — no giant icon library bundle!
export function IconNote({ className }: { className?: string }) {
  return (
    <svg
      width="1em" height="1em" viewBox="0 0 16 16"
      className={className} fill="currentColor">
      <path d="M8 1.5a6.5 6.5 ..." />
    </svg>
  );
}
  • Tree-shakeable: only imported icons end up in the bundle
  • Uses currentColor for easy theming
  • 1em sizing makes icons respect parent font size

Pattern 4: Component Composition via Layout Shell

The Page component does not accept dozens of boolean flags. It composes layout pieces via children and wraps only the content area in Context providers, keeping the render tree lean.

// Page.tsx — The layout orchestrator
export function Page({ children, toc, routeTree, section }) {
  return (
    <>
      <Seo title={title} />
      <TopNav section={section} />

      <div className="grid grid-cols-sidebar-content-toc">
        {/* Left: Sidebar */}
        <SidebarNav routeTree={routeTree} />

        {/* Center: Content with Context Providers wrapping children */}
        <Suspense fallback={null}>
          <main>
            <TocContext value={toc}>
              <LanguagesContext value={languages}>
                {children}  {/* <— MDX content goes here */}
              </LanguagesContext>
            </TocContext>
          </main>
        </Suspense>

        {/* Right: Table of Contents */}
        <Toc headings={toc} />
      </div>

      <Footer />
    </>
  );
}

Context providers wrap only the content area, not the entire app

Pattern 5: The forwardRefWithAs Polymorphic Pattern

// Allows a component to render as ANY HTML element
export function forwardRefWithAs<Props, ComponentType extends As>(
  comp: (props, ref) => React.ReactElement | null
) {
  return React.forwardRef(comp as any);
}

// Usage: Heading can render as h1, h2, h3, or any element!
const Heading = forwardRefWithAs<HeadingProps, 'div'>(
  function Heading({ as: Comp = 'div', children, id, ...props }, ref) {
    return (
      <Comp id={id} ref={ref} {...props}>
        {children}
        <a href={`#${id}`} className="mdx-header-anchor">#</a>
      </Comp>
    );
  }
);
// H1 = <Heading as="h1" />  → renders <h1>

Advanced TypeScript pattern — lets one component render as multiple HTML elements

Pattern 6: Named Exports over Default Exports

//  Preferred in this codebase:
export function Button({ children, onClick, active }: ButtonProps) { ... }
export function DocsPageFooter({ nextRoute, prevRoute }: Props) { ... }

//  Barrel exports via index files:
// components/Layout/Sidebar/index.tsx
export { SidebarButton } from './SidebarButton';
export { SidebarLink } from './SidebarLink';
export { SidebarRouteTree } from './SidebarRouteTree';
  • Named exports enable better tree-shaking
  • IDE auto-import works perfectly
  • Barrel index files create clean public APIs

Custom Hooks

Extracting reusable stateful logic

click on bottom arrow to read and left arrow to move to nest topic

Hook 1: usePendingRoute

Navigating between pages in a docs site should feel instant. This hook debounces route changes so a loading indicator only appears when navigation actually takes longer than 100ms, eliminating flicker on fast transitions.

Detects slow page navigations for loading states

const usePendingRoute = () => {
  const { events } = useRouter();
  const [pendingRoute, setPendingRoute] = useState<string | null>(null);
  const currentRoute = useRef<string | null>(null);

  useEffect(() => {
    let routeTransitionTimer: any = null;

    const handleRouteChangeStart = (url: string) => {
      clearTimeout(routeTransitionTimer);
      // 🎯 KEY: Only show pending after 100ms delay (debounce!)
      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 () => {  // 🔑 Cleanup!
      events.off('routeChangeStart', handleRouteChangeStart);
      events.off('routeChangeComplete', handleRouteChangeComplete);
      clearTimeout(routeTransitionTimer);
    };
  }, [events]);

  return pendingRoute;
};

What Makes usePendingRoute Great

Great hooks do one thing well and clean up after themselves. This one demonstrates the full lifecycle: subscribe, throttle, track mutable state with refs, and unsubscribe on unmount.

  • Debounce throttling: Only shows loading state after 100ms — avoids flicker on fast navigations
  • useRef for mutable tracking: currentRoute tracks without re-renders
  • Proper cleanup: Removes listeners, clears timers in the return function
  • Dependency array: [events] ensures effect re-runs if router events change
  • useState for render-triggering: setPendingRoute triggers re-render to show loading UI
💡 Rule of thumb: useRef for values that shouldn't trigger re-renders, useState for values that should.

Hook 2: useTocHighlight

A table of contents is only useful if it shows where you are. This hook tracks which heading is currently in view, throttled to 100ms so scroll performance stays at 60fps.

Scroll-spy for the Table of Contents sidebar

export function useTocHighlight() {
  const [currentIndex, setCurrentIndex] = useState<number>(0);
  const timeoutRef = useRef<number | null>(null);

  useEffect(() => {
    function updateActiveLink() {
      const scrollPosition = window.scrollY + window.innerHeight;
      const headersAnchors = getHeaderAnchors();

      if (scrollPosition >= document.body.scrollHeight) {
        setCurrentIndex(headersAnchors.length - 1);
        return;
      }

      let index = -1;
      while (index < headersAnchors.length - 1) {
        const { top } = headersAnchors[index + 1].getBoundingClientRect();
        if (top >= 85) break;  // 85px offset for sticky header
        index++;
      }
      setCurrentIndex(Math.max(index, 0));
    }

    // 🎯 Throttle: max one update per 100ms
    function throttledUpdateActiveLink() {
      if (timeoutRef.current === null) {
        timeoutRef.current = window.setTimeout(() => {
          timeoutRef.current = null;
          updateActiveLink();
        }, 100);
      }
    }

    window.addEventListener('scroll', throttledUpdateActiveLink, {passive: true});
    window.addEventListener('resize', throttledUpdateActiveLink);
    // ...cleanup
  }, []);
  return { currentIndex };
}

Hook 3: useActiveSection (Inline)

Sometimes the best abstraction is no abstraction at all. This function derives the active section directly from the router path without any state or effects. If it does not call hooks, it is not a hook.

Determines which site section you're in from the URL

function useActiveSection() {
  const { asPath } = useRouter();
  const cleanedPath = asPath.split(/[\?\#]/)[0];

  if (cleanedPath === '/')      return 'home';
  if (cleanedPath.startsWith('/reference')) return 'reference';
  if (asPath.startsWith('/learn'))     return 'learn';
  if (asPath.startsWith('/community')) return 'community';
  if (asPath.startsWith('/blog'))      return 'blog';

  return 'unknown';
}
  • Simple derived state — no useEffect needed!
  • Note: this isn't actually a hook (no "use" prefix pattern usage), it just calls useRouter()

Custom Hook Design Rules (from this codebase)

  1. Name starts with "use": usePendingRoute, useTocHighlight
  2. Return minimal API: usePendingRoute returns just a string | null, not an object
  3. Clean up in useEffect returns: timers, listeners, subscriptions
  4. Use useRef for non-render values: timer IDs, previous values, DOM refs
  5. Use useState only for render-triggering values: loading states, indices, toggles
  6. Throttle expensive work: scroll handlers, resize handlers → setTimeout gate
  7. Debounce user feedback: 100ms delay before showing loading → avoids flicker

React Context

Prop drilling is dead — long live Context

click on bottom arrow to read and left arrow to move to nest topic

How This Codebase Uses Context

Context is not a state manager; it is a dependency injector. React.dev uses it sparingly, placing providers close to consumers rather than at the root, which minimizes re-render blast radius.

Context Purpose Pattern
TocContext Table of Contents data Pass data down to MDX components (InlineToc, etc.)
LanguagesContext Available translations Language picker in MDX content
ErrorDecoderContext Error message + code Sentinel pattern: throw if used outside expected page
IllustrationContext Is inside an IllustrationBlock? Boolean flag: changes behavior of nested components

Context Pattern 1: Basic Data Passing

The simplest use of Context is passing data down without prop drilling. Here, TOC data and available translations are provided at the layout level so any MDX component can read them.

// 1. Define the context with a default value
export const TocContext = createContext<Toc>([]);

// 2. Provide it at the layout level
<TocContext value={toc}>
  <LanguagesContext value={languages}>
    {children}  {/* All MDX components can now use these */}
  </LanguagesContext>
</TocContext>

// 3. Consume it deep in the tree
function InlineToc() {
  const toc = useContext(TocContext);
  // Use toc data to build inline navigation
}

Context providers are placed close to where the data is needed, not at the root

Context Pattern 2: The Sentinel Pattern

A unique Symbol as the default value makes it impossible to accidentally match real data. The custom hook then validates usage and throws a clear error if the component is rendered outside its expected parent.

// 🔒 Type-safe guard against misuse
const notInErrorDecoderContext = Symbol('not in error decoder context');

export const ErrorDecoderContext = createContext<
  { errorMessage: string | null; errorCode: string | null }
  | typeof notInErrorDecoderContext  // <— Sentinel default!
>(notInErrorDecoderContext);

// Custom hook with built-in validation
export const useErrorDecoderParams = () => {
  const params = useContext(ErrorDecoderContext);

  if (params === notInErrorDecoderContext) {
    throw new Error(
      'useErrorDecoder must be used in error decoder pages only'
    );
  }
  return params; // TypeScript now knows it's the real type!
};
  • Uses a unique Symbol as default — can never collide with real data
  • The custom hook validates context exists and throws a clear error if missing
  • TypeScript narrows the type after the guard check

Context Pattern 3: Boolean Behavior Flag

Context can change child behavior without passing props through every layer. Here, an isInBlock flag suppresses author credits for illustrations nested inside a grouped block.

// IllustrationContext controls whether child Illustrations
// show their author credit (don't show it inside a block)
const IllustrationContext = React.createContext<{isInBlock?: boolean}>({
  isInBlock: false,
});

function IllustrationBlock({ children }) {
  return (
    // Wrap children so they know they're inside a block
    <IllustrationContext value={{ isInBlock: true }}>
      {children}
    </IllustrationContext>
  );
}

function Illustration({ caption, src, alt, author }) {
  const { isInBlock } = useContext(IllustrationContext);
  return (
    <figure>
      <img src={src} alt={alt} />
      {/* Only show author credit if NOT inside a block */}
      {!isInBlock && <AuthorCredit author={author} />}
    </figure>
  );
}

Elegant way for parent components to change child behavior without prop drilling

MDX + React

Content as Components — the heart of react.dev

click on bottom arrow to read and left arrow to move to nest topic

What is MDX?

MDX lets authors write JSX inside Markdown. For a documentation site, this means interactive sandboxes, callout boxes, and diagrams live right inside the content files without touching React code.

Markdown + JSX = components in your markdown

# Rendering Lists

You can render lists from arrays like this:

<Sandpack>
```js App.js
const people = ['Creola', 'Mario', 'Mohammad'];
export default function List() {
  return (
    <ul>
      {people.map(p => <li key={p}>{p}</li>)}
    </ul>
  );
}
```
</Sandpack>

<Note>Keys must be unique among siblings.</Note>

The <Sandpack> and <Note> are real React components!

The MDX Compilation Pipeline

At build time, every markdown file goes through a multi-stage pipeline. The result is a JSON-serialized React tree that the client revives into real components without re-parsing MDX in the browser.

.md file
    │
    ▼
1. Add fake imports for every MDX component name
   ("import CodeBlock from 'CodeBlock'" etc.)
    │
    ▼
2. @mdx-js/mdx → Compile to JSX
    │
    ▼
3. Babel transform → CommonJS JavaScript
    │
    ▼
4. eval() the JS code → Get React element tree
   ( BUILD TIME ONLY — never eval user content!)
    │
    ▼
5. prepareMDX() — Extract TOC, wrap in MaxWidth containers
    │
    ▼
6. JSON.stringify() — Serialize React tree to JSON for client
    │
    ▼
7. Cache to disk (metro-cache)
    │
    ▼
8. Client: JSON.parse() → reviveNodeOnClient()
   String component names → Real React components

The MDXComponents Map — 50+ Custom Components!

export const MDXComponents = {
  // Override standard HTML elements
  p: P, strong: Strong, blockquote: Blockquote,
  ol: OL, ul: UL, li: LI,
  h1: H1, h2: H2, h3: H3, h4: H4, h5: H5,
  a: Link, img: Image, code: InlineCode, pre: CodeBlock,

  // Custom MDX-only components
  Sandpack, SandpackRSC,     // Interactive code playgrounds
  Note, Pitfall, Canary, Wip, Deprecated,  // Callout boxes
  Challenges, Hint, Solution,             // Interactive exercises
  Diagram, DiagramGroup,                  // SVG diagrams
  CodeDiagram, CodeStep,                  // Annotated code
  ConsoleBlock, TerminalBlock,            // Terminal output
  YouWillLearn, Recap, Intro,            // Learning structure
  InlineToc,                              // Auto-generated TOC
  DeepDive,                               // Expandable sections
  YouTubeIframe,                          // Video embeds
  PackageImport,                          // npm install snippets
  TeamMember,                             // Author profiles
  // ...and more!
};

Key MDX Pattern: Serialization & Revival

// SERVER: Serialize React elements to JSON
function stringifyNodeOnServer(key, val) {
  if (val?.$$typeof === Symbol.for('react.transitional.element')) {
    const { mdxType, originalType, parentName, ...cleanProps } = val.props;
    return ['$r', typeof val.type === 'string' ? val.type : mdxType, val.key, cleanProps];
  }
  return val;
}

// CLIENT: Revive JSON back to React elements
function reviveNodeOnClient(parentPropertyName, val) {
  if (Array.isArray(val) && val[0] === '$r') {
    let Type = val[1];      // Component name as string
    let key = val[2];       // React key
    let props = val[3];     // Cleaned props

    if (Type === 'wrapper') {
      Type = Fragment;
    }
    if (Type in MDXComponents) {
      Type = MDXComponents[Type];  // String → Real component!
    }
    return <Type key={key} {...props} />;
  }
  return val;
}

This is a custom SSG pipeline — predates React Server Components

Heading Components with Auto-Anchors

const Heading = forwardRefWithAs<HeadingProps, 'div'>(
  function Heading({ as: Comp = 'div', children, id, isPageAnchor = true, ...props }, ref) {
    let label = typeof children === 'string'
      ? 'Link for ' + children
      : 'Link for this heading';

    return (
      <Comp id={id} ref={ref} className="mdx-heading" {...props}>
        {children}
        {isPageAnchor && (
          <a href={`#${id}`} aria-label={label} className="mdx-header-anchor">
            {/* SVG link icon */}
          </a>
        )}
      </Comp>
    );
  }
);

// Then pre-configured variants:
export const H1 = (props) => <Heading as="h1" className="text-5xl font-bold" {...props} />;
export const H2 = (props) => <Heading as="h2" className="text-3xl font-bold my-6" {...props} />;
export const H3 = (props) => <Heading as="h3" className="text-2xl font-bold my-6" {...props} />;

Performance Patterns

Real-world optimization techniques

click on bottom arrow to read and left arrow to move to nest topic

1. React.lazy() + Dynamic Imports

Not every component needs to be in the initial bundle. The search modal and code playgrounds are split into separate chunks and loaded only when the user actually needs them.

// Search.tsx — Lazy load the heavy DocSearch modal
const DocSearchModal: any = lazy(() =>
  import('@docsearch/react/modal').then((mod) => ({
    default: mod.DocSearchModal,
  }))
);

// Only loaded when user opens search (Cmd+K)
// Page.tsx — Prefetch the CodeBlock component
import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock');

// Browser downloads CodeBlock during idle time,
// so it's ready when user scrolls to a code example
  • lazy() defers loading until component renders
  • webpackPrefetch downloads during browser idle time
  • Different strategies for different priorities

2. React.memo() with Custom Comparator

Memoization is not free. React.dev only uses memo when a component receives large objects and only needs to re-render on specific field changes, such as the current page path.

// DocsFooter.tsx — Only re-render if the route actually changed
function areEqual(prevProps, props) {
  return prevProps.route?.path === props.route?.path;
}

export const DocsPageFooter = memo(
  function DocsPageFooter({ nextRoute, prevRoute, route }) {
    // This component only re-renders when the page path changes,
    // not when the entire route object reference changes
    return (/* Previous/Next navigation links */);
  },
  areEqual  // Custom comparison function!
);
  • memo() skips re-render when props are shallow-equal
  • Custom comparator for fine-grained control
  • Only use when you've measured a performance issue

3. startTransition — Mark Updates as Non-Urgent

// TopNav.tsx — Opening search is not urgent
import { startTransition } from 'react';

const onOpenSearch = useCallback(() => {
  startTransition(() => {
    setShowSearch(true); // React can defer this if user is typing
  });
}, []);
💡 Why? If the user opens search while React is rendering something else, startTransition tells React "this can wait." The UI stays responsive.

4. useMemo — Cache Expensive Computations

// [[...markdownPath]].js — Only re-parse JSON when content string changes
const parsedContent = useMemo(
  () => JSON.parse(content, reviveNodeOnClient),
  [content]
);
const parsedToc = useMemo(
  () => JSON.parse(toc, reviveNodeOnClient),
  [toc]
);
// MDXComponents.tsx — Memoize nested TOC calculation
function InlineToc() {
  const toc = useContext(TocContext);
  const root = useMemo(() => calculateNestedToc(toc), [toc]);
  if (root.children.length < 2) return null;
  return <InlineTocItem items={root.children} />;
}
  • Use for expensive computations that depend on stable inputs
  • Don't wrap everything — useMemo has its own cost

5. Suspense — Graceful Loading States

// Page.tsx
<Suspense fallback={null}>
  <main>
    <TocContext value={toc}>...</TocContext>
  </main>
</Suspense>

// TopNav.tsx
<Suspense fallback={null}>
  <SidebarRouteTree ... />
</Suspense>
  • fallback={null} means "show nothing while loading" (not a spinner)
  • Avoids layout shift for fast-loading content
  • The comment says: "No fallback UI so need to be careful not to suspend directly inside"

6. IntersectionObserver — Scroll Detection

// TopNav.tsx — Detect when nav should get shadow
useEffect(() => {
  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        setIsScrolled(!entry.isIntersecting);
      });
    },
    { root: null, rootMargin: '0px', threshold: 0 }
  );

  observer.observe(scrollDetectorRef.current!);
  return () => observer.disconnect();
}, []);

// In JSX: <div ref={scrollDetectorRef} />  (0px sentinel at top)

Far more performant than scroll event listeners!

7. body-scroll-lock — Prevent Background Scroll

// When mobile menu opens, lock body scroll
useEffect(() => {
  if (isMenuOpen) {
    const scrollParent = scrollParentRef.current!;
    disableBodyScroll(scrollParent);
    return () => enableBodyScroll(scrollParent);
  }
}, [isMenuOpen]);

Critical for mobile UX — prevents the page behind the modal from scrolling

8. react-collapsed — Animated Collapse

// SidebarRouteTree.tsx — Smooth expand/collapse animation
const { getCollapseProps } = useCollapse({ isExpanded, duration });

return (
  <div {...getCollapseProps()}>
    {/* Nested sidebar items animate their height */}
  </div>
);

Uses FLIP animations — no manual height calculations needed

TypeScript Patterns

Advanced types from production code

click on bottom arrow to read and left arrow to move to nest topic

1. Discriminated Unions for Variants

A union of string literals turns runtime values into compile-time contracts. If you add a new variant, TypeScript forces you to update every map, switch, and lookup object that consumes it.

type CalloutVariants =
  | 'deprecated'
  | 'pitfall'
  | 'note'
  | 'wip'
  | 'canary'
  | 'experimental'
  | 'rc'
  | 'major'
  | 'rsc';

// TypeScript enforces that variantMap covers ALL variants
const variantMap: Record<CalloutVariants, VariantConfig> = { ... };

If you add a variant to the union, TypeScript forces you to add it to the map

2. Generic Polymorphic Component Types

// Advanced: "as" prop pattern with full type inference
export type As<BaseProps = any> = React.ElementType<BaseProps>;

export type PropsWithAs<ComponentType extends As, ComponentProps> =
  ComponentProps &
  Omit<
    React.ComponentPropsWithRef<ComponentType>,
    'as' | keyof ComponentProps
  > & {
    as?: ComponentType;
  };

This is what allows <Heading as="h1"> to infer all h1-specific props

3. Symbol-Based Sentinel Types

A Symbol cannot be forged by user data, making it the perfect sentinel for Context defaults. After a guard check, TypeScript narrows the type so you never have to deal with the sentinel again.

// Create a unique, unforgeable default value
const notInErrorDecoderContext = Symbol('not in error decoder context');

// The context type includes the sentinel
export const ErrorDecoderContext = createContext<
  { errorMessage: string | null; errorCode: string | null }
  | typeof notInErrorDecoderContext  // <— TypeScript narrows after guard
>(notInErrorDecoderContext);

TypeScript's type narrowing works perfectly with this pattern

4. Route Tree as Recursive Type

export interface RouteItem {
  title: string;
  version?: 'canary' | 'major';
  description?: string;
  tags?: RouteTag[];
  path?: string;            // URL path
  heading?: boolean;        // Is this just a section header?
  routes?: RouteItem[];     // 🔄 RECURSIVE! A tree structure
  hasSectionHeader?: boolean;
  sectionHeader?: string;
  skipBreadcrumb?: boolean;
}

Self-referential types for tree structures — sidebar and breadcrumbs both use this

5. TypeScript + Tailwind: classnames Library

import cn from 'classnames';

// Conditional classes made easy
<div className={cn(
  'text-base leading-tight font-bold rounded-full',     // Always
  { 'bg-link text-white': active },                     // Conditional
  { 'bg-transparent text-primary': !active },           // Conditional
  className                                              // From props
)} />

classnames (aliased as cn) is the standard approach in this codebase — used in nearly every component

State Management

No Redux, no Zustand — just React

click on bottom arrow to read and left arrow to move to nest topic

The Philosophy: React Built-ins Only

A documentation site does not need Redux, Zustand, or MobX. The URL is the single source of truth; everything else is derived from it via props, Context, and simple computation.

  • No external state library — Redux, Zustand, MobX, etc. are absent
  • useState for local component state
  • useReducer — not used (state isn't complex enough!)
  • Context for shared data (TOC, languages, error decoder)
  • URL as state — the router IS the state manager for navigation
  • Derived state — compute from props/URL instead of storing separately
💡 Key insight: A documentation site doesn't need complex client state. The URL is the single source of truth, and everything else is derived from it.

State Lifting Pattern

When multiple children need to share state, lift it to their closest common ancestor. The Challenges component owns the index and passes callbacks down so children can request changes without knowing about each other.

// Challenges.tsx — State lives at the top, passes down via props
export function Challenges({ children, isRecipes }) {
  const [currentChallengeIndex, setCurrentChallengeIndex] = useState(0);

  const handleClickNextChallenge = () => {
    setCurrentChallengeIndex(i => i + 1);
  };

  return (
    <Challenge
      currentChallenge={challenges[currentChallengeIndex]}
      hasNextChallenge={currentChallengeIndex < challenges.length - 1}
      handleClickNextChallenge={handleClickNextChallenge}
      totalChallenges={challenges.length}
    />
  );
}

State at the top, data flows down, events flow up — the classic React way

URL as State

// The router IS the state manager
const { asPath } = useRouter();

// Everything derives from the URL:
const section = useActiveSection();           // 'learn' | 'reference' | ...
const { route, prevRoute, nextRoute } =        // Current page metadata
  getRouteMeta(cleanedPath, routeTree);
const breadcrumbs = getBreadcrumbs(path, routeTree);  // Navigation trail

URL-driven state means: shareable, bookmarkable, no stale state

Recursive Components

Rendering tree structures elegantly

click on bottom arrow to read and left arrow to move to nest topic

SidebarRouteTree — A Recursive Sidebar

Tree-shaped data demands tree-shaped rendering. The sidebar renders itself recursively for every nested route group, with a clear base case for leaf nodes and collapsible wrappers for branches.

export function SidebarRouteTree({ routeTree, level = 0, ... }) {
  const currentRoutes = routeTree.routes as RouteItem[];

  return (
    <ul>
      {currentRoutes.map(({ path, title, routes, heading }) => {
        if (!path || heading) {
          // 🔄 RECURSIVE: No path = section header, recurse into children
          return <SidebarRouteTree
            level={level + 1}
            routeTree={{ title, routes }}
            ...
          />;
        }

        if (routes) {
          // 🔄 RECURSIVE: Has children = expandable item
          return (
            <li>
              <SidebarLink href={path} title={title} />
              <CollapseWrapper isExpanded={isExpanded}>
                <SidebarRouteTree
                  level={level + 1}
                  routeTree={{ title, routes }}
                  ...
                />
              </CollapseWrapper>
            </li>
          );
        }

        // Base case: leaf node with no children
        return (
          <li>
            <SidebarLink href={path} title={title} />
          </li>
        );
      })}
    </ul>
  );
}

InlineTocItem — Recursive Table of Contents

function InlineTocItem({ items }: { items: Array<NestedTocNode> }) {
  return (
    <UL>
      {items.map((node) => (
        <LI key={node.item.url}>
          <Link href={node.item.url}>{node.item.text}</Link>
          {/* 🔄 RECURSE: Nested headings get nested lists */}
          {node.children.length > 0 && (
            <InlineTocItem items={node.children} />
          )}
        </LI>
      ))}
    </UL>
  );
}

getBreadcrumbs — Recursive Tree Traversal

// Depth-first search through a tree of routes
function getBreadcrumbs(
  path: string,
  currentRoute: RouteItem,
  breadcrumbs: RouteItem[] = []
): RouteItem[] {
  //  Base case: found the matching route
  if (currentRoute.path === path) {
    return breadcrumbs;
  }

  if (!currentRoute.routes) {
    return [];  // Dead end
  }

  // 🔄 RECURSE: Search through children
  for (const route of currentRoute.routes) {
    const childRoute = getBreadcrumbs(path, route, [
      ...breadcrumbs,
      currentRoute,   // Add current to the trail
    ]);
    if (childRoute?.length) {
      return childRoute;
    }
  }

  return [];  // Not found in this branch
}

Sandpack Integration

Live, editable code examples

click on bottom arrow to read and left arrow to move to nest topic

SandpackRoot — Provider + File Map Pattern

Every interactive code example on react.dev is a Sandpack instance. The root component inspects its children to build a file map, injects default styles, and configures lazy initialization so sandboxes only spin up when scrolled into view.

function SandpackRoot({ children, autorun = true }) {
  //  Extract code snippets from children using React.Children API
  const codeSnippets = Children.toArray(children) as React.ReactElement[];
  const files = createFileMap(codeSnippets);

  // 🔧 Add default CSS to every sandbox
  files['/src/styles.css'] = {
    code: [sandboxStyle, files['/src/styles.css']?.code ?? ''].join('\n\n'),
    hidden: !files['/src/styles.css']?.visible,
  };

  return (
    <SandpackProvider
      files={{ ...template, ...files }}
      theme={CustomTheme}
      customSetup={{ environment: 'create-react-app' }}
      options={{
        autorun,
        initMode: 'user-visible',       // Lazy init!
        initModeObserverOptions: { rootMargin: '1400px 0px' },
      }}>
      <CustomPreset providedFiles={Object.keys(files)} />
    </SandpackProvider>
  );
}

Key Sandpack Patterns

  • React.Children API: Inspects children to extract file definitions
  • Lazy initialization: initMode: 'user-visible' — sandbox only loads when scrolled into view
  • Custom theme: Dark mode aware sandpack theme
  • Custom ESLint: Built-in linting for sandbox code (useSandpackLint)
  • RSC support: SandpackRSC variant for React Server Component examples
  • File bridge: Custom RSC worker for server-side sandbox execution

Next.js Patterns

Pages Router done right

click on bottom arrow to read and left arrow to move to nest topic

1. _app.tsx — The Root Component

The Next.js app component is the entry point for every page. React.dev uses it for one-time side effects: browser-specific scroll restoration and Google Analytics page-view tracking across route changes.

export default function MyApp({ Component, pageProps }: AppProps) {
  const router = useRouter();

  // Side effects that should run ONCE for the entire app:

  // 1. Browser-specific scroll restoration
  useEffect(() => {
    const isSafari = /safari/i.test(navigator.userAgent);
    history.scrollRestoration = isSafari ? 'auto' : 'manual';
  }, []);

  // 2. Google Analytics page view tracking
  useEffect(() => {
    const handleRouteChange = (url: string) => {
      gtag('event', 'pageview', { event_label: url });
    };
    router.events.on('routeChangeComplete', handleRouteChange);
    return () => router.events.off('routeChangeComplete', handleRouteChange);
  }, [router.events]);

  return <Component {...pageProps} />;
}

2. Next.js Router Events API

// Pages Router provides events for navigation lifecycle:
const { events } = useRouter();

events.on('routeChangeStart', (url) => {
  // Navigation started — show loading indicator
});

events.on('routeChangeComplete', (url) => {
  // Navigation complete — hide loading, track analytics
});

events.on('routeChangeError', (err, url) => {
  // Navigation failed — handle error
});

Used by usePendingRoute and _app.tsx for GA tracking

3. getStaticPaths — Pre-generate All Pages

Static site generation means every docs page is rendered at build time. This recursive scan discovers all markdown files and returns a path object for each one, so Next.js knows exactly which HTML files to produce.

export async function getStaticPaths() {
  // Recursively find ALL .md files
  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)        // 🔄 Recursive!
          : res.slice(rootDir.length + 1);
      })
    );
    return files.flat().filter(file => file.endsWith('.md'));
  }

  const files = await getFiles(rootDir);
  const paths = files.map(file => ({
    params: { markdownPath: getSegments(file) },
  }));

  return { paths, fallback: false };
  //       ↑ All pages pre-rendered at build time (SSG)
}

4. next.config.js Key Settings

const nextConfig = {
  pageExtensions: ['jsx', 'js', 'ts', 'tsx', 'mdx', 'md'],
  reactStrictMode: true,               //  Double-render in dev to catch bugs
  experimental: {
    scrollRestoration: true,           //  Restore scroll on back/forward
    reactCompiler: true,               //  React Compiler (auto-memoization!)
  },
  async rewrites() {
    return {
      beforeFiles: [
        // Serve .md files directly (for LLM agents / raw markdown)
        { source: '/:path*.md', destination: '/api/md/:path*' },
      ],
    };
  },
  webpack: (config) => {
    // Custom aliases, shims, and bundle analysis
    config.resolve.alias['use-sync-external-store/shim'] = 'react';
    return config;
  },
};

React 19 Features Used

Cutting-edge React in production

click on bottom arrow to read and left arrow to move to nest topic

React 19 in This Codebase

React.dev is already running React 19 in production. The experimental React Compiler auto-memoizes components, while the serialization pipeline uses the new transitional element symbol for server-client handoff.

  • React Compiler experimental
    // next.config.js
    experimental: { reactCompiler: true }
    
    // Auto-memoizes components and hooks — no manual useMemo/useCallback needed!
    // Also: eslint-plugin-react-compiler checks for rules-of-react violations
  • React Server Components (RSC) partial
    // SandpackRSC provides a client-side RSC simulation
    // react-server-dom-webpack is a devDependency
    // Real RSC adoption is limited — mostly demo sandboxes
  • $$typeof: Symbol.for('react.transitional.element')
    // Used in the serialization pipeline
    // This is the React 19 element type symbol

React Compiler — What It Does

// BEFORE React Compiler:
function Component() {
  const handleClick = useCallback(() => { ... }, []);
  const items = useMemo(() => data.filter(...), [data]);
  return <MemoChild onClick={handleClick} items={items} />;
}

// WITH React Compiler:
function Component() {
  // The compiler automatically memoizes these!
  const handleClick = () => { ... };
  const items = data.filter(...);
  return <Child onClick={handleClick} items={items} />;
}
//  Cleaner code, same performance!

The codebase still has manual useMemo/memo in places — the compiler is experimental, and not everything is auto-optimized yet.

Anti-Patterns & "Hacks"

What the codebase itself admits is problematic

click on bottom arrow to read and left arrow to move to nest topic

1. Build-Time eval()

The MDX pipeline compiles JSX strings into executable code at build time. Because this happens on the server with trusted source files, eval() is acceptable here. Never use it with user-generated or CMS content.

//  From compileMDX.ts:
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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);
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  • eval() is dangerous with untrusted input
  • Safe here because: build-time only, trusted content authors, not user-generated
  • The comment explicitly warns: NEVER do this with CMS content!

2. Serializing React Elements to JSON

//  Custom serialization of React trees
// This is what React Server Components does natively now!

function stringifyNodeOnServer(key, val) {
  if (val?.$$typeof === Symbol.for('react.transitional.element')) {
    return ['$r', val.type, val.key, val.props];
  }
  return val;
}
  • This is a custom RSC-like pipeline built before RSC existed
  • The code has a TODO: "Replace with React.cache + React.use when migrating to Next.js App Router"
  • Works for static content — but wouldn't work for dynamic server components

3. Known Hacks in the Code

Hack Location Status
// HACK. Fix up the data structures instead. TopNav.tsx Mutates routeTree if only one child
// This is kind of a lie. _app.tsx Safari scroll restoration workaround
// HACK: Fix up the data structures instead. TopNav.tsx routeTree normalization hack
as any type casts Multiple files TypeScript escape hatches (forwardRefWithAs, lazy imports)
@ts-ignore / @ts-expect-error Multiple files Used sparingly with explanations

What NOT to Do (General Lessons)

These anti-patterns are not theoretical. They are real mistakes the codebase actively avoids or explicitly warns against in comments. Treat them as guardrails, not suggestions.

  1. Don't add a state library prematurely. This site has zero — useState + Context + URL is enough.
  2. Don't wrap everything in useMemo/useCallback. The React Compiler now handles this. Profile first.
  3. Don't use React.FC. This codebase never uses it. Plain functions with typed props are cleaner.
  4. Don't put Context providers at the root unnecessarily. Wrap them as close to consumers as possible.
  5. Don't ignore the dependency array. The eslint-plugin-react-hooks is there for a reason.
  6. Don't eval() user content. The codebase explicitly warns about this.
  7. Don't use default exports for everything. Named exports are preferred for tree-shaking.
  8. Don't skip cleanup in useEffect. Every effect with subscriptions/timers has a cleanup function.

Best Practices Summary

What to adopt from this codebase

click on bottom arrow to read and left arrow to move to nest topic

Component Design

  1. One component, one file. Each icon, each MDX element, each layout piece gets its own file.
  2. Barrel exports. index.tsx files re-export from sibling files for clean imports.
  3. Interface for props. Always typed, always at the top of the file (or imported).
  4. Variant map over if/else. Configuration objects scale better than condition chains.
  5. Composition over configuration. The Page component composes layout pieces rather than accepting dozens of boolean flags.

State & Data Flow

  1. URL as the source of truth. Navigation state, active section, breadcrumbs — all derived from the URL.
  2. Context for shared document data. TOC, languages, error params — things many components need.
  3. useState for UI state. Open/closed toggles, form inputs, selected indices.
  4. useRef for mutable non-render values. Timer IDs, previous values, DOM nodes.
  5. Lift state up minimally. State lives in the closest common ancestor.

Performance

  1. Lazy load heavy components. React.lazy() for search modal, code playgrounds.
  2. Prefetch likely-needed code. webpackPrefetch: true for CodeBlock.
  3. IntersectionObserver > scroll events. For scroll-spy, "is scrolled" detection.
  4. Throttle/debounce scroll handlers. 100ms timeout prevents jank.
  5. startTransition for non-urgent updates. Search opening, route transitions.

TypeScript

  1. Discriminated unions for variants. TypeScript exhaustiveness checking is your friend.
  2. Symbol for sentinel values. Prevents collision with real data in Context defaults.
  3. Recursive types for trees. RouteItem, NestedTocNode — self-referencing interfaces.
  4. Generic components when needed. forwardRefWithAs for polymorphic components.
  5. @ts-expect-error with comments. When you must, explain why.

Real-World Wisdom

After studying tens of thousands of lines of production React, a few truths emerge. They are not exciting, but they are reliable.

  1. You don't need a state management library until you really do. Use what React gives you first.
  2. The React Compiler is production-ready enough for react.dev. It simplifies code by removing manual memoization.
  3. Sometimes a "hack" is okay — as long as it's commented and you understand the tradeoffs.
  4. MDX is incredibly powerful when you provide a rich component library. Content authors get superpowers.
  5. Build-time compilation (SSG) is great for documentation — fast, SEO-friendly, and simple.
  6. Dark mode is a first-class citizen. Every component handles light and dark variants.
  7. Accessibility matters. aria-labels, semantic HTML, keyboard navigation everywhere.

Quick Reference Cheatsheet

These templates are distilled directly from the react.dev source. Copy them into your own projects and adapt the naming to your team's conventions.

Patterns you can copy today

click on bottom arrow to read and left arrow to move to nest topic

Component Template

import cn from 'classnames';
import * as React from 'react';

interface MyComponentProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';
  className?: string;
}

export function MyComponent({
  children,
  variant = 'primary',
  className,
}: MyComponentProps) {
  return (
    <div className={cn('base-classes', {
      'bg-blue-500': variant === 'primary',
      'bg-gray-500': variant === 'secondary',
    }, className)}>
      {children}
    </div>
  );
}

Custom Hook Template

import { useState, useRef, useEffect } from 'react';

export function useMyHook(dependency: string) {
  const [value, setValue] = useState<string | null>(null);
  const timeoutRef = useRef<number | null>(null);

  useEffect(() => {
    // Setup
    const handler = () => {
      if (timeoutRef.current === null) {
        timeoutRef.current = window.setTimeout(() => {
          timeoutRef.current = null;
          setValue(/* computed value */);
        }, 100); // throttle
      }
    };

    window.addEventListener('event', handler);

    // Cleanup!
    return () => {
      window.removeEventListener('event', handler);
      if (timeoutRef.current !== null) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, [dependency]);

  return value;  // Return minimal API
}

Context Template

import { createContext, useContext } from 'react';

// 1. Define the type
interface MyContextValue {
  data: string;
  update: (val: string) => void;
}

// 2. Create context with a sentinel default
const missingContext = Symbol('missing');
const MyContext = createContext<MyContextValue | typeof missingContext>(
  missingContext
);

// 3. Custom hook with validation
export function useMyContext() {
  const ctx = useContext(MyContext);
  if (ctx === missingContext) {
    throw new Error('useMyContext must be used within <MyProvider />');
  }
  return ctx;
}

// 4. Export the provider (optional, or use Context.Provider directly)
export { MyContext };

What's Next?

The best way to learn is to read the actual source. Start with the catch-all route and trace a single page from disk to DOM. Every pattern shown here is waiting in the codebase to be discovered.

To go deeper:

  • Read the actual source: src/components/Layout/Page.tsx
  • Trace how one page renders: start at [[...markdownPath]].js
  • Study the MDX compilation: src/utils/compileMDX.ts
  • Explore Sandpack: src/components/MDX/Sandpack/

Key files to bookmark:

  1. src/pages/[[...markdownPath]].js — The magic catch-all route
  2. src/components/MDX/MDXComponents.tsx — All 50+ custom MDX components
  3. src/components/Layout/Page.tsx — The layout orchestrator
  4. src/hooks/usePendingRoute.ts — Perfect custom hook example
  5. src/utils/compileMDX.ts — Server-side MDX pipeline

Happy Learning!

This slidedeck was generated from the actual react.dev source code.
Every pattern shown here is in production, serving millions of developers.

Press Esc for slide overview · S for speaker notes · F for fullscreen