Skip to main content
ASoc
Tutorial

React DevTools Shows 24 of This Site's 92 Components

Open the Components tab on an App Router page and the tree looks empty. 68 of 92 components ran at build time and never mount in a browser.

The ASoc Team9 min read

Install React Developer Tools, open the Components tab on this site's home page, and you will find about eight entries under the root. The page is built from 92 components across five layers. The gap is not a bug in the extension: 68 of those 92 are Server Components that ran during next build and have no browser-side instance to select. In an App Router codebase, the Components tab stops being a picture of your UI and becomes a picture of your client bundle.

The short answer

React Developer Tools is a browser extension that adds two panels to DevTools: Components, an inspectable tree of mounted React components with live props and state, and Profiler, a commit-by-commit render recorder. It is available for Chrome, Firefox and Edge, and for React Native and non-browser targets via the standalone react-devtools package.

What it can see, layer by layer

Every component in this repository is one of five layers, and "use client" appears in exactly 24 of the 92 files:

LayerComponentsClient ("use client")Visible in the Components tab
Atoms70none
Molecules411919
Organisms3355
Templates110none
Total922424 (26%)

Two more files outside src/components carry the directive — src/app/error.tsx and src/app/global-error.tsx, which must be Client Components because error boundaries are a client-side concept — plus two hooks, src/lib/useOwnedProducts.ts and src/lib/useWishlist.ts.

The distribution is the interesting part. Atoms and templates are 100% server: an atom is a styled primitive with no state, and a template is a layout that orders organisms. Molecules carry 19 of the 24, because a molecule is where a single unit of interaction lives — a modal, a picker, a form, a toggle. The rule that produces that distribution is not "is this interactive?" but "what does this file drag across the boundary with it?"

What actually mounts on the home page

HomeTemplate composes ten organisms. Nine of them — Hero, FeaturedTemplates, TechStack, Features, FeatureTabs, UseCases, Testimonials, Blog, Footer — are Server Components. Their output is HTML, generated once for each of this build's 584 prerendered pages.

So the Components tree on / is:

  • Header, the only client organism on the page (it owns the mobile-menu open state)
  • six TemplateCards, one per featured product, each nesting WishlistButton and PreviewModal
  • Analytics, Vercel's client component from @vercel/analytics/next

That is the whole list. Features renders fourteen feature cards from a typed data array and not one of them is selectable, because by the time the browser has the page, Features has been HTML for hours.

This is worth sitting with the first time you see it, because the instinct is to think the extension failed to attach. It did attach. It is telling you the truth about what you shipped.

Reading the tree as a bundle map

The useful reframing: the Components tab is your client boundary, rendered. Anything in that tree is JavaScript a visitor downloaded, parsed and hydrated. Anything absent cost them HTML only.

That makes it a review tool as much as a debugging one. Two findings in this codebase came out of exactly this reading:

A card that should not have been six components deep. The "More templates" rail that closes every product page originally rendered TemplateCard — the same client molecule as above, carrying a preview modal, a wishlist button and an ownership lookup. On a navigation rail, none of that is used. It now renders RelatedTemplateCard, a Server Component, so six cards' worth of interactive machinery stopped shipping on 111 product pages.

An auth stack on pages with no account UI. Header renders site-wide and useOwnedProducts renders behind every templates grid; both imported the Supabase browser client at module scope. That put @supabase/ssr plus auth-js — about 68 KiB over the wire, 255 KiB parsed — into the initial bundle of /, /blog, /docs and /pricing. The fix is in src/lib/supabase/lazyClient.ts: both call sites only touch the client inside an effect, so the import waits for the effect too, and a cookie probe skips it entirely for signed-out visitors.

// src/lib/supabase/lazyClient.ts
export function hasAuthCookie(): boolean {
  if (typeof document === "undefined") return false;
  return /(?:^|;\s*)sb-.+?-auth-token/.test(document.cookie);
}

export async function loadSupabaseClient(): Promise<BrowserClient> {
  const { createClient } = await import("@/lib/supabase/client");
  return createClient();
}

Neither of those is something the Components tab reports as a warning. Both are obvious the moment you look at a tree and ask why a component is in it.

The Profiler on an island

The Profiler records commits — each render React flushed to the DOM — with per-component timings. On a page of Server Components, that recording is scoped to the islands: click "record", open PreviewModal, stop, and you get the commits PreviewModal and its parent TemplateCard produced, not a whole-page render.

That constraint is mostly good news. A slow commit inside an island is genuinely a client-render problem, and the Profiler will name it. A slow page usually is not, and the Profiler will have nothing to say about it — which is itself the answer.

What to use for the other 68 components

QuestionRight toolNot React DevTools, because
Why is this page slow to load?Lighthouse / the Network panelLoad time here is HTML, images and the client bundle — none of it a React commit
Why is this component re-rendering?Profiler → "Why did this render?" (enable it in Profiler settings)Only applies to the 24 that render in the browser at all
What did a Server Component output?View source, or the prerendered HTML in .next/server/appThe component instance no longer exists anywhere
What is in the client bundle?next build --experimental-analyze — the webpack-era @next/bundle-analyzer plugin writes nothing under TurbopackThe Components tab shows what mounted, not how many bytes it cost
Is this component server or client?grep -rl '"use client"' srcFaster and complete; the tab only shows what the current page mounted
Why did a Server Component throw?The terminal running next dev, or the server logsThe error happened in Node, before any browser was involved

Installing it, and the standalone fallback

The browser extension is the normal path — install for Chrome, Firefox or Edge and two tabs appear in DevTools, Components ⚛ and Profiler ⚛. The tabs only appear on pages that actually run React; a fully static route with no client components may show them greyed out or missing, which again is information rather than a fault.

When the extension cannot reach the page — a non-browser target, a WebView, an iframe with a restrictive policy — the standalone app is the fallback:

npx react-devtools

That opens a window and prints a <script src="http://localhost:8097"> tag to add to your document before any React code runs. Worth knowing this site would need a CSP exception for it: next.config.ts sets script-src 'self' 'unsafe-inline' plus three named hosts, and localhost:8097 is not one of them.

Troubleshooting

SymptomCauseFix
Components/Profiler tabs never appearThe page runs no client-side React, or the extension has no host permission on that originCheck a page with a known client component; confirm the extension is enabled for the site in the browser's extension settings
The tree is far shallower than the pageMost components are Server Components — expected in the App RouterRead it as a client-boundary map, not a UI tree
Component names are all t, o, n in productionMinification renames functions; there is no displayName to fall back onDebug against next dev or a non-minified build; do not add display names just to read a production tree
"Why did this render?" is greyed outThe setting is off, or the recording predates itProfiler → settings gear → "Record why each component rendered while profiling", then record again
Editing a prop in the panel does nothing visibleThe value is re-derived on the next render from server-supplied propsServer-supplied props are not client state; change the source, not the panel
Profiler records zero commits on an interactionThe interaction changed no client state — it was a link, a form post, or a server round-tripLook at the Network panel instead; nothing re-rendered in the browser
Extension shows a different React version than your appAnother React instance on the page (an embedded iframe, a browser extension of your own)Check for duplicate React copies; the panel inspects whichever renderer registered first

Frequently asked questions

Does React DevTools work with the Next.js App Router? Yes, for everything that runs in the browser. Client Components appear in the tree with live props and state, and the Profiler records their commits. Server Components do not appear as selectable, inspectable components, because they executed on the server and never mounted client-side — there is no instance holding state for the panel to read.

Why does the Components tab look almost empty on my page? Usually because the page is mostly Server Components, which is the App Router working as intended. Check with grep -rl '"use client"' src: if the count is low, an empty-looking tree is the correct result. In this repo it is 24 files out of 92.

Is it safe to leave the extension installed while browsing normally? Yes. It injects a hook that detects React and does nothing on pages without it. It does surface component names and props to anyone with DevTools open, but so does the page's own JavaScript — never treat client-side props as a place to keep a secret. That rule holds regardless of the extension: in this codebase the real gates run server-side, in authorizeDownload and in the database's row-level security policies.

Can I profile a production build? You can, and you should when measuring — but names are minified. React ships a profiling build of react-dom for this; in practice, profile against next dev for readable names, then confirm the fix with Lighthouse against next build && next start, where the numbers are real.

Templates in this post

ASoc Pip, ASoc Press and ASoc Quest are Next.js + Tailwind landing page templates with the same shallow client boundary — the interactive pieces are isolated molecules, so what you inspect is a short list rather than the whole page.

Browse the full sets: Next.js landing page templates, Tailwind landing page templates.

Keep reading

Tutorial9 min read

E-Commerce in React: The Storefront With No Shopping Cart

This storefront sells 113 products with zero cart code: one Server Action resolves a fixed-tier checkout, and a slot-based entitlement engine replaces the order.

Read more
Tutorial10 min read

Email Signup Landing Pages: Where the Capture Point Actually Goes

Squeeze-page advice says strip the page to one form. Only 14 of 66 landing templates in this catalog do — audited against this storefront's own single, sitewide capture point.

Read more
Tutorial8 min read

forEach in TypeScript: 4 Uses Against 149 Maps

A census of a real TypeScript codebase: 4 forEach calls, 149 .map() and 137 for...of. What the four have in common, and the async trap that explains the ratio.

Read more