
Next.js File-system conventions
Every framework has to answer one boring but load-bearing question: how does a folder full of files turn into a running application? Most React frameworks before Next.js answered it with configuration — a routes array, a manifest, a bundler plugin that stitched things together based on rules you wrote yourself. The App Router answers it differently: the folder structure is the configuration. Create a folder, drop a page.js in it, and you have a route. Add a layout.js next to it, and every route under that folder shares that layout. There's no routes file to keep in sync with your file tree, because the file tree is the routes file.
That only works because Next.js reserves a specific set of filenames and treats each one as a slot with a defined job. page.js means "this segment is a visitable route." layout.js means "wrap children in this UI, and keep it mounted across navigations." loading.js means "show this automatically while the segment below is still resolving." None of these are components you import and use explicitly — Next.js finds them by name, at build time, and wires them into the route tree for you. That's the trade you're making: less boilerplate, in exchange for the compiler needing to recognize your filenames.
This article is a map of every convention in that system — what each reserved filename or folder does, how they're grouped, and how they compose in a real route. It isn't a substitute for the dedicated reference page on any single convention (layout.js, route.js, error.js, and the rest each have their own deep-dive elsewhere on this blog) — think of it as the table of contents you'd want before diving into any of them.
The core idea: folders are segments, filenames are slots
In the App Router, every folder inside app/ maps to a segment of the URL path. app/blog/[slug]/page.js becomes the route for /blog/:slug. That part is common to most file-based routers, React or otherwise.
What's distinctive is what happens inside each segment folder. Instead of one file per route, you can have several files, each with a reserved name, each responsible for one specific piece of behavior at that point in the tree:
app/
blog/
[slug]/
page.js ← the actual page content
layout.js ← shared wrapper for this segment and its children
loading.js ← automatic Suspense fallback while page.js resolves
error.js ← automatic error boundary for this segment
not-found.js ← UI shown when notFound() is called here
None of these files import or reference each other directly. Next.js's compiler scans the file tree, recognizes each reserved name, and assembles them into a single nested React tree behind the scenes — roughly Layout > Error > Suspense(loading) > Page, repeated at every level of nesting. You write five small, single-purpose files; Next.js does the composition.
This is why the conventions are worth learning as a system rather than one at a time. Once you understand that "special filename = reserved slot in the route tree," almost every convention below is just answering: which slot, doing what, at what scope?
Routing-shape conventions
These conventions don't render anything themselves — they change how folders map to URLs and how routes nest.
Dynamic Segments — a folder named [slug] or [...slug] captures a portion of the URL as a parameter, available to page.js and layout.js via params. This is how one file can serve an unbounded number of URLs (/blog/[slug] serving every blog post).
Route Groups — a folder named (marketing) groups routes for organizational purposes — shared layouts, logical separation — without adding a segment to the URL. app/(marketing)/about/page.js still serves /about, not /marketing/about. This is the tool for "I want these routes to share a layout, but I don't want that grouping to leak into the URL."
Parallel Routes — a folder named @analytics lets you render more than one independent page-like tree in the same layout simultaneously — a dashboard with a @team panel and an @analytics panel that each have their own loading and error states, navigable independently. This is the advanced end of the routing-shape conventions; most apps never need it, but it's the right tool when you do.
Intercepting Routes — using (.), (..), or (...) prefixes, you can load a route within the current layout while the URL still updates to reflect the target route — the mechanism behind "click a photo in a grid, it opens as a modal, but reloading the page shows the full photo page instead." It's almost always paired with Parallel Routes in practice.
src folder — not really a routing convention, but a project-structure one: you can nest your entire app/ (and public/, if you want) under src/, keeping application code separate from root-level config files. Purely organizational, zero behavioral difference.
UI-slot conventions
These are the files that actually render something, each scoped to the segment they live in and everything nested below it.
| File | What it renders | Scope |
|---|---|---|
layout.js | Persistent UI wrapped around a segment and its children; doesn't re-render on navigation between sibling routes | This segment + all nested segments |
template.js | Like a layout, but re-mounts (and re-runs effects) on every navigation | This segment + all nested segments |
page.js | The actual, unique UI for a route; what makes a segment publicly reachable | This segment only |
loading.js | Automatic Suspense fallback shown while this segment's async work resolves | This segment + all nested segments |
error.js | Automatic error boundary catching thrown errors in this segment and below | This segment + all nested segments |
not-found.js | UI rendered when notFound() is called, or an unmatched dynamic segment is hit | This segment + all nested segments |
forbidden.js | UI rendered when forbidden() is called (requires the authInterrupts flag) | This segment + all nested segments |
unauthorized.js | UI rendered when unauthorized() is called (requires the authInterrupts flag) | This segment + all nested segments |
default.js | Fallback UI for a Parallel Routes slot when no matching route exists for the current URL | The parallel slot it lives in |
The pattern to notice: page.js is the only one of these that's scoped to just that segment. Everything else — layouts, loading states, error boundaries — cascades down to every nested route beneath where it's defined, the same way CSS cascades down the DOM. Put an error.js at the root of app/, and it catches errors from your entire application unless a more specific error.js further down intercepts first.
Backend and integration conventions
Not every file in app/ renders UI. Some define server-side behavior directly.
route.js — defines a Route Handler: a function per HTTP method (GET, POST, etc.) that returns a Response, effectively an API endpoint co-located with your routes. A folder can have a page.js or a route.js, never both, at the same URL.
proxy.js — a single file at your project root that runs before a request reaches any matched route, letting you rewrite, redirect, or modify the request/response — the App Router's replacement for what older Next.js versions called Middleware.
instrumentation.js and instrumentation-client.js — hooks for running setup code at server startup and in the browser respectively, most commonly used to wire up observability tooling like OpenTelemetry before anything else runs.
mdx-components.js — a required file (when using MDX) that maps standard Markdown elements to your own React components, so every .mdx file in your project renders consistent, styled output without repeating the mapping in each file.
Metadata conventions
A separate family of conventions covers everything that ends up in <head> or served as a top-level file rather than rendered as visible UI: favicon.ico/icon/apple-icon for icons, manifest.json for PWA metadata, opengraph-image/twitter-image for social share previews, robots.txt for crawler rules, and sitemap.xml for search engines. Each can be a static file or a dynamically generated one (a .js/.ts file exporting a function), and Next.js handles serving them at the correct well-known URL either way. They're grouped together in the docs as "Metadata Files" because they solve a related problem — describing your app to browsers, crawlers, and social platforms — despite having almost nothing in common technically.
Configuration, not files: Route Segment Config
One more category doesn't add a new file at all — it's a set of exported constants (dynamic, revalidate, runtime, and others) that you place inside an existing page.js, layout.js, or route.js to configure how that segment behaves: whether it's statically or dynamically rendered, how long it can run, which runtime it executes in. It's worth knowing this exists as its own reference category, because it's easy to go looking for a "config file" convention when the actual answer is "a few exported variables in the file you already have."
Special files vs. the public folder
It's worth drawing one clear boundary: everything above lives inside app/ and is recognized by name by the Next.js compiler. The public folder is different — it's a plain static-asset directory at your project root, and anything inside it is served as-is at the matching URL (public/logo.png → /logo.png). Nothing in public/ is "special" the way page.js is special; there's no reserved filename behavior there, just a direct file-to-URL mapping. It's easy to lump it in mentally with the rest of these conventions since it's also file-system-based, but the mechanism is completely different.
How this actually composes in practice
Here's a realistic segment showing several conventions working together:
app/
(shop)/
products/
[id]/
page.js
layout.js
loading.js
error.js
not-found.js
@reviews/
page.js
default.js
Reading this tree: (shop) groups shop-related routes without affecting the URL. products/[id] is a dynamic route reading an id param. Its layout.js wraps the product page and persists across navigations to sibling products. loading.js shows a skeleton while the product data streams in. error.js catches anything that throws during that fetch. not-found.js renders if the product ID doesn't exist. And @reviews is a Parallel Route slot rendering reviews independently, with default.js providing a fallback if no reviews route matches for a given URL.
None of these files reference each other in code. The composition is entirely a function of where they sit in the tree — which is exactly the trade-off this whole system makes: you give up explicit wiring, and get a route tree that reads its own structure directly off your folders.
Key Takeaways
| Category | Conventions | What they control |
|---|---|---|
| Routing shape | Dynamic Segments, Route Groups, Parallel Routes, Intercepting Routes, src folder | How folders map to URLs and nest |
| UI slots | layout.js, template.js, page.js, loading.js, error.js, not-found.js, forbidden.js, unauthorized.js, default.js | What renders, and at what scope, within a segment |
| Backend & integration | route.js, proxy.js, instrumentation.js, instrumentation-client.js, mdx-components.js | Server-side behavior and setup hooks |
| Metadata | Icons, manifest.json, OG/Twitter images, robots.txt, sitemap.xml | What appears in <head> and at well-known URLs |
| Configuration | Route Segment Config (dynamic, revalidate, runtime, etc.) | Rendering behavior of an existing file, not a new file |
The single idea underneath all of it: Next.js turns your file tree into your route tree by reserving filenames as slots, and letting scope — which folder a file lives in — do the wiring that other frameworks make you write by hand.


