Featured Image
Every Next.js project starts clean. A few components, a couple of pages, maybe a utility file. Then features grow, pages multiply, and suddenly your components folder is a dumping ground with 87 files and no discernible organization. Sound familiar?
The default Next.js starter gives you almost no opinion about project structure beyond the app/ directory. That's great for flexibility, terrible for teams that need consistency.
Instead of organizing by file type (components/, utils/, hooks/), organize by feature. Each major feature gets its own folder containing everything it needs to function. This is the single biggest structural change you can make for maintainability.
src/
├── features/
│ ├── auth/
│ │ ├── components/
│ │ │ ├── LoginForm.tsx
│ │ │ ├── SignupForm.tsx
│ │ │ └── AuthGuard.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── utils/
│ │ │ └── tokens.ts
│ │ ├── types/
│ │ │ └── auth.types.ts
│ │ └── index.ts
│ │
│ ├── products/
│ │ ├── components/
│ │ ├── api/
│ │ ├── hooks/
│ │ └── index.ts
│ │
│ └── dashboard/
│ ├── components/
│ ├── hooks/
│ └── index.tsThe key test: can you delete a feature folder and have everything related to that feature disappear cleanly? If yes, your structure is working. If deleting a folder causes cascading breakage across the codebase, your boundaries are wrong.
This approach makes features portable, testable, and — crucially — easier to delete when requirements change. No more archaeology expeditions to find every file related to a feature scattered across 8 directories.
Not everything belongs to a single feature. Buttons, inputs, API clients, and generic hooks are used everywhere. These live in a shared directory with clear categories:
src/
├── shared/
│ ├── ui/ → Buttons, inputs, cards, modals
│ ├── lib/ → API clients, utilities, helpers
│ ├── hooks/ → useWindowSize, useDebounce, useInView
│ └── types/ → Global TypeScript types and interfacesThe rule of thumb: if a piece of code is used by 3+ features, it belongs in shared. If it's used by 1-2 features, keep it local to the primary feature and import where needed.
With Next.js 13+ and the App Router, the app/ directory encourages route-based organization. Each route gets its own folder with layout.tsx, page.tsx, loading.tsx, and error.tsx. This is powerful — but it introduces a temptation.
Don't put business logic in page files. Keep pages thin — they should compose components and pass data, nothing more. Move business logic to lib/ or hooks/, and UI to feature components.
For a recent platform handling 50,000+ lines of code across 10 developers, this structure saved us measurable time and frustration:
Folder structure isn't glamorous, but it's the foundation everything else is built on. Get it right early and your future self — and every developer who joins after you — will be grateful.
I'm always open to discussing software architecture, platform engineering, or potential collaborations.
Let's Talk