react.dev
Stack: Next.js 15 · React 19 · TypeScript · Tailwind CSS · MDX
Pages Router
React Compiler
Sandpack
This deck extracts patterns from the actual react.dev source code. Every example is production-tested, serving millions of developers every month.
How react.dev is organized
The site runs on a carefully chosen stack that balances performance, maintainability, and author experience. No bloat, no novelty for novelty's sake.
| Framework | Next.js 15.1.11 (Pages Router) |
| React | React 19 + React Compiler |
| Language | TypeScript 5.7 |
| Styling | Tailwind CSS 3 + classnames |
| Content | MDX (Markdown + JSX) |
| Search | Algolia DocSearch |
| Playground | CodeSandbox Sandpack |
| UI Primitives | Headless UI + Radix UI |
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
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
src/content/ for all .md filesgetStaticPaths() generates all URL pathsgetStaticProps() reads each MDX filenode_modules/.cache/Client Side
reviveNodeOnClient() deserializesOne 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!
}
How real React components are structured
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 */);
}
interface?React.FC (it's not used anywhere in this codebase!)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
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>
);
}
currentColor for easy theming1em sizing makes icons respect parent font sizeThe 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
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
// 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';
Extracting reusable stateful logic
usePendingRouteNavigating 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;
};
usePendingRoute GreatGreat 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.
currentRoute tracks without re-renders[events] ensures effect re-runs if router events changesetPendingRoute triggers re-render to show loading UI💡 Rule of thumb: useRef for values that shouldn't trigger re-renders, useState for values that should.
useTocHighlightA 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 };
}
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';
}
useRouter()usePendingRoute, useTocHighlightusePendingRoute returns just a string | null, not an objectProp drilling is dead — long live 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 |
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
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!
};
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
Content as Components — the heart of react.dev
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!
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
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!
};
// 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
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} />;
Real-world optimization techniques
React.lazy() + Dynamic ImportsNot 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 renderswebpackPrefetch downloads during browser idle timeReact.memo() with Custom ComparatorMemoization 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-equalstartTransition — 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.
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} />;
}
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)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!
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
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
Advanced types from production code
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
// 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
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
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
classnames Libraryimport 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
No Redux, no Zustand — just React
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.
💡 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.
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
// 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
Rendering tree structures elegantly
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>
);
}
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>
);
}
// 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
}
Live, editable code examples
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>
);
}
initMode: 'user-visible' — sandbox only loads when scrolled into viewuseSandpackLint)SandpackRSC variant for React Server Component examplesPages Router done right
_app.tsx — The Root ComponentThe 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} />;
}
// 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
getStaticPaths — Pre-generate All PagesStatic 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)
}
next.config.js Key Settingsconst 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;
},
};
Cutting-edge React in production
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.
// 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
// 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
// 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.
What the codebase itself admits is problematic
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// 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;
}
| 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 |
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.
React.FC. This codebase never uses it. Plain functions with typed props are cleaner.What to adopt from this codebase
index.tsx files re-export from sibling files for clean imports.Page component composes layout pieces rather than accepting dozens of boolean flags.React.lazy() for search modal, code playgrounds.webpackPrefetch: true for CodeBlock.forwardRefWithAs for polymorphic components.After studying tens of thousands of lines of production React, a few truths emerge. They are not exciting, but they are reliable.
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
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>
);
}
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
}
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 };
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:
src/components/Layout/Page.tsx[[...markdownPath]].jssrc/utils/compileMDX.tssrc/components/MDX/Sandpack/Key files to bookmark:
src/pages/[[...markdownPath]].js — The magic catch-all routesrc/components/MDX/MDXComponents.tsx — All 50+ custom MDX componentssrc/components/Layout/Page.tsx — The layout orchestratorsrc/hooks/usePendingRoute.ts — Perfect custom hook examplesrc/utils/compileMDX.ts — Server-side MDX pipeline
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