Why Component Structure Matters
Most developers start a Next.js project by building pages. Then they copy a button here, a card there. Six weeks in, the same UI element exists in 11 different files — each slightly different. Change one, the others stay broken.
The fix isn't discipline. It's structure. When there's a clear place for everything, the right decision becomes the obvious decision.
Three Layers — Everything Fits Somewhere
components/
ui/ ← no business logic, pure display
Button.tsx
Card.tsx
Badge.tsx
Input.tsx
layout/ ← structure shared across pages
Navbar.tsx
Footer.tsx
PageWrapper.tsx
features/ ← specific to one domain
blog/
BlogCard.tsx
BlogList.tsx
projects/
ProjectCard.tsx
ProjectGrid.tsx
ui/ — components that know nothing about your application. They receive data via props and render it. No API calls, no business logic, no assumptions about where they'll be used.
layout/ — components that define the structure of a page. These appear on every page or most pages. The Navbar doesn't know what page it's on. The Footer doesn't know what content surrounds it.
features/ — components tied to a specific domain of your application. A BlogCard knows about blog posts. A ProjectCard knows about projects. They live close to the feature they belong to.
The One Rule
If a component appears in more than one place, it belongs in ui/.
That's it. Apply that rule consistently and the folder structure stays clean without active effort.
What a Good Reusable Component Looks Like
A good reusable component has one job. The moment you find yourself adding props to control five different behaviours, the component is doing too much.
// ❌ Doing too much — too many responsibilities in one component
<ProjectCard
project={project}
showContact
showBadge
openModal
variant="featured"
/>
// ✓ One clear responsibility
<ProjectCard project={project} />
<FeaturedBadge />
<ContactCTA />
Split it when a component starts accumulating conditional props. Each piece is easier to test, easier to update, and easier to reuse independently.
Use children and className to Extend Without Coupling
The most flexible pattern for UI components is accepting children and an optional className. This lets the component be extended at the call site without changing the component itself.
interface CardProps {
children: React.ReactNode;
className?: string;
}
export function Card({ children, className }: CardProps) {
return (
<div className={cn("rounded-lg border p-6 bg-white", className)}>
{children}
</div>
);
}
Now Card works everywhere. Blog post previews. Project tiles. Pricing sections. Testimonial blocks. You style it at the call site using className, not by adding props inside the component.
// Blog post
<Card className="hover:shadow-md transition-shadow">
<BlogPostContent post={post} />
</Card>
// Pricing tier
<Card className="border-blue-500 border-2">
<PricingTier tier={tier} />
</Card>
One component. Two completely different use cases. No new props required.
Co-locate Types With Components
Keep the TypeScript interface for a component's props in the same file as the component. Don't create a separate types/ folder for component props — it's unnecessary indirection.
// BlogCard.tsx
interface BlogCardProps {
title: string;
excerpt: string;
date: string;
slug: string;
tags: string[];
}
export function BlogCard({ title, excerpt, date, slug, tags }: BlogCardProps) {
// ...
}
The type lives where it's used. When you update the component, the type is right there. No switching files.
When to Create a New Component
A useful heuristic: if you're about to copy and paste JSX, stop and ask whether it should be a component instead.
The answer is yes if:
- The same JSX will appear in more than one place
- The block of JSX has a clear, nameable responsibility
- You want to test it in isolation
The answer is no if:
- The JSX only appears once and is unlikely to be reused
- Extracting it would make the code harder to read, not easier
- It would need so many props to be generic that it's more complex than just writing it inline
The Payoff
None of this feels significant on day one. The folder structure looks like overhead. The discipline of keeping components single-responsibility feels like extra work.
Day 60 is when it pays off.
You need to update a button style. In a well-structured project, you change Button.tsx once. It updates everywhere. In a project without structure, you spend three hours finding every place a button was copy-pasted and hoping you didn't miss one.
You need to add a loading state to a card. In a well-structured project, you add it to Card.tsx and every card in the application gets it. In an unstructured project, you add it to the three different inline card implementations and forget the fourth.
Build components like you're building a library — even when you're the only one using it. Future you, six months from now, will thank you.
If you're building a Next.js application and want to talk through the architecture before you start, get in touch. Getting the structure right at the start is far cheaper than refactoring it later.