Skip to main content
ASoc
Tutorial

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.

The ASoc Team9 min read

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:

LayerCountRoleExample
Atoms7Smallest primitive, no business contentButton, Container, Logo
Molecules41One reusable unit composed of atomsFaqItem, TemplateCard, BuyButton
Organisms33A full page section, maps a data arrayHeader, Hero, Footer
Templates11Page-level ordering of organismsHomeTemplate, PricingTemplate
Pages34 (src/app)Route entry, renders one templateapp/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:

LayerClient componentsShare
Atoms0 of 70%
Molecules19 of 4146%
Organisms5 of 3315%
Templates0 of 110%
Total24 of 9226%

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 modelThis codebase's taxonomy
Number of categories2 (Server, Client)5 (atoms, molecules, organisms, templates, pages) + a data layer
What decides the categoryWhether the file has "use client"The component's role in the page (primitive, unit, section, layout, route)
Where interactivity concentratesNot specified — any component can opt inEmpirically molecules: 79% of this site's client components live there
Import rulesNone enforced by the model itselfA level may only import from levels below it
Content ownershipNot addressedOrganisms and below take content via props; only src/data holds copy

Troubleshooting

SymptomCauseFix
A whole page re-renders on every interaction"use client" was added at the template or page level instead of the specific interactive moleculePush 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 moleculeRe-classify it, or extract the stateful part into a child molecule the atom composes
A molecule imports an organismViolates the layer's import rule, usually to reach a data file's shapeImport the type only, and have the organism pass the data down as a prop
Server/Client split looks "too small" to matterOnly counting whole pages, not componentsCount 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 cleanlyIt mixes primitive markup with page-section responsibilitySplit 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.

Keep reading

Tutorial11 min read

A Next.js Contact Form with Server Actions, Zod and Resend

No API route, no client fetch, and it still submits with JavaScript off. Validation, a honeypot, a rate limit that survives serverless, and the from-address trap that kills deliverability.

Read more
Tutorial10 min read

A Next.js Content Security Policy That Keeps Static Rendering

The documented nonce recipe turns every route it touches dynamic. The static-safe policy we ship instead, what 'unsafe-inline' really costs, and what the header still blocks.

Read more