Dev.to · 7 min read

Mastering Next.js File-Based Routing (App Router)

Mastering Next.js File-Based Routing (App Router)

Hello folks, as promised, back with the blog of Next.js file-based routing. Next.js has one of the most elegant routing systems in the React ecosystem: file-system based routing. Instead of manually declaring routes in a configuration file, the structure of your folders and files is the routing structure. This blog focuses exclusively on the modern App Router (app/ directory) and covers every major routing convention in detail. Introduction – File-Based Routing & App Router vs Pages Router Traditionally, you often write something like: router.get('/blog/:slug', handler) Next.js flips this model. You create a folder named blog, put a file inside it, and the route appears automatically. There are two routing systems in Next.js: Pages Router (pages/ directory) – the original system (still fully supported). App Router (app/ directory) – introduced in Next.js 13, built on React Server Components, nested layouts, streaming, and more powerful conventions. This guide uses the App Router, which is the recommended approach for all new projects. Core Mental Model Three rules govern almost everything: Folders = Route Segments Every folder inside app/ becomes a route segment in the URL. page.tsx (or page.jsx) makes a route public A folder without a page.tsx is not a public route. It can still contain nested layouts, components, or other files. Special files create UI and behavior layout.tsx → shared UI that wraps the segment and all children (can be global or nested) loading.tsx → loading UI error.tsx → error boundary not-found.tsx → 404 UI (can be global or nested) Folders + Files = URLs With these rules in mind, let’s explore each routing pattern. 1. Static Routes + Nested Static Routes Folder structure app/ ├── page.tsx → / ├── about/ │ └── page.tsx → /about ├── blog/ │ └── page.tsx → /blog └── dashboard/ ├── page.tsx → /dashboard ├── analytics/ │ └── page.tsx → /dashboard/analytics └── settings/ ├── page.tsx → /dashboard/settings └── profile/ └── page.tsx → /dashboard/settings/profile Static routes are the simplest form: the folder name becomes the exact URL segment. Nesting folders creates nested URLs. Each level can optionally have its own layout.tsx that only wraps that segment and its children. Key technical notes The root app/page.tsx is required for the homepage (/). A folder without page.tsx is invisible to the router (great for colocation). Layouts nest automatically: root layout → dashboard layout → settings layout. 2. Dynamic Routes (Single Segment) Folder structure app/ └── blog/ ├── page.tsx → /blog └── [slug]/ └── page.tsx → /blog/:slug A dynamic segment is created by wrapping the folder name in square brackets: [slug]. The value of slug is extracted from the URL and passed to your page. Accessing the parameter (server component) // app/blog/[slug]/page.tsx export default async function Page({ params, }: { params: Promise; }) { const { slug } = await params; return Blog Post: {slug}; } Key technical notes In recent Next.js versions, params is a Promise — you must await it. Client Components can use the useParams() hook from next/navigation. Use generateStaticParams() to pre-render known dynamic paths at build time (covered later). 3. Multiple Dynamic Route Segments Folder structure app/ └── shop/ └── [category]/ └── [productId]/ └── page.tsx → /shop/:category/:productId You can stack as many dynamic segments as you need. Each [param] is independent and appears in the params object. Example URL params /shop/electronics/iphone-15 { category: "electronics", productId: "iphone-15" } /shop/clothing/nike-air { category: "clothing", productId: "nike-air" } export default async function Page({ params, }: { params: Promise; }) { const { category, productId } = await params; // fetch product data using both values } You can freely mix static and dynamic segments (e.g. /shop/[category]/reviews/[reviewId]). 4. Catch-All Routes Folder structure app/ └── docs/ └── [...slug]/ └── page.tsx → /docs/* (one or more segments) The [...slug] syntax (ellipsis inside brackets) captures one or more segments as an array. This is introduced because when we don't know about the depth of level in multiple dynamic routes then this is the best option to choose. URL params.slug /docs/getting-started ['getting-started'] /docs/api/auth ['api', 'auth'] /docs/a/b/c/d ['a', 'b', 'c', 'd'] Important: A regular catch-all does not match the parent path /docs. That would 404 unless you also have a docs/page.tsx. export default async function Page({ params, }: { params: Promise; }) { const { slug } = await params; return Path: {slug.join(' / ')}; } 5. Optional Catch-All Routes Folder structure app/ └── docs/ └── [[...slug]]/ └── page.tsx → /docs AND /docs/* Double square brackets [[...slug]] make the catch-all optional. It now also matches the route with zero additional segments means it solves the problem of catch-all-routes also. URL params.slug /docs undefined /docs/getting-started ['getting-started'] /docs/api/auth ['api', 'auth'] This pattern is extremely useful for documentation sites, knowledge bases, or any UI that needs a root view plus arbitrary depth. Always handle the undefined case in your component. Important: Don't ever use both the catch all routes and optional catch all routes together not under the same folder. Either use first one or the second one; but not together under same folder. export default async function Page({ params, }: { params: Promise; }) { const { slug } = await params; return Path: {slug ? slug.join('/') : '(no slug — matched /page itself)'}; } 6. Route Groups (+ Private Folders) Folder structure app/ ├── (marketing)/ │ ├── page.tsx → / │ ├── about/ │ │ └── page.tsx → /about │ ├── pricing/ │ │ └── page.tsx → /pricing │ └── layout.tsx // marketing layout only ├── (shop)/ │ ├── products/ │ │ └── page.tsx → /products │ ├── cart/ │ │ └── page.tsx → /cart │ └── layout.tsx // shop layout only ├── (dashboard)/ │ ├── layout.tsx // dashboard layout │ └── settings/ │ └── page.tsx → /settings └── layout.tsx // root layout Route groups are created by wrapping a folder name in parentheses: (marketing). The parentheses and the name inside them never appear in the URL. They exist purely for organization and for applying different layouts to different sets of routes. If we don't want to show the folder structure in the route/URL itself, then we will use this. Common use cases Separate marketing pages from authenticated dashboard pages Apply different root layouts Organize by team or feature without polluting the URL Private folders (related convention) Prefix any folder with an underscore (_components, _lib, _utils). These folders and everything inside them are completely ignored by the router — perfect for colocation. Important rules Two different route groups cannot resolve to the same URL path (build-time error). Navigating between routes that use different root layouts triggers a full page load. Practical Tips Mixing conventions You can combine everything freely: app/ └── (shop)/ └── [category]/ └── [...slug]/ └── page.tsx generateStaticParams For dynamic and catch-all routes you often want static generation: // app/blog/[slug]/page.tsx export async function generateStaticParams() { const posts = await getAllPosts(); return posts.map((post) => ({ slug: post.slug })); } Layouts interaction Layouts wrap their children and persist across navigations within the same segment. A layout inside a route group only applies to routes inside that group. Root layout is required (unless you intentionally create multiple root layouts via route groups). Common pitfalls Forgetting that a folder needs a page.tsx to be public. Conflicting paths between two route groups. Not awaiting params in newer Next.js versions. Expecting a regular catch-all [...slug] to match the parent path. Putting business logic or shared components directly in route folders instead of using private folders or a top-level components/ directory. Conclusion & Further Reading File-based routing in the App Router is both powerful and surprisingly simple once the core mental model clicks: folders define segments, page.tsx makes them public, and special naming conventions ([], [...], [[...]], ()) give you dynamic behavior and organization. Master these patterns and you can model almost any URL structure cleanly while keeping your project organized and performant. Official documentation Defining Routes Dynamic Route Segments Route Groups Project Structure Happy routing!

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Programming & Dev News