How I Modularize Code in Large-Scale Next.js Projects

3 min read

by Sandi Maulana Juhana, Full-stack Developer

A detailed look at how I structure and modularize Next.js App Router projects using feature-based folders, reusable components, and clean architecture principles.

How I Modularize Code in Large-Scale Next.js Projects

As projects grow, code that once felt manageable can quickly turn into a tangled mess — scattered files, repeated logic, and fragile components that break with the slightest change.

Over time, I've developed a clear approach to modularizing code in my large-scale Next.js projects, especially with App Router, TypeScript, and Tailwind CSS. The goal isn’t just to make things tidy — it’s to build something that’s scalable, navigable, and team-friendly, even if you’re currently a team of one.

🧱 Start with a Feature-First Mindset

I no longer organize code by type (e.g. components/, pages/, utils/). Instead, I group by feature or domain.

Example:

css

src/
├── features/
│   ├── checkout/
│   │   ├── CheckoutPage.tsx
│   │   ├── CourierPopup.tsx
│   │   └── payment/
│   │       ├── PaymentPopup.tsx
│   │       └── InvoicePopup.tsx
│   ├── profile/
│   │   ├── EditProfile.tsx
│   │   └── AddressEnhanced.tsx

This makes onboarding easier. When I (or someone else) needs to touch “checkout”, everything is in one place — logic, UI, state, and API hooks.

🧩 Break Components by Role, Not Size

Instead of thinking "big vs small components", I think in terms of responsibility:

  • ItemCard.tsx → presentational only

  • ItemDetail.tsx → display + logic

  • useItemData.ts → data fetching hook

  • ItemStore.ts → local Zustand store (if needed)

The result? Each file has a single purpose, and I know exactly where to look when something breaks.

"If you need to scroll more than 2 screens to understand a component, it’s probably doing too much."

🔁 Create Reusable Building Blocks

I maintain a global components/ folder for generic, reusable UI pieces:

mathematica

src/components/
├── Button.tsx
├── InputField.tsx
├── Modal.tsx
├── Screen.tsx

These are not tied to any business logic. They’re simple, consistent, and used across features.

Every time I find myself copying code from one screen to another, that’s a sign I need to extract a building block.

💡 Use Aliases to Stay Sane

Nothing kills DX like this:

tsx

import Button from '../../../../../../../components/Button'

That’s why I always configure path aliases in tsconfig.json:

json

{
  "paths": {
    "@/*": ["src/*"]
  }
}

Now I can write:

tsx

import Button from '@/components/Button'

Simple, readable, and portable.

🧠 Shared State? Choose Wisely

I use Zustand for local/global state when needed — but only if it actually simplifies things.

Pattern:

  • UI State → local component state.

  • Cross-component sync → Zustand store in src/stores/

  • Server state → Firestore or SWR/fetcher hooks in lib/

Avoid unnecessary complexity. Not everything needs global state.

🗃️ Example Folder Layout (Real Project)

From one of my actual projects:

pgsql

src/
├── app/
│   └── (App Router pages)
├── components/
├── features/
│   └── checkout/
│       ├── CourierPopup.tsx
│       ├── PaymentPopup.tsx
├── lib/
│   └── firebase.ts
├── stores/
│   └── useCartStore.ts
├── types/
│   └── index.ts

It’s not fancy — it’s predictable.

✨ Final Thought: Structure Is UX for Developers

The same way we design UI for users, we should design file structure for ourselves — the developer experience.

Good architecture isn't about complexity. It’s about making your future self say:

“Oh wow, this is easy to follow.”
Not:
“Who the heck wrote this?!”

By modularizing early and consistently, I’ve saved myself from burnout, confusion, and late-night debugging.

And best of all, I enjoy working with my own code.

More insights

Saat Dunia Terlalu Riuh

2 min read

Di tengah riuhnya media sosial, tulisan ini merefleksikan pilihan untuk menjaga lisan dan tulisan—membangun nilai dalam diam, dengan dasar iman, tanggung jawab.

Read more

Counting Page Views with Upstash Redis in a Next.js App Router Project

2 min read

A lightweight page view counter using Upstash Redis + Next.js, optimized for speed, edge, and serverless architecture.

Read more

Let’s build something meaningful.

Based in

  • South Jakarta
    12140, Jakarta, Indonesia