DEV Community

EliezerKibet
EliezerKibet

Posted on • Originally published at eliezerkibet.dev

How to Structure Reusable Components in a Next.js Project

How to Structure Reusable Components in a Next.js Project
One habit separates Next.js codebases that stay clean from ones that become a nightmare to maintain — thinking in reusable components from day one. Here is the structure and rules I use on every project.

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
project={project}
showContact
showBadge
openModal
variant="featured"
/>

// ✓ One clear responsibility



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 (


{children}

);
}
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


// Pricing tier



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.

Top comments (0)