DevTools
The built-in DevTools panel lets you inspect your running application during development. It covers server process health, RSC payload contents, cache behavior, the route tree, outlet layout, remote and live components, worker threads, and server logs — all without leaving the browser.
Pass --devtools when starting the development server:
pnpm react-server ./App.jsx --devtools
Or add it to your config so it stays on across restarts:
// react-server.config.mjs
export default {
devtools: true,
};
DevTools is development-only. It is never included in production builds.
Once enabled, a small floating button with the react-server logo appears in the bottom-right corner. Click it to open the panel, or use the keyboard shortcut:
| Shortcut | Platform |
|---|---|
| Ctrl+Shift+D | Windows / Linux |
| Cmd+Shift+D | macOS |
The same shortcut closes the panel again.
The toolbar at the top of the panel has four dock-mode buttons. Click one to switch where the panel sits:
- Bottom (default) — the panel docks to the bottom edge. Your page shrinks vertically to make room.
- Left / Right — the panel docks to the side. Your page shrinks horizontally.
- Float — the panel becomes a free-floating window you can drag by its toolbar and resize from the bottom-right corner.
In any docked mode, drag the handle between the panel and your page to resize it.
All panel state — open/closed, dock mode, size, and float position — is saved to localStorage so it persists across page reloads.
The / button in the toolbar toggles between light and dark mode. DevTools picks up your app's theme automatically by checking, in order:
- A
darkorlightclass on<html> - A
dark=1ordark=0cookie - The system
prefers-color-schememedia query
Clicking the toggle updates both the panel and your host page, and persists the choice via a cookie.
The panel has nine tabs. If the panel is too narrow to fit them all, the ones that don't fit collapse into an overflow menu behind the » button on the right side of the tab bar. The currently active tab is always kept visible, even if it would normally overflow.
The Status tab shows your development server's resource usage. It is a "use live" component that streams fresh numbers every second.
Three cards are displayed:
| Card | What it shows |
|---|---|
| Process | Node.js version, PID, platform/architecture, and uptime |
| CPU | Usage percentage as a gauge (green under 50 %, amber up to 80 %, red above), core count, and 1/5/15-minute load averages |
| Memory | Heap used versus heap total, RSS, external allocations, array buffers, and OS-level free/total memory — each metric has its own gauge bar |
Use this tab to watch for memory growth over time (a sign of leaks) or to check whether a rendering change is spiking CPU.
The Payload tab captures every React Server Components Flight response and decodes it so you can see exactly what the server sent.
At the top of the tab, a stacked bar breaks down the initial HTML document into three segments:
| Segment | Color | What it contains |
|---|---|---|
| RSC Payload | Green | The Flight stream embedded in the HTML |
| Hydration | Purple | Serialized "use cache" entries inlined for the client |
| HTML | Blue | Everything else (markup, scripts, styles) |
Total and transferred sizes are shown below the bar. If the transferred size is smaller, the server is compressing the response.
The dropdown lists every payload captured during the session. Each entry shows the URL, total byte size, and chunk count. Select one to inspect it.
| Label | What it captures |
|---|---|
| ⚡ initial | The Flight data from the first page load |
| 🔄 stream | Additional chunks that arrived after the initial response (streamed through Suspense boundaries) |
| 🧭 nav | Flight responses triggered by client-side navigations |
Below the summary badges, the chunk list shows every Flight protocol row:
| Column | Meaning |
|---|---|
| ID | The chunk identifier |
| Type | A colored tag: Model (component tree data), Module (client component import), Error, Hint (resource preload), Debug, Text, Binary, or Console |
| Size | How many bytes that chunk occupies |
| Data | A truncated preview of the chunk content |
Type the name of a tag (e.g., module) or any content substring into the filter field to narrow the list.
Expand the Client References section to see every "use client" module the server referenced. Each entry shows the module path and exported name. Hover over a reference to highlight the corresponding DOM element on the page with a green overlay. Click a reference to scroll the chunk list to the matching Module chunk.
The Cache tab records every "use cache" call — both on the server and on the client — and shows whether it was a hit, a miss, or a revalidation.
Each row in the event list contains:
| Column | Meaning |
|---|---|
| Time | When the call happened |
| Type | hit (served from cache), miss (computed and stored), or revalidate (re-computed in the background) |
| Provider | request (lives for one request only) or default (persists across requests) |
| Function | The cached function name and the arguments it was called with |
| Source | File path and line number — click to open in VS Code |
| TTL | Shown on misses and revalidations; how long the entry is valid |
The toolbar at the top shows aggregate hit/miss/revalidation counts. Use the filter buttons to show only one type at a time.
Hover over any default-provider cache event and click ✕ on the right. This immediately invalidates the entry on the server and removes it from the event list. The next call to that function will be a miss. Request-scoped cache entries cannot be invalidated manually because they are discarded at the end of the request anyway.
If your page uses "use cache" with request-scoped caching, the server serializes those cache entries into the HTML so the client can hydrate without re-fetching. The collapsible request cache section at the top of the Cache tab lists these hydrated entries with their hashed keys, byte sizes, and a preview of the serialized data.
The Routes tab shows the full route tree produced by the file-system router. Every page, layout, middleware, API route, error boundary, loading state, fallback, template, and named outlet is listed.
| Control | How to use it |
|---|---|
| Text filter | Type a path fragment, filename, outlet name, or HTTP method to narrow the list |
| Type buttons | Click a type (page, layout, middleware, api, error, loading, fallback, template, outlet) to show only that type. The count badge on each button tells you how many routes of that type exist |
| Active toggle | Click "active" to hide everything that does not match the current server pathname. This is useful to see exactly which layouts, middlewares, and pages are involved in rendering the current URL |
Routes matching the current server pathname are highlighted with a green left border. The matching accounts for dynamic segments ([param]), optional parameters ([[optional]]), and catch-all patterns ([...slug]). Layouts, middlewares, and templates match as prefixes (they are active for all child paths), while pages must match exactly.
Note: the pathname used for matching is the server pathname (after any rewrites), not the client-visible URL.
Each route row shows a file-type icon (React/JSX, TypeScript, MDX, etc.) and the relative source path. Click the source path to open the file in VS Code.
Below the file-router tree, a Component Routes section appears if your app registers routes via <Route> components in code. Each entry shows the route pattern, type (server, client, or fallback), and flags like remote, exact, or loading. Active component routes display the matched path parameters inline.
The Outlets tab lists every named outlet currently rendered on the page. Outlets are the named slots (@sidebar, @content, etc.) that layouts receive as props, plus PAGE_ROOT for the root rendering slot.
| Field | Meaning |
|---|---|
| Name | The outlet identifier |
| URL | The URL the outlet content was loaded from (if applicable) |
| Badge | How the outlet is managed: router (file-router), remote (fetched from another server), live (streaming), defer (lazily loaded), or static |
Hover over any outlet card. A colored dashed rectangle appears around that outlet's rendered area on the page, with a label showing the outlet name. Each outlet gets a distinct color so you can visually map the cards to sections of the page.
The highlight overlay handles edge cases: hidden marker elements, outlets that render as multiple sibling elements (the overlay covers the union bounding rect), and full-screen backdrop elements (which are filtered out so the actual content is highlighted).
| Action | What it does |
|---|---|
| Scrolls the page to the outlet's position | |
| ↻ | Re-renders that single outlet without a full page reload — useful when you want to test server-side changes for one section of the page without navigating away |
If your app uses <RemoteComponent> to load RSC content from other react-server instances, the Remotes tab shows each one.
Each card shows:
| Field | Meaning |
|---|---|
| Remote URL | The URL the component is fetched from. Click to open it in a new browser tab |
| Outlet name | The outlet this remote component renders into |
| TTL badge | How long the response is cached |
isolate badge | The component is rendered in a sandbox |
defer badge | The component is loaded lazily |
live badge | The component streams real-time updates |
Hover to highlight the remote component on the page. Click to scroll to it. Click to jump to its outlet in the Outlets tab.
The Live tab monitors every "use live" component — async generator functions on the server that yield successive renders to the client.
Each card shows the component's name, its current state, and runtime statistics:
| State | Meaning |
|---|---|
starting | The generator is being initialized |
waiting | Waiting before the next yield |
running / connected | Actively streaming to the client |
finished | The generator returned normally |
aborted | The client disconnected |
error | The generator threw an exception (the error message is shown inline) |
A streaming badge appears while data is being sent. The card also shows how many times the generator has yielded, when the last yield happened, and when the component started. If the component is loaded from a remote server, a remote badge appears.
The Workers tab tracks "use worker" threads — both server-side Worker Threads (Node.js) and client-side Web Workers.
The toolbar summarizes how many workers exist, broken down by type. Use the filter buttons to show only server or only client workers.
Each worker entry shows:
| Field | Meaning |
|---|---|
| Type | server or client |
| State | Spawning (thread starting), Ready (idle), Error (last call failed), or Restarting |
| Module path | The file that defines the worker, shortened to the last few path segments |
| Call count | Total invocations and how many are currently in flight |
| Error / restart counts | Helps spot unstable workers |
| Last function | The name of the most recently called export |
| Timing | When the worker was spawned and when it was last invoked |
The Logs tab streams all server terminal output (stdout and stderr) into the browser so you don't need to switch back to the terminal.
| Control | How to use it |
|---|---|
| Stream buttons | Click stdout or stderr to show only that stream. The count on each button tells you how many entries of that type exist |
| Search field | Type any text to filter log entries. The search matches against the plain text content with ANSI codes stripped |
Server logs often contain ANSI escape codes for colors and text styling. The Logs tab renders these faithfully — 4-bit, 8-bit, and 24-bit (true-color) sequences are all supported, including bold, italic, underline, and dim. What you see in the panel matches what you'd see in a terminal.
By default the log view stays pinned to the bottom so new entries scroll into view as they arrive. Scroll up to pause auto-scrolling. A Scroll to bottom button appears at the bottom of the panel — click it to resume.
Click Clear in the toolbar to empty the log buffer.
Several tabs let you hover over an item to highlight the corresponding element on the page:
- In the Payload tab, hovering a client reference highlights the DOM element rendered by that client component (green overlay).
- In the Outlets tab, hovering an outlet card highlights its rendered region (each outlet gets a unique color).
- In the Remotes tab, hovering a remote card highlights its rendered region the same way.
The overlay is a colored dashed rectangle with a label badge showing the name. It repositions in real time as you scroll or resize the page.
The panel UI runs inside an iframe served from /__react_server_devtools__/*. This keeps the DevTools React tree separate from your app — it won't interfere with your component state or styling.
Three communication channels connect the parts:
- Host page → Iframe (
postMessage): RSC payloads (captured by interceptingfetch), outlet registrations, component routes, page-level size stats, navigation events, and theme changes. - Server → Iframe (Socket.IO,
/__devtools__namespace): live component state, server-side cache events, worker status, server logs, and the file-router manifest. The connection is established automatically when the panel opens. - Iframe → Server (Socket.IO): cache invalidation requests and outlet refresh commands travel back over the same socket.