beginner10 min readPart 1 of 3

Next.js App Router Fundamentals: Routing, Layouts & Server Components

Master the Next.js App Router foundations: file-based routing, nested layouts, server vs client components, and the loading/error conventions that structure every modern Next.js app.

TechLogHub
·

Prerequisites

  • •Solid React basics (components, props, state)
  • •Node.js 18+ installed

What You'll Learn

  • Map URLs to files with the app/ directory conventions
  • Compose nested layouts that preserve state across navigation
  • Decide correctly between server and client components
  • Add instant loading and error states with route conventions

The Mental Model

The App Router (the app/ directory) is built on one idea: your folder structure is your UI structure. Folders define URL segments; special files inside them define what renders at each segment — and React Server Components make the server the default place your components run.

npx create-next-app@latest my-app
cd my-app && npm run dev

Files That Mean Something

Inside app/, a handful of filenames are conventions the router understands:

  • page.tsx — the UI for a route. A folder becomes a navigable URL only when it contains one.
  • layout.tsx — shared UI that wraps every page beneath it. Layouts nest.
  • loading.tsx — instant loading UI shown while the page's data streams in.
  • error.tsx — an error boundary for the segment.
  • not-found.tsx — rendered by notFound() or unmatched routes.
  • So this tree:

    app/
      layout.tsx        <- wraps everything (html, body, nav)
      page.tsx          <- /
      blog/
        layout.tsx      <- wraps all blog pages (sidebar)
        page.tsx        <- /blog
        [slug]/
          page.tsx      <- /blog/hello-world

    …gives you /, /blog, and /blog/anything with zero routing code.

    Layouts: Persistent by Design

    A layout renders its children through the children prop:

    // app/blog/layout.tsx
    export default function BlogLayout({ children }: { children: React.ReactNode }) {
      return (
        <div className="grid grid-cols-[240px_1fr] gap-8">
          <BlogSidebar />
          <main>{children}</main>
        </div>
      );
    }

    The crucial property: navigating between /blog/post-a and /blog/post-b does not re-render the layout. Sidebar scroll position, playing audio, expanded menus — all preserved. This is why layouts, not copy-pasted wrappers in each page, are the right home for chrome.

    Dynamic Segments

    Square brackets capture URL parts. In current Next.js, params is a Promise — await it:

    // app/blog/[slug]/page.tsx
    export default async function PostPage({
      params,
    }: {
      params: Promise<{ slug: string }>;
    }) {
      const { slug } = await params;
      const post = await getPost(slug);
      if (!post) notFound();
      return <article>{/ ... /}</article>;
    }

    [...parts] catches all remaining segments, and (groups) — parentheses — organize folders without affecting the URL (the classic use: (marketing) and (app) route groups with different root layouts).

    Server Components by Default — and Why

    Every component in app/ is a Server Component unless you opt out. Server components:

  • run only on the server — they can query databases and read secrets directly
  • ship zero JavaScript to the browser
  • can be async and await data right in the component body
  • // This is a complete, legitimate data-fetching component.
    export default async function ProductList() {
      const products = await db.product.findMany({ take: 10 });
      return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
    }

    No useEffect, no loading-state juggling, no API route just to feed your own UI.

    "use client" — the Interactivity Boundary

    The browser still owns interactivity. When a component needs state, effects, or event handlers, mark it:

    "use client";

    import { useState } from "react";

    export function Counter() { const [n, setN] = useState(0); return <button onClick={() => setN(n + 1)}>Clicked {n}x</button>; }

    Two rules keep this clean:

  • "use client" marks a boundary, not a single file — everything that component imports becomes client code too. Put the directive on small leaf components, not on pages.
  • Server components can render client components and pass them props (including JSX as children) — that's the composition pattern: server shell, client islands.
  • Instant Loading and Error States

    Drop a loading.tsx next to a page and the router shows it immediately on navigation while the server component's data resolves — powered by streaming and Suspense under the hood:

    // app/blog/loading.tsx
    export default function Loading() {
      return <div className="animate-pulse h-40 rounded-xl bg-zinc-800" />;
    }

    error.tsx must be a client component and receives error and a reset() retry function. Together these two files give every route professional UX with ~10 lines of code.

    Linking

    Use next/link for client-side navigation with automatic prefetching, and the useRouter hook (client components only) for programmatic pushes.


    Next: where data actually comes from — fetch semantics, caching layers, and ISR — in Part 2: Data Fetching & Caching.

    nextjsreactapp-routerserver-componentsfrontend

    Keep the references coming

    One email a week: new and trending developer tools, fresh comparisons, and what shipped. Unsubscribe in one click.