Next.jsBeginner

Next.js Fundamentals: App Router & Components

Start building with the Next.js App Router. Understand file-based routing, the split between Server and Client Components, client-side navigation with Link, static assets, and how environment variables keep secrets safe.

4 sections · ~25 min · 5-question quiz (pass ≥ 70%)

1App Router Basics: Files Become Routes

Next.js 13+ uses the App Router — a app/ directory where folders define URL segments and special files define UI:

app/
  layout.tsx      → root shell (wraps every page)
  page.tsx        → route UI for /
  about/
    page.tsx      → /about
  blog/
    page.tsx      → /blog
    [slug]/
      page.tsx    → /blog/my-post (dynamic segment)

Every route needs a page.tsx to be publicly accessible. Optional files:

  • layout.tsx — shared UI that wraps child routes (persists across navigation).
  • loading.tsx — instant fallback UI while a segment loads.
  • error.tsx — error boundary for that segment.
  • not-found.tsx — custom 404 for the segment.

Layouts nest automatically. The root app/layout.tsx wraps everything — typically holding <html>, <body>, fonts, and global providers. Nested layouts (app/dashboard/layout.tsx) wrap only their subtree.

Pages are React Server Components by default in the App Router. They can fetch data directly without useEffect, and their JavaScript is not shipped to the client unless they import client-only features.

2Server Components vs Client Components

The App Router's biggest mental shift: components render on the server unless you opt into the client.

Server Components (default):

  • Run only on the server — zero client bundle cost for their logic.
  • Can await database queries and file reads directly in the component body.
  • Cannot use hooks (useState, useEffect) or browser APIs.

Client Components — add "use client" at the top of the file:

"use client";

import { useState } from "react";

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

Use Client Components for interactivity, effects, and browser-only APIs. Keep them as leaf nodes as much as possible — wrap a small interactive island inside a Server Component page:

// app/page.tsx — Server Component (no directive)
import { Counter } from "./Counter";

export default async function HomePage() {
  const posts = await db.post.findMany();
  return (
    <main>
      <h1>Latest Posts</h1>
      <PostList posts={posts} />
      <Counter />
    </main>
  );
}

This pattern minimizes JavaScript sent to the browser while keeping the page fast and SEO-friendly.

3Link, Navigation & Layouts

Use next/link for client-side navigation — faster transitions without full page reloads:

import Link from "next/link";

export function Nav() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/blog/hello-world">Latest post</Link>
    </nav>
  );
}

Link prefetches routes in the viewport by default (in production), so clicks feel instant. Use prefetch={false} for rarely visited pages.

Programmatic navigation in Client Components uses the useRouter hook from next/navigation:

"use client";
import { useRouter } from "next/navigation";

function LogoutButton() {
  const router = useRouter();
  return (
    <button onClick={() => {
      signOut();
      router.push("/login");
    }}>
      Log out
    </button>
  );
}

Layouts share chrome across routes. A dashboard layout might render a sidebar once while child pages swap:

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
  return (
    <div className="flex">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

When navigating between /dashboard/settings and /dashboard/billing, the layout persists — only children re-render.

4Public Assets & Environment Variables

Static files that don't need processing live in the public/ directory at the project root:

public/
  logo.svg
  favicon.ico
  images/hero.png

Reference them from the site root — /logo.svg, not /public/logo.svg. Next.js serves them as-is with cache-friendly headers.

For images that benefit from optimization, prefer next/image (covered in the advanced track). For simple icons and downloads, public/ is fine.

Environment variables configure your app per environment. Create .env.local (git-ignored) for secrets:

# .env.local
DATABASE_URL="postgresql://..."
NEXT_PUBLIC_APP_URL="http://localhost:3000"

Rules:

  • Variables without the NEXT_PUBLIC_ prefix are available only on the server — safe for API keys and database URLs.
  • Variables with NEXT_PUBLIC_ are inlined into the client bundle. Never put secrets there.
  • Access via process.env.VAR_NAME in Server Components, route handlers, and server actions.
  • Client Components can only read NEXT_PUBLIC_ variables.

Restart the dev server after changing .env files — values are loaded at build/start time, not on every request.

Ready to test yourself?

Sign in to take the quiz, track progress, and earn a certificate.

Sign in