Organise by feature rather than by file type, and keep boundaries between layers. The most common cause of the mess you're describing is a structure that looks tidy at 500 lines (`/components`, `/utils`, `/helpers`) and becomes useless at 5,000 because everything is in three enormous folders with no relationship to what the app does.
The shift that helps most:
```
/features
/auth
LoginForm.js
useAuth.js
auth-api.js
/billing
InvoiceList.js
billing-api.js
/shared
/ui (Button, Modal — genuinely reusable)
/lib (formatting, dates)
```
Instead of a `components` folder with 200 files. The test: when you work on billing, is everything you need in one place? Feature-based structure means yes, and it means deleting a feature is a single folder deletion rather than an archaeology exercise.
The other habits that keep large codebases workable:
1. **Separate layers and keep them one-directional.** UI calls business logic, business logic calls data access. Data access never reaches back up into UI. When your database query lives inside a button component, every change becomes risky.
2. **Keep files small.** A 2,000-line file is where organisation goes to die. If a file has more than one clear responsibility, split it. Small files are easier to name, and naming difficulty is a reliable signal that something does too much.
3. **Push shared code up only when it's genuinely shared.** Premature `/utils` folders become junk drawers. Let something live in its feature until a second feature actually needs it — then move it, with an accurate name.
4. **Make dependencies explicit.** Import what you use; avoid global state that anything can mutate from anywhere. Untraceable state is the main reason large codebases become frightening to change.
5. **Name folders after the domain, not the technology.** `/checkout` tells you what it is. `/containers` tells you nothing about this application.
On `/utils`: it's where unrelated functions go to hide. If a file has date formatting, an API wrapper and a random string generator, it's not a module, it's a drawer. Split by purpose and name accordingly (`format-date.js`, `slugify.js`).
The realistic expectation: no structure survives contact with a growing project unchanged. The goal isn't a perfect layout up front — it's noticing when the current one is fighting you and restructuring deliberately, in one focused pass, rather than working around it for another six months. Refactoring structure is normal maintenance, not an admission of failure.