<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged routing</title>
<link>https://coderlegion.com/tag/routing</link>
<description>Powered by Question2Answer</description>
<item>
<title>TanStack Start Layouts Explained (Pathless Layout Routes + Outlet)</title>
<link>https://coderlegion.com/28879/tanstack-start-layouts-explained-pathless-layout-routes-outlet</link>
<description>&lt;p&gt;Layouts let us customize sections of our app based on their purpose, their content, or the look and feel we want. They give us the flexibility to mix things up, get more creative, and offer a really great UI/UX experience to our users.&lt;/p&gt;
&lt;p&gt;Navigation benefits the most. With layouts we can change the nav style, content, and links based on the page a user is on. We also get to write &lt;strong&gt;DRY&lt;/strong&gt; (Don't Repeat Yourself) code: build a navigation component once, call it in a layout, and when something needs to change, edit one file and the change propagates throughout the app.&lt;/p&gt;
&lt;p&gt;So, enough talk. Let's crack on.&lt;/p&gt;
&lt;h3 id=&quot;where-we-re-picking-up&quot;&gt;Where we're picking up&lt;/h3&gt;
&lt;p&gt;This post continues the app we've been building in this series. If you're here for the first time, start with the first post in this &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=dEqDPVPYRDI&amp;amp;list=PLVNnZvjyvjgo&quot;&gt;TanStack Start series&lt;/a&gt;. If you want to keep up with the full stack, you can also go through the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=tJGuxqhv2SY&amp;amp;list=PLCsGGtToskOE&quot;&gt;Directus series&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;create-a-simple-header&quot;&gt;Create a simple header&lt;/h3&gt;
&lt;p&gt;Open the app in VS Code. In &lt;code&gt;src/components&lt;/code&gt;, create a file called &lt;code&gt;MainHeader.tsx&lt;/code&gt; with the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default function MainHeader() {
  return &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the div, add another div containing an &lt;code&gt;h1&lt;/code&gt; and a &lt;code&gt;p&lt;/code&gt; tag:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default function MainHeader() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;Logo&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;This is our navigtion menu&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, &quot;navigtion&quot; is missing an &quot;a&quot;. That's deliberate, and you'll see why shortly.&lt;/p&gt;
&lt;p&gt;Now add some Tailwind CSS classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default function MainHeader() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div className=&quot;flex items-center justify-between p-8&quot;&amp;gt;
        &amp;lt;h1 className=&quot;uppercase font-bold text-2xl&quot;&amp;gt;Logo&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;This is our navigtion menu&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;approach-1-paste-the-header-into-every-route&quot;&gt;Approach 1: paste the header into every route&lt;/h3&gt;
&lt;p&gt;Copy the div and its content from &lt;code&gt;MainHeader&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;lt;div&amp;gt;
  &amp;lt;div className=&quot;flex items-center justify-between p-8&quot;&amp;gt;
    &amp;lt;h1 className=&quot;uppercase font-bold text-2xl&quot;&amp;gt;Logo&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This is our navigtion menu&amp;lt;/p&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste it into each route component (index, about, and products) above the first &lt;code&gt;h1&lt;/code&gt;. For example, in the home route:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({ component: Home })

function Home() {
  return (
    &amp;lt;div className=&quot;p-8&quot;&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;div className=&quot;flex items-center justify-between p-8&quot;&amp;gt;
          &amp;lt;h1 className=&quot;uppercase font-bold text-2xl&quot;&amp;gt;Logo&amp;lt;/h1&amp;gt;
          &amp;lt;p&amp;gt;This is our navigtion menu&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;

      &amp;lt;h1 className=&quot;font-bold text-5xl text-slate-700&quot;&amp;gt;Home&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should now see the header on every page. As a reminder from the previous post, the home page is &lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:3000&quot;&gt;http://localhost:3000&lt;/a&gt;, and for every other page you add a forward slash and the page name, like &lt;code&gt;/about&lt;/code&gt; and &lt;code&gt;/products&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We have a site header. But this isn't a good implementation. Remember the misspelled word? To fix it, we'd have to visit every route and correct it. That's not efficient. Delete the header from every page and let's try something better.&lt;/p&gt;
&lt;h3 id=&quot;approach-2-use-the-mainheader-component&quot;&gt;Approach 2: use the MainHeader component&lt;/h3&gt;
&lt;p&gt;Our site should be back to how it was, without a header. This time, import the &lt;code&gt;MainHeader&lt;/code&gt; component in every route and place it above the first &lt;code&gt;h1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import MainHeader from '@/components/MainHeader'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/about')({
  component: RouteComponent,
})

function RouteComponent() {
  return (
    &amp;lt;div className=&quot;p-8&quot;&amp;gt;
      &amp;lt;MainHeader /&amp;gt;
      &amp;lt;h1 className=&quot;font-bold text-5xl text-slate-700&quot;&amp;gt;About&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now go into &lt;code&gt;MainHeader.tsx&lt;/code&gt; and fix &quot;navigtion&quot; to &quot;navigation&quot;. One fix, one file, and it shows up across the app. That's a real improvement.&lt;/p&gt;
&lt;p&gt;It's still not ideal, though, because we have to remember to add &lt;code&gt;MainHeader&lt;/code&gt; to every route component we create.&lt;/p&gt;
&lt;h3 id=&quot;approach-3-put-the-header-in-root-tsx&quot;&gt;Approach 3: put the header in &lt;code&gt;__root.tsx&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;There's a way to call the header once and have it available to every route. Remember &lt;code&gt;__root.tsx&lt;/code&gt;? It wraps our entire app using the &lt;code&gt;children&lt;/code&gt; prop. What if we place the header above &lt;code&gt;children&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;First, remove &lt;code&gt;&amp;lt;MainHeader /&amp;gt;&lt;/code&gt; from your route components. Then update the root:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// Only the code relevant to this discussion is shown
import MainHeader from '@/components/MainHeader'

function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;head&amp;gt;
        &amp;lt;HeadContent /&amp;gt;
      &amp;lt;/head&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
          &amp;lt;MainHeader /&amp;gt;
          {children}
        &amp;lt;/QueryClientProvider&amp;gt;
        &amp;lt;Scripts /&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visit the home page and you'll see the logo on the left and the nav menu on the right, above the page content. Visit &lt;code&gt;/about&lt;/code&gt; and &lt;code&gt;/products&lt;/code&gt; and the header is there too.&lt;/p&gt;
&lt;p&gt;Is this great? Well, not quite. It's efficient: the component is called once and shows on every route, and changes happen in one place. The limitation is flexibility. If we want a different header depending on the page the user is on, we have to add complexity to this file. A good rule of thumb is to keep &lt;code&gt;__root.tsx&lt;/code&gt; as clean and lean as possible.&lt;/p&gt;
&lt;p&gt;So delete the header and its import from &lt;code&gt;__root.tsx&lt;/code&gt;. There's an even better way: layouts.&lt;/p&gt;
&lt;h3 id=&quot;layouts&quot;&gt;Layouts&lt;/h3&gt;
&lt;p&gt;With layouts you get the efficiency of editing one file and having the change ripple through your app, but you can also have different layouts for different pages and still edit just one file for each.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;src/components&lt;/code&gt;, create a &lt;code&gt;layouts&lt;/code&gt; folder. Inside it, create &lt;code&gt;AppLayout.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Outlet } from '@tanstack/react-router'
import MainHeader from '../MainHeader'

export default function AppLayout() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;MainHeader /&amp;gt;
      &amp;lt;div className=&quot;max-w-7xl mx-auto p-4&quot;&amp;gt;
        &amp;lt;Outlet /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only part that might seem strange is &lt;code&gt;Outlet&lt;/code&gt;. It works the same way as the &lt;code&gt;children&lt;/code&gt; prop in &lt;code&gt;__root.tsx&lt;/code&gt;: it's a placeholder where the matched child route gets rendered. That's how a layout wraps a route.&lt;/p&gt;
&lt;h4 id=&quot;create-the-layout-route&quot;&gt;Create the layout route&lt;/h4&gt;
&lt;p&gt;In &lt;code&gt;src/routes&lt;/code&gt;, create a file called &lt;code&gt;_appLayout.tsx&lt;/code&gt;. TanStack Start will scaffold the route for you. You may see a path conflict error with the home route. Don't worry about it, it clears up once we're done. Update the file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import AppLayout from '@/components/layouts/AppLayout'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_appLayout')({
  component: () =&amp;gt; {
    return &amp;lt;AppLayout /&amp;gt;
  },
  notFoundComponent: () =&amp;gt; {
    return &amp;lt;p&amp;gt;This page doesn't exist!&amp;lt;/p&amp;gt;
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's what the two options do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;component&lt;/code&gt;&lt;/strong&gt;: what actually renders when a child route matches. Since this is a layout route, &lt;code&gt;AppLayout&lt;/code&gt; contains an &lt;code&gt;&amp;lt;Outlet /&amp;gt;&lt;/code&gt;, the placeholder where whichever child route matched gets rendered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;notFoundComponent&lt;/code&gt;&lt;/strong&gt;: defines not-found handling scoped to this layout. It lets different sections of your app show contextually appropriate &quot;not found&quot; messaging instead of one generic message everywhere.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;move-your-routes-under-the-layout&quot;&gt;Move your routes under the layout&lt;/h4&gt;
&lt;p&gt;How do we add pages to this layout? Through the file names. Rename your route files like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_appLayout.index.tsx
_appLayout.about.tsx
_appLayout.products.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then remove &lt;code&gt;&amp;lt;MainHeader /&amp;gt;&lt;/code&gt; from each route component (if you haven't already), since the layout now provides it. The route path inside each file, like &lt;code&gt;createFileRoute('/_appLayout/about')&lt;/code&gt;, is updated for you by the router plugin while the dev server is running.&lt;/p&gt;
&lt;p&gt;You'll notice the whole app layout has changed. The pages are now displayed at a fixed width instead of the full page. What's happening is that the route components are now nested inside the &lt;code&gt;_appLayout&lt;/code&gt; route. You don't see &lt;code&gt;_appLayout&lt;/code&gt; in the browser's URL because the leading underscore tells TanStack Router this is a &lt;strong&gt;pathless route&lt;/strong&gt;, in other words, a layout route.&lt;/p&gt;
&lt;p&gt;To change the header, edit the header component and it renders across the app. To change the layout structure, change one file.&lt;/p&gt;
&lt;h3 id=&quot;what-if-i-need-a-different-header-for-a-specific-page-&quot;&gt;What if I need a different header for a specific page?&lt;/h3&gt;
&lt;p&gt;Layouts have you covered. In &lt;code&gt;src/components&lt;/code&gt;, create another header component called &lt;code&gt;ProductsHeader.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default function ProductsHeader() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div className=&quot;flex items-center justify-between p-8&quot;&amp;gt;
        &amp;lt;h1 className=&quot;uppercase font-bold text-2xl&quot;&amp;gt;Logo&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;This is the Products menu&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now create a layout component for it in &lt;code&gt;src/components/layouts&lt;/code&gt; called &lt;code&gt;ProductsLayout.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { Outlet } from '@tanstack/react-router'
import ProductsHeader from '../ProductsHeader'

export default function ProductsLayout() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;ProductsHeader /&amp;gt;
      &amp;lt;div className=&quot;max-w-7xl mx-auto p-4&quot;&amp;gt;
        &amp;lt;Outlet /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;ProductsHeader&lt;/code&gt; is now part of &lt;code&gt;ProductsLayout&lt;/code&gt;. To make the products route a child of this layout, rename its file from &lt;code&gt;_appLayout.products.tsx&lt;/code&gt; to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_productsLayout.products.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in &lt;code&gt;src/routes&lt;/code&gt;, create &lt;code&gt;_productsLayout.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import ProductsLayout from '@/components/layouts/ProductsLayout'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_productsLayout')({
  component: () =&amp;gt; {
    return &amp;lt;ProductsLayout /&amp;gt;
  },
  notFoundComponent: () =&amp;gt; {
    return &amp;lt;p&amp;gt;This page doesn't exist!&amp;lt;/p&amp;gt;
  },
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the app. The home and about pages share the same header, while the products page has a different one. You could just as easily give the products page an entirely different layout structure, not just a different header.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Here's what we covered:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pasting a header into every route&lt;/strong&gt; works, but a single typo means editing every page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A reusable component&lt;/strong&gt; fixes the typo problem, but you still have to add it to every route by hand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Putting the header in &lt;code&gt;__root.tsx&lt;/code&gt;&lt;/strong&gt; shows it everywhere from one place, but it makes your root file busier, and it's inflexible when pages need different headers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layouts&lt;/strong&gt; give you the best of both: a pathless layout route (a file starting with &lt;code&gt;_&lt;/code&gt;) renders a layout component, and that component uses &lt;code&gt;&amp;lt;Outlet /&amp;gt;&lt;/code&gt; to render whichever child route matched.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A few things to remember:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The underscore prefix makes a route pathless, so it wraps its children without adding to the URL.&lt;/li&gt;
&lt;li&gt;A route file's name decides which layout it lives under, for example &lt;code&gt;_appLayout.about.tsx&lt;/code&gt; versus &lt;code&gt;_productsLayout.products.tsx&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can create as many layouts as your app needs, each with its own header, structure, and not-found handling, and still only edit one file per layout.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the next post we'll keep building on this app. If you found this helpful, leave a comment or a reaction, and follow the series so you don't miss it.&lt;/p&gt;
&lt;p&gt;Happy coding! 🚀&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28879/tanstack-start-layouts-explained-pathless-layout-routes-outlet</guid>
<pubDate>Mon, 28 Sep 2026 06:41:48 +0000</pubDate>
</item>
<item>
<title>Next.js 16 Routing Explained: A Complete Beginner's Guide (2026)</title>
<link>https://coderlegion.com/23721/next-js-16-routing-explained-a-complete-beginners-guide-2026</link>
<description>&lt;p&gt;If you've ever opened a Next.js project and wondered why a folder named &lt;code&gt;(marketing)&lt;/code&gt; doesn't show up in the URL, or what on earth &lt;code&gt;@modal&lt;/code&gt; means in a file path — you're not alone. Routing is one of the first things you touch in Next.js, and it's also one of the most misunderstood parts of the App Router.&lt;/p&gt;
&lt;p&gt;The good news? Once you understand the core idea — &lt;strong&gt;folders are routes&lt;/strong&gt; — everything else is just variations on that one theme.&lt;/p&gt;
&lt;p&gt;By the end of this guide, you'll understand every major routing pattern in Next.js 16: static routes, dynamic routes, nested layouts, route groups, parallel routes, intercepting routes, and catch-all segments. We'll build real examples for each one, not just theory.&lt;/p&gt;
&lt;hr&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Pattern            &lt;/th&gt;&lt;th&gt;Example                        &lt;/th&gt;&lt;th&gt;URL&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Static             &lt;/td&gt;&lt;td&gt;&lt;code&gt;about/page.tsx&lt;/code&gt;               &lt;/td&gt;&lt;td&gt;&lt;code&gt;/about&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dynamic            &lt;/td&gt;&lt;td&gt;&lt;code&gt;[slug]/page.tsx&lt;/code&gt;              &lt;/td&gt;&lt;td&gt;&lt;code&gt;/post/hello&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Catch-all          &lt;/td&gt;&lt;td&gt;&lt;code&gt;[...slug]/page.tsx&lt;/code&gt;           &lt;/td&gt;&lt;td&gt;&lt;code&gt;/docs/a/b&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Optional Catch-all &lt;/td&gt;&lt;td&gt;&lt;code&gt;[[...slug]]/page.tsx&lt;/code&gt;         &lt;/td&gt;&lt;td&gt;&lt;code&gt;/docs&lt;/code&gt; or &lt;code&gt;/docs/a&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Route Group        &lt;/td&gt;&lt;td&gt;&lt;code&gt;(marketing)/pricing/page.tsx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;/pricing&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Parallel Route     &lt;/td&gt;&lt;td&gt;&lt;code&gt;@analytics/page.tsx&lt;/code&gt;          &lt;/td&gt;&lt;td&gt;Slot&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Intercepting Route &lt;/td&gt;&lt;td&gt;&lt;code&gt;(.)photo/[id]/page.tsx&lt;/code&gt;       &lt;/td&gt;&lt;td&gt;Modal&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-next-js-routing-feels-confusing-at-first&quot;&gt;Why Next.js Routing Feels Confusing at First&lt;/h3&gt;
&lt;p&gt;Unlike React Router or other client-side routers, Next.js doesn't use a config file or a &lt;code&gt;&amp;lt;Routes&amp;gt;&lt;/code&gt; component to define your pages. Instead, it uses your &lt;strong&gt;file system&lt;/strong&gt; as the source of truth. The folder structure inside &lt;code&gt;app/&lt;/code&gt; directly maps to the URLs your site responds to.&lt;/p&gt;
&lt;p&gt;This is powerful once it clicks, but it also means special folder names (like &lt;code&gt;[slug]&lt;/code&gt;, &lt;code&gt;(group)&lt;/code&gt;, or &lt;code&gt;@slot&lt;/code&gt;) carry real meaning. Misreading one of these conventions is usually where beginners get stuck.&lt;/p&gt;
&lt;p&gt;Let's go through them one at a time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-basics-how-folders-become-urls&quot;&gt;The Basics: How Folders Become URLs&lt;/h3&gt;
&lt;p&gt;In the App Router, every route lives inside the &lt;code&gt;app/&lt;/code&gt; directory, and a folder only becomes a &lt;em&gt;visible&lt;/em&gt; page when it contains a &lt;code&gt;page.tsx&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/about/page.tsx
export default function AboutPage() {
  return &amp;lt;h1&amp;gt;About Us&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This single file creates the route &lt;code&gt;/about&lt;/code&gt;. No router config, no manual registration — Next.js scans the folder tree and wires it up automatically.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; A folder without a &lt;code&gt;page.tsx&lt;/code&gt; inside it does &lt;strong&gt;not&lt;/strong&gt; create a route. It might just be there to hold layouts, components, or nested routes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;special-files-you-ll-see-in-a-route-folder&quot;&gt;Special Files You'll See in a Route Folder&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;File &lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;page.tsx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Makes the route publicly accessible (the actual UI)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;layout.tsx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Wraps the page (and its children) with shared UI&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;loading.tsx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Shown automatically while the page is loading&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;error.tsx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Catches errors thrown inside that route segment&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;not-found.tsx&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Shown when &lt;code&gt;notFound()&lt;/code&gt; is called or no route matches&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We'll use most of these as we go.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;nested-routes&quot;&gt;Nested Routes&lt;/h3&gt;
&lt;p&gt;Want &lt;code&gt;/blog/getting-started&lt;/code&gt; to be a real page? Just nest folders:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;app/
└── blog/
    ├── page.tsx              → /blog
    └── getting-started/
        └── page.tsx          → /blog/getting-started
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/blog/getting-started/page.tsx
export default function GettingStartedPage() {
  return &amp;lt;h1&amp;gt;Getting Started With Next.js&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each folder level adds a segment to the URL path. There's no limit to how deep you can nest — Next.js just keeps following the folders.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;dynamic-routes&quot;&gt;Dynamic Routes&lt;/h3&gt;
&lt;p&gt;Static folders are great until you need a page per blog post, per product, or per user. That's where &lt;strong&gt;dynamic routes&lt;/strong&gt; come in, using square brackets in the folder name.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;app/
└── blog/
    └── [slug]/
        └── page.tsx          → /blog/anything-here
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/blog/[slug]/page.tsx
interface BlogPostPageProps {
  params: Promise&amp;lt;{ slug: string }&amp;gt;;
}

export default async function BlogPostPage({ params }: BlogPostPageProps) {
  const { slug } = await params;

  return &amp;lt;h1&amp;gt;Reading post: {slug}&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Visiting &lt;code&gt;/blog/nextjs-routing-guide&lt;/code&gt; renders this page with &lt;code&gt;slug&lt;/code&gt; equal to &lt;code&gt;&quot;nextjs-routing-guide&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; In Next.js 16, &lt;code&gt;params&lt;/code&gt; is a &lt;strong&gt;Promise&lt;/strong&gt;, not a plain object. You must &lt;code&gt;await&lt;/code&gt; it before reading values — forgetting this is one of the most common upgrade bugs when migrating from older Next.js versions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;generating-pages-at-build-time&quot;&gt;Generating Pages at Build Time&lt;/h4&gt;
&lt;p&gt;If you know all possible slugs ahead of time (e.g. you're reading blog posts from the file system or a CMS), you can pre-render them at build time with &lt;code&gt;generateStaticParams&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts();

  return posts.map((post) =&amp;gt; ({
    slug: post.slug,
  }));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Next.js &quot;here are all the dynamic values that exist — build a static page for each one&quot; instead of rendering them on-demand at request time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;catch-all-and-optional-catch-all-routes&quot;&gt;Catch-All and Optional Catch-All Routes&lt;/h3&gt;
&lt;p&gt;Sometimes one dynamic segment isn't enough. Maybe you want &lt;code&gt;/docs/a&lt;/code&gt;, &lt;code&gt;/docs/a/b&lt;/code&gt;, and &lt;code&gt;/docs/a/b/c&lt;/code&gt; to all hit the same page, with the rest of the path available as an array. That's a &lt;strong&gt;catch-all route&lt;/strong&gt;, written with three dots inside the brackets.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;app/
└── docs/
    └── [...slug]/
        └── page.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/docs/[...slug]/page.tsx
interface DocsPageProps {
  params: Promise&amp;lt;{ slug: string[] }&amp;gt;;
}

export default async function DocsPage({ params }: DocsPageProps) {
  const { slug } = await params;

  // For /docs/a/b/c, slug = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]
  return &amp;lt;h1&amp;gt;Docs path: {slug.join(&quot; / &quot;)}&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's also an &lt;strong&gt;optional&lt;/strong&gt; catch-all, which uses double brackets &lt;code&gt;[[...slug]]&lt;/code&gt;. The difference: with optional catch-all, &lt;code&gt;/docs&lt;/code&gt; itself (with no extra segments) also matches the same page, instead of returning a 404.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;app/
└── docs/
    └── [[...slug]]/
        └── page.tsx          → matches /docs, /docs/a, /docs/a/b, etc.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Catch-all routes are exactly how Clerk's &lt;code&gt;[[...sign-in]]&lt;/code&gt; pattern works under the hood — one page handles every sub-step of the auth flow.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;layouts-shared-ui-across-routes&quot;&gt;Layouts: Shared UI Across Routes&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;layout.tsx&lt;/code&gt; wraps every page inside its folder (and all nested folders below it) without losing state on navigation — things like a sidebar won't re-render or flicker when you click between pages.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/blog/layout.tsx
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &amp;lt;div className=&quot;flex gap-6&quot;&amp;gt;
      &amp;lt;aside className=&quot;w-48&quot;&amp;gt;Blog Sidebar&amp;lt;/aside&amp;gt;
      &amp;lt;main className=&quot;flex-1&quot;&amp;gt;{children}&amp;lt;/main&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every page under &lt;code&gt;app/blog/&lt;/code&gt; — including &lt;code&gt;[slug]/page.tsx&lt;/code&gt; — now renders inside this layout automatically. Layouts nest too: the root &lt;code&gt;app/layout.tsx&lt;/code&gt; wraps everything, and a deeper layout wraps just its own subtree.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Layouts don't re-render when you navigate between sibling pages that share them. If you need something to reset per-page (like scroll position or a &lt;code&gt;key&lt;/code&gt;-based remount), you'll need to handle that explicitly — layouts are persistent by design.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;route-groups-organizing-without-affecting-the-url&quot;&gt;Route Groups: Organizing Without Affecting the URL&lt;/h3&gt;
&lt;p&gt;Sometimes you want to organize routes into logical folders — like separating marketing pages from authenticated dashboard pages — without those folder names showing up in the URL. Wrapping a folder name in parentheses does exactly that.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;app/
├── (marketing)/
│   ├── layout.tsx        → navbar + footer
│   ├── page.tsx          → /
│   └── pricing/
│       └── page.tsx      → /pricing
│
└── (dashboard)/
    ├── layout.tsx        → sidebar + topbar
    └── settings/
        └── page.tsx      → /settings
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that &lt;code&gt;/pricing&lt;/code&gt; doesn't become &lt;code&gt;/marketing/pricing&lt;/code&gt; — the parentheses tell Next.js &quot;this folder is for organization only, ignore it in the URL.&quot; This is exactly how you'd separate a public marketing layout from a private dashboard layout, each with completely different navigation chrome.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Route groups are also useful for giving the &lt;em&gt;same&lt;/em&gt; URL segment multiple layout options during development, or for splitting large apps into logical sections for your team without touching the live URL structure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;parallel-routes-rendering-multiple-pages-at-once&quot;&gt;Parallel Routes: Rendering Multiple Pages at Once&lt;/h3&gt;
&lt;p&gt;This is where things get genuinely interesting. &lt;strong&gt;Parallel routes&lt;/strong&gt; let you render two or more independent pages in the &lt;em&gt;same&lt;/em&gt; layout, side by side — each with its own loading and error state. You define them with a &lt;code&gt;@&lt;/code&gt; prefix, called a &quot;slot.&quot;&lt;/p&gt;
&lt;p&gt;Imagine a dashboard that shows an analytics chart and a notifications feed at the same time, but each one loads independently:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;app/
└── dashboard/
    ├── layout.tsx
    ├── page.tsx
    ├── @analytics/
    │   └── page.tsx
    └── @notifications/
        └── page.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  analytics,
  notifications,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  notifications: React.ReactNode;
}) {
  return (
    &amp;lt;div className=&quot;grid grid-cols-2 gap-4&quot;&amp;gt;
      &amp;lt;div className=&quot;col-span-2&quot;&amp;gt;{children}&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;{analytics}&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;{notifications}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each slot (&lt;code&gt;@analytics&lt;/code&gt;, &lt;code&gt;@notifications&lt;/code&gt;) is passed into the layout as a prop, matching its folder name. Next.js renders them all in parallel — if &lt;code&gt;@notifications&lt;/code&gt; is still loading, it won't block &lt;code&gt;@analytics&lt;/code&gt; from showing up.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Slot folders need their own &lt;code&gt;page.tsx&lt;/code&gt; (or a &lt;code&gt;default.tsx&lt;/code&gt; as a fallback) — if a slot has no matching route for the current URL and no &lt;code&gt;default.tsx&lt;/code&gt;, Next.js will throw a 404 for the &lt;em&gt;entire&lt;/em&gt; layout, not just that slot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/dashboard/@notifications/default.tsx
export default function Default() {
  return null; // fallback when no route matches this slot
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;intercepting-routes-modals-that-feel-like-pages&quot;&gt;Intercepting Routes: Modals That Feel Like Pages&lt;/h3&gt;
&lt;p&gt;Ever clicked a photo on Instagram and seen it open in a modal — but if you refresh that same URL, it loads as a full standalone page instead? That's an &lt;strong&gt;intercepting route&lt;/strong&gt;, and Next.js supports this pattern natively using &lt;code&gt;(.)&lt;/code&gt;-style folder prefixes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;app/
├── feed/
│   ├── page.tsx
│   └── @modal/
│       ├── default.tsx
│       └── (.)photo/
│           └── [id]/
│               └── page.tsx     → intercepted modal view
└── photo/
    └── [id]/
        └── page.tsx              → full page (direct visit / refresh)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The convention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(.)&lt;/code&gt; intercepts a route at the &lt;strong&gt;same&lt;/strong&gt; level&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(..)&lt;/code&gt; intercepts one level &lt;strong&gt;above&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(..)(..)&lt;/code&gt; intercepts two levels above&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(...)&lt;/code&gt; intercepts from the &lt;strong&gt;root&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/feed/@modal/(.)photo/[id]/page.tsx
export default async function PhotoModal({
  params,
}: {
  params: Promise&amp;lt;{ id: string }&amp;gt;;
}) {
  const { id } = await params;

  return (
    &amp;lt;div className=&quot;fixed inset-0 flex items-center justify-center bg-black/50&quot;&amp;gt;
      &amp;lt;div className=&quot;bg-white p-6 rounded-xl&quot;&amp;gt;Photo {id} (modal view)&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So when a user clicks a photo &lt;em&gt;while browsing the feed&lt;/em&gt;, Next.js intercepts the navigation and shows the modal. But if they paste that same &lt;code&gt;/photo/123&lt;/code&gt; URL directly into a new tab, Next.js skips the interception and renders the real &lt;code&gt;app/photo/[id]/page.tsx&lt;/code&gt; as a normal full page.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Intercepting routes are almost always paired with parallel routes (the &lt;code&gt;@modal&lt;/code&gt; slot) — the slot is what lets the modal render &lt;em&gt;on top of&lt;/em&gt; the feed instead of replacing it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;linking-between-routes&quot;&gt;Linking Between Routes&lt;/h3&gt;
&lt;p&gt;All of this routing is useless without navigation. Always use Next.js's &lt;code&gt;Link&lt;/code&gt; component instead of a plain &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag — it enables client-side transitions and automatic prefetching.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import Link from &quot;next/link&quot;;

export default function Nav() {
  return (
    &amp;lt;nav&amp;gt;
      &amp;lt;Link href=&quot;/blog&quot;&amp;gt;Blog&amp;lt;/Link&amp;gt;
      &amp;lt;Link href=&quot;/blog/nextjs-routing-guide&quot;&amp;gt;A Specific Post&amp;lt;/Link&amp;gt;
    &amp;lt;/nav&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For programmatic navigation (e.g. redirecting after a form submits), use the &lt;code&gt;useRouter&lt;/code&gt; hook in a Client Component:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;&quot;use client&quot;;

import { useRouter } from &quot;next/navigation&quot;;

export default function SubmitButton() {
  const router = useRouter();

  return (
    &amp;lt;button onClick={() =&amp;gt; router.push(&quot;/dashboard&quot;)}&amp;gt;
      Go to Dashboard
    &amp;lt;/button&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;do-route-groups-affect-performance-&quot;&gt;Do route groups affect performance?&lt;/h4&gt;
&lt;p&gt;No. Route groups are purely an organizational tool at build time — they don't add extra network requests, extra DOM nodes, or any runtime cost. They simply change how your files are organized without changing the URL.&lt;/p&gt;
&lt;h4 id=&quot;can-i-combine-dynamic-routes-with-route-groups-&quot;&gt;Can I combine dynamic routes with route groups?&lt;/h4&gt;
&lt;p&gt;Yes, and it's common. For example, &lt;code&gt;app/(dashboard)/projects/[id]/page.tsx&lt;/code&gt; gives you &lt;code&gt;/projects/123&lt;/code&gt; while keeping the dashboard layout grouping intact.&lt;/p&gt;
&lt;h4 id=&quot;what-s-the-difference-between-loading-tsx-and-a-parallel-route-s-own-loading-state-&quot;&gt;What's the difference between &lt;code&gt;loading.tsx&lt;/code&gt; and a parallel route's own loading state?&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;loading.tsx&lt;/code&gt; shows a fallback for an entire route segment while its data loads. Inside parallel routes, each slot (&lt;code&gt;@analytics&lt;/code&gt;, &lt;code&gt;@notifications&lt;/code&gt;, etc.) can have its &lt;em&gt;own&lt;/em&gt; &lt;code&gt;loading.tsx&lt;/code&gt;, so one slot can show a spinner while another already displays its content — they load independently.&lt;/p&gt;
&lt;h4 id=&quot;why-did-my-page-404-after-adding-a-parallel-route-slot-&quot;&gt;Why did my page 404 after adding a parallel route slot?&lt;/h4&gt;
&lt;p&gt;This usually means a slot (like &lt;code&gt;@modal&lt;/code&gt;) has no &lt;code&gt;page.tsx&lt;/code&gt; matching the current URL and no &lt;code&gt;default.tsx&lt;/code&gt; fallback. Add a &lt;code&gt;default.tsx&lt;/code&gt; that returns &lt;code&gt;null&lt;/code&gt; (or some neutral fallback UI) to fix it.&lt;/p&gt;
&lt;h4 id=&quot;can-intercepting-routes-work-without-parallel-routes-&quot;&gt;Can intercepting routes work without parallel routes?&lt;/h4&gt;
&lt;p&gt;Technically you can intercept into the main &lt;code&gt;children&lt;/code&gt; slot, but in practice almost every real-world use case (modals, lightboxes, previews) pairs intercepting routes with a parallel &lt;code&gt;@slot&lt;/code&gt; so the intercepted content renders &lt;em&gt;alongside&lt;/em&gt; the existing page instead of replacing it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;Next.js 16's App Router routing system might look intimidating with all its special folder syntax, but it really boils down to a handful of conventions: brackets for dynamic segments, dots for catch-alls, parentheses for invisible grouping, and &lt;code&gt;@&lt;/code&gt; for parallel slots. Once you've built each pattern once with your own hands, reading someone else's project structure stops feeling like decoding a puzzle.&lt;/p&gt;
&lt;p&gt;From here, a natural next step is wiring up authentication-protected routes using these same patterns — check out the Clerk guide below to see route groups and catch-all routes used together in a real auth flow.&lt;/p&gt;
&lt;h3 id=&quot;useful-resources&quot;&gt;Useful Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/building-your-application/routing&quot;&gt;Next.js Routing Fundamentals Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot;&gt;Next.js Parallel Routes Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes&quot;&gt;Next.js Intercepting Routes Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;continue-learning&quot;&gt;Continue Learning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/how-to-add-clerk-authentication-in-nextjs-16&quot;&gt;How to Add Clerk Authentication in Next.js 16 (2026 Guide)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/saas-starter-architecture-nextjs&quot;&gt;SaaS Starter Architecture with Next.js 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23721/next-js-16-routing-explained-a-complete-beginners-guide-2026</guid>
<pubDate>Wed, 29 Jul 2026 19:00:00 +0000</pubDate>
</item>
<item>
<title>Routing in Alusus WebPlatform</title>
<link>https://coderlegion.com/15932/routing-in-alusus-webplatform</link>
<description>&lt;p&gt;Routing is one of the important elements in any web library, and it is also one of the things that the Alusus language handles in a distinctive way. Routing in WebPlatform provides the following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatically loading the page that matches the current URL.&lt;/li&gt;
&lt;li&gt;Navigating between pages without reloading.&lt;/li&gt;
&lt;li&gt;Updating the address bar when navigating between pages.&lt;/li&gt;
&lt;li&gt;Responding to the browser's back button without reloading the site.&lt;/li&gt;
&lt;li&gt;Applying transition animations when navigating between pages and allowing the user to create their own custom animations.&lt;/li&gt;
&lt;li&gt;Providing two navigation modes: a replacement mode where one page replaces another, and a stacking mode where a page is loaded on top of the current one with the intent of dismissing it later to return to the previous page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last point is what we would like to focus on in this article, as WebPlatform supports these features in a unique way that I have not seen in any other web library.&lt;/p&gt;
&lt;h3 id=&quot;the-switcher-and-the-stack&quot;&gt;The Switcher and the Stack&lt;/h3&gt;
&lt;p&gt;WebPlatform supports routing operations by providing two fundamental components dedicated to this purpose: &lt;code&gt;Switcher&lt;/code&gt; and &lt;code&gt;Stack&lt;/code&gt;. The Switcher allows switching between pages by removing the current page from memory and creating a different one, while the Stack allows the user to load pages on top of each other to enable returning to previous pages later without losing their data and state.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10401859865556065733&quot; alt=&quot;Switcher vs Stack&quot;&gt;&lt;/p&gt;
&lt;p&gt;The user can make these two components fill the entire screen or just part of it. Multiple instances of each can be created and combined within an application in any desired configuration, allowing great flexibility in designing any site map. For example, a user can create a Stack with a Switcher inside it, where the Switcher is responsible for switching between the site's pages, while the Stack is used to display popup windows or temporary pages that appear on top of the site (i.e., on top of the Switcher) regardless of which page the user is currently on.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=4684790636427676050&quot; alt=&quot;Switcher within a Stack&quot;&gt;&lt;/p&gt;
&lt;p&gt;The user can also do the reverse: create a Switcher and embed a Stack within one of its views. In this case, the Stack is used to display a temporary window that appears on top of that specific page only, not the entire site — meaning that navigating to another page removes the current page along with its associated popup.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=287339134956846489&quot; alt=&quot;Stack within a Switcher&quot;&gt;&lt;/p&gt;
&lt;p&gt;Multiple Switchers or Stacks can also be created as desired and in any configuration. For example, a main Switcher can be created for the site's primary pages, and then within one of those pages a secondary Switcher can be embedded to handle navigation between sub-pages within that main page.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8196820819210375600&quot; alt=&quot;Switcher within a Switcher&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;routing&quot;&gt;Routing&lt;/h3&gt;
&lt;p&gt;The two base classes &lt;code&gt;Switcher&lt;/code&gt; and &lt;code&gt;Stack&lt;/code&gt; are only responsible for providing the view-switching operation, which is the fundamental mechanism on which routing is built — but they do not provide true routing that binds a URL to the requested page. That responsibility falls on the &lt;code&gt;RoutingSwitcher&lt;/code&gt; and &lt;code&gt;RoutingStack&lt;/code&gt; classes, which rely on the former two to provide view transitions and link them to the browser's address bar.&lt;/p&gt;
&lt;p&gt;In both classes, the user needs to specify a set of routes along with a closure for each route that creates the desired view for that route. Both classes use regular expressions to determine which view to display, with a difference in the algorithm used as described below.&lt;/p&gt;
&lt;p&gt;Before diving into details, it should be noted that all of this assumes you place the entire site under a single UI endpoint — meaning that instead of relying on multiple functions annotated with &lt;code&gt;@uiEndpoint&lt;/code&gt;, each dedicated to a specific path, you instead use a single function that covers all the paths handled by the two routing components mentioned above. This is done by using a wildcard &lt;code&gt;*&lt;/code&gt; in the endpoint path. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;@uiEndpoint[&quot;/*&quot;]
@title[&quot;WebPlatform Example&quot;]
func main {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, we tell WebPlatform to route every path starting with &lt;code&gt;/&lt;/code&gt; to the same function, effectively moving all routing from the server to the browser. Of course, you can restrict the scope to a smaller path, such as using a specific prefix followed by &lt;code&gt;*&lt;/code&gt; — for instance, &lt;code&gt;/ui/*&lt;/code&gt; — to tell WebPlatform that any path beginning with &lt;code&gt;/ui/&lt;/code&gt; should be routed to that function.&lt;/p&gt;
&lt;p&gt;It is worth noting that WebPlatform uses priorities when matching paths: it gives priority to back-end endpoint paths, then resource paths, before UI endpoint paths. Therefore, setting the UI endpoint path to &lt;code&gt;/*&lt;/code&gt; will not prevent access to an API endpoint like &lt;code&gt;/api/login&lt;/code&gt;, for example.&lt;/p&gt;
&lt;h4 id=&quot;routingswitcher&quot;&gt;RoutingSwitcher&lt;/h4&gt;
&lt;p&gt;The algorithm used by &lt;code&gt;RoutingSwitcher&lt;/code&gt; is very straightforward: it iterates through the defined routes in sequence, matching each route's regular expression against the full current URL. If a match is found, that view is displayed and iteration stops. If no match is found, it continues to the next definition, and so on.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;RoutingSwitcher().{
    route(&quot;^/one$&quot;) = closure(RoutePayload): SrdRef[Widget] {
        return View1();
    };
    route(&quot;^/two$&quot;) = closure(RoutePayload): SrdRef[Widget] {
        return View2();
    };
    route(&quot;^/three$&quot;) = closure(RoutePayload): SrdRef[Widget] {
        return View3();
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But what if we want to embed a &lt;code&gt;RoutingSwitcher&lt;/code&gt; inside another one — for instance, if &lt;code&gt;View3&lt;/code&gt; contains another Switcher for navigating between inner pages? Suppose the sub-paths are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/three/subview1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/three/subview2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, we need to account for these paths in the regular expressions of both the outer and inner Switchers. We change the outer Switcher's route from &lt;code&gt;^/three$&lt;/code&gt; to &lt;code&gt;^/three&lt;/code&gt; — i.e., by removing the &lt;code&gt;$&lt;/code&gt; — to allow View3 to be created when sub-paths match. Otherwise, the inner Switcher would never be instantiated and the inner pages could never be shown. Likewise, the inner Switcher's routes must account for the presence of the &lt;code&gt;three&lt;/code&gt; segment in the URL, either by writing the full URL or by writing the sub-path without using the &lt;code&gt;^&lt;/code&gt; anchor.&lt;/p&gt;
&lt;h4 id=&quot;routingstack&quot;&gt;RoutingStack&lt;/h4&gt;
&lt;p&gt;The routing Stack works differently. Instead of iterating through all views looking for the one matching view, it does the opposite: it iterates through all views looking for the first non-matching view. It keeps creating views one on top of another until it reaches a view whose path does not match, at which point it stops. Therefore, the user must ensure that each view defines a path that matches all the views before it, otherwise it will never be displayed. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;RoutingStack().{
    route(String(&quot;^/one&quot;)) = closure(RoutePayload): SrdRef[Widget] {
        return View1();
    };
    route(String(&quot;^/one/edit&quot;)) = closure(RoutePayload): SrdRef[Widget] {
        return Editor();
    };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the current path is &lt;code&gt;/one&lt;/code&gt;, only &lt;code&gt;View1&lt;/code&gt; is displayed. If the path is &lt;code&gt;/one/edit&lt;/code&gt;, then &lt;code&gt;View1&lt;/code&gt; is displayed with &lt;code&gt;Editor&lt;/code&gt; stacked on top of it.&lt;/p&gt;
&lt;p&gt;What if you have more than one sub-view that you want to display on top of the current view — say &lt;code&gt;/one/edit&lt;/code&gt; and &lt;code&gt;/one/share&lt;/code&gt;? To handle this, you can make the sub-view a Switcher that decides which of the two views to load. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;RoutingStack().{
    route(String(&quot;^/one&quot;)) = closure(RoutePayload): SrdRef[Widget] {
        return View1();
    };
    route(String(&quot;^/one/(edit|share)&quot;)) = closure(RoutePayload): SrdRef[Widget] {
        return RoutingSwitcher().{
            route(&quot;^/one/edit$&quot;) = closure(RoutePayload): SrdRef[Widget] {
                return Editor();
            };
            route(&quot;^/one/share$&quot;) = closure(RoutePayload): SrdRef[Widget] {
                return Share();
            };
        };
    };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;navigation-without-reloading&quot;&gt;Navigation Without Reloading&lt;/h3&gt;
&lt;p&gt;Relying on traditional hyperlinks allows navigation between pages, but clicking a traditional hyperlink causes the page to reload. To avoid reloading, we use the &lt;code&gt;pushLocation&lt;/code&gt; function from the &lt;code&gt;Window&lt;/code&gt; class. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;Window.instance.pushLocation(&quot;/one/edit&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This updates the address bar in the browser and then navigates to the requested view. WebPlatform will take care of displaying the new view with the appropriate transition animations. Pressing the browser's back button afterward will return to the previous page with transition animations, without reloading the page.&lt;/p&gt;
&lt;p&gt;This does not mean hyperlinks should be avoided entirely — they serve an important purpose: informing search engines about the pages that can be navigated to. If the page is private and not meant for search engine indexing, hyperlinks can be omitted. But if it is a public page that we want search engines to index, it is better to use hyperlinks alongside &lt;code&gt;pushLocation&lt;/code&gt;, as in this example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;Hyperlink().{
    url = String(&quot;/about&quot;);
    setChild(Text(String(&quot;About&quot;)));
    onClick.defaultPrevented = 1;
    onClick.connect(closure (ref[Widget], ref[Int]) {
        Window.instance.pushLocation(&quot;/about&quot;);
    });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, we use a hyperlink but override its click behavior to use &lt;code&gt;pushLocation&lt;/code&gt; instead of relying on the default behavior that reloads the page. This way we get the best of both worlds: smooth navigation with animations and no reloading, plus links that search engines can read.&lt;/p&gt;
&lt;h3 id=&quot;animations&quot;&gt;Animations&lt;/h3&gt;
&lt;p&gt;The Switcher and Stack both support transition animations when moving from one view to another, and they allow the user to specify these animations — either by defining a custom animation style or by choosing from a set of predefined effects. Animations are set using the &lt;code&gt;setTransition&lt;/code&gt; function available in both &lt;code&gt;RoutingSwitcher&lt;/code&gt; and &lt;code&gt;RoutingStack&lt;/code&gt;. It accepts two arguments: the first specifies the animation when transitioning to a higher view (according to the order in the view list), and the second specifies the animation for the reverse direction. For example, to set a transition on the Switcher:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;setTransition(
    createSlideSwitcherTransition(0.5, 0, 0),
    createSlideSwitcherTransition(0.5, 0, 1)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function above uses a slide effect, meaning the new view slides in from and out to the edge of the screen. The first argument specifies the duration of the slide in seconds, the second specifies whether the slide is vertical (1) or horizontal (0), and the third specifies the direction of movement — entering or exiting.&lt;/p&gt;
&lt;p&gt;The following functions are available for creating Switcher animations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;createSlideSwitcherTransition&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createFadeSwitcherTransition&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the following for Stack animations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;createSlideStackTransition&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createFadeStackTransition&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createFadeWithScaleStackTransition&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Creating your own custom animations is also straightforward. You can look at the definitions of these functions in the &lt;code&gt;WebPlatform&lt;/code&gt; library to learn how to create your own animations.&lt;/p&gt;
&lt;h3 id=&quot;full-example&quot;&gt;Full Example&lt;/h3&gt;
&lt;p&gt;The following WebPlatform example demonstrates routing using these two components:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Alusus/WebPlatform/blob/main/Examples/routing_showcase.alusus&quot;&gt;https://github.com/Alusus/WebPlatform/blob/main/Examples/routing_showcase.alusus&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/15932/routing-in-alusus-webplatform</guid>
<pubDate>Mon, 27 Apr 2026 00:34:02 +0000</pubDate>
</item>
</channel>
</rss>