Next.js Components: 92 Files, Five Layers, One Client Boundary
Next.js names two component types. This codebase runs five layers across 92 files, and 79% of its client components live in just one of them.
Next.js's own docs describe two kinds of component: Server and Client. This codebase has 92 of them, and slotting each into just those two buckets would flatten a real distinction — a Button and a page's entire HomeTemplate are both "Server Components," but one is a 40-line primitive and the other composes ten sections. The actual taxonomy this site runs on has five layers, and the Server/Client line falls in a different place at each one.
The short answer
Next.js components are just React components rendered either on the server (the default) or the client (opt-in via "use client"). This codebase organizes its 92 of them into five explicit layers — atoms, molecules, organisms, templates and pages — and the client boundary concentrates almost entirely in one layer: molecules, not pages, carry 79% of every client component on the site.
What "Next.js components" usually means
The docs and tutorial results dominating this search — Next.js's own API reference, a GeeksforGeeks walkthrough, a beginner's Medium guide — all teach the same two-way split: Server Components (the default, can fetch data, never ship JS to the browser) and Client Components (opt in with "use client", needed for state, effects, and browser APIs). That's accurate, and it's also the only taxonomy most of them offer — a page either is or isn't interactive, full stop.
A five-layer taxonomy, not two types
This codebase's src/components directory sorts every file into one of four layers below the page itself, each with a stated role and an import rule that only lets it depend on the layers beneath it:
| Layer | Count | Role | Example |
|---|---|---|---|
| Atoms | 7 | Smallest primitive, no business content | Button, Container, Logo |
| Molecules | 41 | One reusable unit composed of atoms | FaqItem, TemplateCard, BuyButton |
| Organisms | 33 | A full page section, maps a data array | Header, Hero, Footer |
| Templates | 11 | Page-level ordering of organisms | HomeTemplate, PricingTemplate |
| Pages | 34 (src/app) | Route entry, renders one template | app/page.tsx, app/pricing/page.tsx |
7 + 41 + 33 + 11 = 92 components below the page layer, plus a fifth layer (src/data) of typed content arrays that owns no rendering at all.
Where the client boundary actually falls
Grepping every file in each layer for "use client" gives a distribution the two-type model doesn't show:
| Layer | Client components | Share |
|---|---|---|
| Atoms | 0 of 7 | 0% |
| Molecules | 19 of 41 | 46% |
| Organisms | 5 of 33 | 15% |
| Templates | 0 of 11 | 0% |
| Total | 24 of 92 | 26% |
Atoms and templates are 100% Server Components — an atom is too small to need state, and a template only orders other components, so neither ever has a reason to opt in. Organisms mostly stay server-rendered too: Header needs "use client" for its mobile menu, but Hero, Trust, TechStack, Features and 28 others don't. Molecules are the outlier — nearly half of them are client components, and they account for 19 of the 24 client components on the entire site (79%). That's FaqItem tracking which accordion row is open, TemplateGallery tracking the active slide, BuyButton tracking checkout state, DownloadMenu and PreviewModal each owning their own open/closed state.
The sixth layer that renders nothing at all
Below organisms sits a layer Next.js's own model has no equivalent for: src/data, 16 typed files holding the actual copy an organism maps over — features.tsx, techStack.tsx, pricing.ts and their siblings. None of them is a component; each exports a typed array, and the matching organism is the only place that array gets rendered. That separation is what lets a molecule stay a "pure" reusable unit — the rule that "molecules never import runtime values from data files" means a FeatureCard molecule has no idea it's rendering feature #3 of 6; the Features organism reads src/data/features.tsx and passes one item down as props. Content changes happen in one typed file; rendering logic never has to change alongside it.
Two real components, two real layers
Container is as small as an atom gets — no state, no client boundary possible even if it wanted one:
// src/components/atoms/Container.tsx
export default function Container({ className = "", children }: {
className?: string;
children: React.ReactNode;
}) {
return <div className={`container ${className}`.trim()}>{children}</div>;
}
FaqItem, a molecule, is the opposite case — it exists because it needs client state:
// src/components/molecules/FaqItem.tsx (shape)
"use client";
export default function FaqItem({ question, answer }: { question: string; answer: string }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(!open)}>{question}</button>
{open && <p>{answer}</p>}
</div>
);
}
Nothing above FaqItem in the tree needs to be a client component just because this one is — Faq (the organism) and PricingTemplate stay Server Components, rendering the accordion's initial markup on the server and letting the one molecule that needs interactivity own it alone.
The organism that does need "use client", in full
Header is one of the 5 client organisms, and its own code shows why the boundary lands on the molecule/organism line rather than the page: it needs state for exactly one thing — whether the mobile drawer is open — and nothing else about the header cares:
// src/components/organisms/Header.tsx (trimmed)
"use client";
export default function Header() {
const [navOpen, setNavOpen] = useState(false);
const [isSignedIn, setIsSignedIn] = useState(false);
return (
<>
<button aria-label={navOpen ? "Close menu" : "Open menu"} aria-expanded={navOpen}>
{/* hamburger/close icon, driven by navOpen */}
</button>
<div className={navOpen ? "visible translate-x-0" : "invisible translate-x-full"}>
<nav aria-label="Primary">{/* nav links */}</nav>
</div>
</>
);
}
Everything below Header in the tree — Logo, the nav links, the Button atoms for sign-in/sign-up — could themselves be Server Components rendered as children; Header only needs to be a Client Component because something in the subtree has to own navOpen, and the organism is the natural place to put it since the state controls the organism's own layout (whether the drawer is visible), not a smaller reusable unit's internal state the way FaqItem's open flag does.
Why the boundary sits at molecules, not pages
The common mental model treats "client" as a page-level or route-level decision — a "use client" page versus a server page. This codebase's numbers say the opposite: interactivity is a property of specific, small, reusable units (an accordion row, a checkout button, a mobile menu), not of whole pages. A page like /pricing composes a PricingTemplate that is itself a Server Component, which renders eight FaqItem molecules that are each independently interactive. The page was never going to be "a client page" — it's a server-rendered shell with several small, independent islands of state, which is exactly what the Atomic Design layering makes easy to keep track of and the flat Server/Client model doesn't name at all.
The comparison
| Next.js's own model | This codebase's taxonomy | |
|---|---|---|
| Number of categories | 2 (Server, Client) | 5 (atoms, molecules, organisms, templates, pages) + a data layer |
| What decides the category | Whether the file has "use client" | The component's role in the page (primitive, unit, section, layout, route) |
| Where interactivity concentrates | Not specified — any component can opt in | Empirically molecules: 79% of this site's client components live there |
| Import rules | None enforced by the model itself | A level may only import from levels below it |
| Content ownership | Not addressed | Organisms and below take content via props; only src/data holds copy |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| A whole page re-renders on every interaction | "use client" was added at the template or page level instead of the specific interactive molecule | Push the boundary down — only the component that owns state needs the directive |
An atom "needs" "use client" | It's importing something stateful, meaning it's actually a molecule | Re-classify it, or extract the stateful part into a child molecule the atom composes |
| A molecule imports an organism | Violates the layer's import rule, usually to reach a data file's shape | Import the type only, and have the organism pass the data down as a prop |
| Server/Client split looks "too small" to matter | Only counting whole pages, not components | Count per-component, per-layer — a 26%-client site can still ship page shells that are 100% server |
| New component doesn't fit atoms/molecules/organisms cleanly | It mixes primitive markup with page-section responsibility | Split it: the reusable unit becomes a molecule, the section-owning wrapper becomes the organism |
Frequently asked questions
Are Server and Client Components the only "types" of component in Next.js? At the framework level, yes — that's the API. Nothing stops a codebase from layering its own taxonomy on top, which is what Atomic Design (atoms/molecules/organisms/templates) does here.
Why are molecules more likely to be Client Components than organisms? Because interactivity in this codebase is scoped to the smallest unit that needs it — an accordion row, a gallery, a menu — rather than the section that contains it. The organism composing several molecules stays a Server Component even when one of its children isn't.
Does using more layers slow down the build? No — layering is a source-organization choice, not a runtime one. The Server/Client split (24 of 92 files) is what determines what ships to the browser; the five-layer taxonomy is how those 92 files are organized on disk.
Can a template ever be a Client Component? Nothing prevents it, but none of this site's 11 are. A template's only job is ordering organisms, which needs no state — the moment a template wants its own interactivity, that's usually a sign the interactive part belongs in a molecule instead.
Templates in this post
ASoc Ledger, ASoc Lens and ASoc Magnet are Next.js + Tailwind landing page templates, built on the same five-layer component taxonomy described above.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
