Where to Put Components in Next.js: 92 Files, None of Them in app/
The App Router routes special files, not folders — so the placement question is about one import path and one place to look, not about avoiding accidental routes.
Put components in src/components/, outside the app directory. In the App Router only page.tsx, route.ts, layout.tsx and their siblings create routes, so a components folder inside app/ is technically safe — but keeping it out gives you one import path, one place to look, and no per-route judgement call. This codebase has 92 components and zero of them live under src/app.
The search results for this question mostly answer "it doesn't matter, pick one." That's true about correctness and unhelpful about everything else. Below is the actual decision: the three placements people use, what each one costs once the project is past twenty files, and the one case where the old Pages Router advice still bites.
The three placements
Root /components | src/components | Colocated app/**/_components | |
|---|---|---|---|
| Sits next to | app/, package.json, configs | app/, lib/, data/ | The route that uses it |
| Import path | @/components/Button | @/components/Button | ./_components/Button |
| Shared across routes | Yes | Yes | Awkward — you move the file |
| Route collision risk | None | None | None in App Router; fatal in Pages Router |
| Scales to | Any size | Any size | Route-local UI only |
| Answers "where does this go?" | Sometimes | Sometimes | Always, until it's shared |
The first two are the same decision wearing different hats: whether your source lives in src/ at all. If it does, components go in src/components. Everything interesting is in the third column.
What this codebase does
src/ holds seven entries, and app is only one of them:
src/
├── app/ 27 page.tsx files — routes, and nothing else
├── components/ 92 components in four layers
├── content/ 282 .mdx blog posts
├── data/ 16 typed content arrays
├── lib/ server actions, entitlements, Supabase clients
├── mdx-components.tsx
└── proxy.ts
Searching src/app for a directory named components or starting with _ returns nothing. Every folder under src/app is a route: pricing, docs, blog, dashboard, login, the seven pSEO landing routes, and so on. That is the whole rule — app/ is the routing table, and a routing table should read like one.
The 92 components sort into four folders by role, not by route:
| Folder | Count | What lives there |
|---|---|---|
src/components/atoms | 7 | Button, Container, Logo |
src/components/molecules | 41 | TemplateCard, FaqItem, BuyButton |
src/components/organisms | 33 | Header, Hero, Footer |
src/components/templates | 11 | HomeTemplate, PricingTemplate |
Why role and not route is a separate question with its own answer — see Next.js components: 92 files, five layers, one client boundary for the taxonomy and the import rule that does the real work for what enforces it. This post is only about which directory the files sit in.
The Pages Router trap that still shows up in advice
In the Pages Router, every file under pages/ becomes a route. Put pages/components/Button.tsx in a project and you have published a broken page at /components/Button — it renders whatever Button exports with no props, usually a crash. That is why a decade of tutorials say "never put components in the pages directory," and why that sentence keeps getting copy-pasted into App Router answers where it no longer applies.
The App Router changed the rule. Routes come from special files — page.tsx, route.ts, layout.tsx, loading.tsx, error.tsx, not-found.tsx — not from every file in the tree. A folder of components inside app/ creates no route at all, because none of those files is a page.tsx. Next.js also supports private folders: prefix a directory with an underscore, like _components, and it is excluded from routing entirely, plus signalled as an implementation detail to anyone reading the tree.
So both of the common fears are wrong on the current router:
- "A components folder in
app/will become a route." It won't. - "You must use
_componentsor it will become a route." You don't have to.
Which frees the decision up to be about something that actually matters.
The real trade-off: colocation versus one place to look
Colocation reads beautifully at the start. A route folder that holds its page, its layout and the three components only that page uses is genuinely easy to reason about — you delete the folder, you delete the feature.
It degrades on one specific event: the second route wants the component. Now you either import across route folders (../../dashboard/_components/Thing, which quietly couples two routes and breaks when either moves), or you promote the file to a shared directory. Promotion is the correct move and it is a real edit — the file moves, every importer updates, and the reviewer has to check you didn't leave a copy behind.
This storefront hit that limit early. TemplateCard renders on /templates, on all seven pSEO spoke routes, and on the home page; Header and Footer render on all 27. There was never a route that owned them. Starting those files inside a route folder would have meant promoting almost all of them within a few weeks, so the shared directory won by default.
The heuristic that survives both cases: colocate when a component is genuinely route-local and you expect it to stay that way — a one-off chart on one dashboard screen, a form only the signup route renders. Put it in src/components the moment a second route imports it, and skip the intermediate step entirely if you already know two routes need it.
Projects with many self-contained feature routes do well with colocation. A marketing site or a storefront, where most UI is a reusable card or section rendered on a dozen pages, mostly does not.
The alias that makes this cheap to change
Whichever placement you pick, set a path alias so no import ever names a relative depth. This project's tsconfig.json has one:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
create-next-app offers this at setup and it is worth taking. It is what makes every import in the codebase read the same from any depth:
// src/components/organisms/Features.tsx
import Container from "@/components/atoms/Container";
import SectionHeading from "@/components/atoms/SectionHeading";
import FeatureCard from "@/components/molecules/FeatureCard";
import { features } from "@/data/features";
That file is four directories deep. Without the alias its first line would be ../../atoms/Container, and moving the file would rewrite every import in it. With the alias, moving a component between src/components and a route folder is a one-line change per importer — which is the actual reason this decision is lower-stakes than the search results imply. Pick a default, set the alias, and let the rare exception move.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| A component renders as a blank page at its own URL | It's under pages/, not app/ — Pages Router routes every file | Move it out of pages/, or migrate the route to app/ |
Module not found: Can't resolve '@/components/...' | No paths alias in tsconfig.json, or it points at the wrong root | Add "@/*": ["./src/*"] and restart the dev server — path config isn't hot-reloaded |
Imports like ../../../components/Button everywhere | Placement is fine, the alias is missing | Same fix; the depth is a symptom, not the problem |
Two routes importing from each other's _components | A colocated component became shared and was never promoted | Move it to src/components and update both importers |
src/app has grown folders that aren't routes | Helpers were colocated into the routing table | Move them to src/lib or src/components; keep app/ readable as a URL map |
A component in app/ accidentally shipped as a route | It's named page.tsx | Rename it — the filename is what creates the route, not the folder |
Frequently asked questions
Does src/ do anything, or is it just a convention?
It's a convention Next.js understands. If a src directory exists, Next.js looks for app (or pages) inside it instead of at the root. Nothing behaves differently; you get one folder separating application code from the dozen config files at the repository root.
Is _components better than a plain components folder inside app/?
Slightly, and only for signalling. Both are excluded from routing in the App Router — a folder only becomes a route when it contains a page.tsx. The underscore makes "this is not a route" visible to the next reader without them having to check.
Should components be grouped by feature or by type? Once they're shared, by whatever answers "where does this go?" fastest for your team. This codebase groups by role in the page — atom, molecule, organism, template — because the layers carry an import rule that catches architectural mistakes at review time. Feature grouping works well when features are genuinely independent; it gets fuzzy on a site where most components appear on most pages.
Do I have to move everything at once if I'm changing placement? No. With a path alias, importers don't encode location, so you can move a folder at a time and the diff stays mechanical. Do it in its own commit — mixing a file move with a logic change makes the review much harder than either alone.
Templates in this post
ASoc Guard, ASoc Haven and ASoc Hearth are Next.js + Tailwind landing page templates. Each ships the src/components layout described above, with app/ holding routes only and the @/* alias already configured.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
