Last update
Have you ever entered a project and felt lost in a sea of generic folders like components, hooks, and utils?
It's a common symptom of technical debt. As an application scales, the location of the code becomes as critical as the quality of the code itself. When everything is "global," nothing has a clear domain.
The solution isn't to create more folders. The solution is intentional architecture applied through The Scope Rule.
The Scope Rule: Context is King
This architectural principle redefines how we organize code based on its scope of use, not on its technical type:
"The location of a file is determined by who consumes it."
Instead of grouping files by their extension or "type" (all buttons together, all hooks together), we group by business functionality (Feature Slicing).
- Local Scope (1 Feature): If a component is exclusive to the Dashboard, it lives INSIDE the
dashboarddirectory. - Shared Scope (2+ Features): Only if a component is used in multiple domains (e.g., Dashboard and Profile) is it promoted to
shared.
Screaming Architecture in the Next.js Era
Your folder structure should "scream" the business intent, not the framework you use. This drastically facilitates the onboarding of new developers.
❌ Silent Structure (Legacy Pattern):
src/
components/ # Buttons? Modals? Product cards?
hooks/ # Vault logic? Auth?
pages/ # Routes disconnected from their logic✅ Screaming Structure (Scope Rule):
src/
app/
(auth)/ # Domain: Authentication
login/
_components/ # Exclusive UI for login (LoginForm)
(dashboard)/ # Domain: Control Panel
analytics/
_components/ # Sales charts (KPIChart)
shared/ # Base UI Kit, reusable primitivesImpact for the team: A new developer can open the repo and understand what the application does in seconds, without navigating complex dependency graphs.
Optimization and Server Components
In Next.js 15, this architecture boosts performance and the separation of responsibilities.
- Server First Mentality: By keeping components close to their routes (
app/dashboard/_components), it's natural to write them as Server Components, reducing the JS bundle sent to the client. - Actions Placement: Your
_actions.tslive next to the form that invokes them. High cohesion, low coupling.
Case Study: Pricing Widget
Imagine a complex PriceWidget that only exists in the Checkout.
- Traditional Approach: You put it in
src/components/PriceWidget.tsx. You contaminate the global scope with specific business logic. - Scope Rule Approach: It lives in
src/app/(shop)/checkout/_components/price-widget.tsx.
Result: Modular code. If tomorrow you remove the checkout feature, you remove its folder and the dead code automatically disappears. Guaranteed maintainability.
Checklist for a Robust Architecture
Before creating a file, apply this decision filter:
-
🛑 Scope?
- 1 Feature → Local (
_components,_hooks). - 2+ Features → Shared (
shared/ui).
- 1 Feature → Local (
-
⚡ Environment?
- Prioritize Server Components by default.
- Use
'use client'only at the leaves of the component tree (specific interactivity).
-
📢 Semantics?
- Use Route Groups
(auth),(shop)to organize domains without affecting the URL.
- Use Route Groups
Architecture isn't rigid rules; it's communication. By adopting the Scope Rule, you write code that explains its own purpose, making life easier for your current team and future developers.