Good project structure is the kind of thing you only notice when it is wrong. When it is right, you open a file and know immediately where the thing you need lives. When it is wrong, every change requires a safari through four directories and three mental context-switches. In 2026 the tooling has never been better for enforcing layout — but you still have to pick the right one first.
What changed in 2026
- Monorepo tooling matured. Turborepo, Nx, and Bazel now handle caching and task orchestration reliably enough that large teams default to them. But small teams still over-adopt them too early.
- Language servers reward explicitness. Path aliases (
@/features/auth) are now first-class in TypeScript, Rust workspaces, and Python's src/ layout — IDEs and LSPs resolve them natively.
- AI-assisted navigation raised the bar. Tools like Copilot Workspace index your structure; a flat, coherent layout gives you better suggestions than a tangled one.
src/ layout won in Python. src/<package>/ is now the recommended default for new Python projects, eliminating the accidental-import-of-the-root bug.
The core principle: colocation
Files that change together should live together. The opposite — splitting by technical layer — creates "shotgun surgery," where one feature change requires edits in six folders.
# Layer-split (fragile)
controllers/
user.js
order.js
models/
user.js
order.js
services/
user.js
order.js
# Feature-first (scales)
features/
user/
controller.js
model.js
service.js
user.test.js
order/
controller.js
model.js
service.js
order.test.js
Reference layouts by stack
Node / TypeScript API
src/
features/
auth/
orders/
users/
shared/ # pure utilities, no feature imports
db.ts
logger.ts
errors.ts
app.ts # express/fastify setup
server.ts # entrypoint
tests/
integration/
.env.example
Python service
src/
myapp/
features/
auth/
billing/
shared/
db.py
settings.py
main.py
tests/
pyproject.toml
React / Next.js frontend
src/
features/
dashboard/
components/
hooks/
api.ts
onboarding/
shared/
components/ # truly global UI only
hooks/
lib/
app/ # Next.js app-router pages
Monorepo vs polyrepo in 2026
| Factor |
Monorepo |
Polyrepo |
| Code sharing |
Easy — import directly |
Requires publishing packages |
| CI complexity |
Higher — scoped pipelines needed |
Lower per repo |
| Team autonomy |
Lower |
Higher |
| Dependency sync |
Trivial |
Manual, error-prone |
| Best for |
>3 related services sharing code |
Unrelated or independent services |
Start with a single repo. Add monorepo tooling only when you have two or more services that share non-trivial code and you feel the pain of duplication.
How to structure shared code
shared/ or common/ — only true cross-feature primitives (errors, loggers, config loaders).
- No circular imports.
features/auth may import shared/db, but shared/ must not import any feature.
- Barrel files with care. One
index.ts per feature for the public interface; avoid re-exporting everything or you lose tree-shaking.
// features/auth/index.ts — explicit public surface
export { login, logout } from './service';
export type { AuthUser } from './types';
// Do NOT export internal helpers
Enforcing structure with tooling
Tribal knowledge decays. Automate it:
// .eslintrc — forbid cross-feature imports
{
"rules": {
"import/no-restricted-paths": [
"error",
{
"zones": [
{
"target": "./src/features/auth",
"from": "./src/features/orders"
}
]
}
]
}
}
Add path aliases so refactoring a folder does not break fifty relative imports:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
How to pick a structure
- Solo project / prototype — flat
src/ with feature folders once you have more than 3 features.
- Small team API — feature folders inside
src/, one shared/, strict import rules.
- Multiple frontend + backend — monorepo with Turborepo, packages for shared types and UI kit.
- Enterprise multi-team — Nx or Bazel with explicit module boundaries and ownership in
CODEOWNERS.
Common mistakes
Going feature-first too late. The layer split feels fine at 5 routes. At 30 it is a disaster. Switch early.
Putting everything in shared/. If 40% of your code is "shared," you have a hidden monolith. Shared should be small.
No index surface contract. Importing ../../features/auth/internals/token from another feature means any internal rename breaks callers.
Inconsistent casing. UserService.ts vs user-service.ts in the same project is a tooling and git headache. Pick one convention and lint it.
What to skip
- Monorepo on day one — the tooling overhead is real. Earn it.
- Clean Architecture layer names (
domain/, application/, infrastructure/) unless your team knows DDD well. They often produce more ceremony than value.
- Deep nesting past three levels —
src/features/billing/stripe/webhooks/handlers/ is a warning sign.
FAQ
Does folder structure matter if I have good tests?
Yes. Tests do not help you find where to add a feature. Structure determines navigation speed and the cognitive load of change.
How do I migrate an existing messy project?
Move one feature at a time. Create the new structure in parallel, migrate one module, run tests, commit. Never big-bang refactor structure.
Should test files live next to source files or in a separate tests/ folder?
Unit tests colocated (auth.test.ts next to auth.ts). Integration and end-to-end tests in a top-level tests/ folder — they test boundaries, not internals.
What about generated code?
Always in a generated/ or __generated__/ directory, never edited by hand, and typically git-ignored or clearly marked.
Where to go next