Get Stack WiseArticles
Web Development

Next.js 14 App Router: A Practical Guide

A hands-on guide to the Next.js 14 App Router: server components, data fetching, caching, and when to reach for a client component.

Jordan Alvarez3 min read
A developer working on a Next.js project in a code editor

The App Router is no longer the new kid. As of Next.js 14 it's the default, it's stable, and it's how most US teams start new projects. But the mental model is genuinely different from the Pages Router, and that's where people trip up. Here's what actually matters day to day.

Server components are the default

Every component under app/ is a React Server Component unless you say otherwise. That means it runs on the server, never ships to the browser, and can talk directly to your database or filesystem.

// app/posts/page.tsx — runs on the server
import { db } from '@/lib/db';

export default async function PostsPage() {
  const posts = await db.post.findMany();
  return <PostList posts={posts} />;
}

No useEffect, no loading spinner boilerplate, no API route in the middle. You await your data right in the component.

When you actually need a client component

Reach for 'use client' only when you need interactivity or browser APIs:

  • State and effects (useState, useEffect)
  • Event handlers (onClick, onChange)
  • Browser-only APIs (localStorage, window)
  • Third-party libraries that use any of the above

The trick most people miss: keep client components small and push them to the leaves of your tree. A single interactive button doesn't require the whole page to be a client component.

Server Components vs Client Components

ConcernServer ComponentClient Component
Runs whereServer onlyServer (initial) + browser
Ships JS to browserNoYes
Data fetchingDirect awaitfetch in effect or a lib
State / interactivityNoYes
Access to DB / secretsYesNo
Marked with(default)'use client'

Data fetching and caching

In the App Router, fetch is extended with caching controls. This is the part that changed most in the Next.js 14 era, so read the labels carefully:

  • fetch(url) — cached by default (static)
  • fetch(url, { cache: 'no-store' }) — always fresh (dynamic)
  • fetch(url, { next: { revalidate: 3600 } }) — ISR: revalidate every hour

For content sites, revalidate is your friend. You get static-fast pages that still pick up new content without a full redeploy.

A few rules that save you pain

  1. Don't import server-only code into client components. Use the server-only package to enforce it.
  2. Colocate loading.tsx and error.tsx with your routes for automatic Suspense and error boundaries.
  3. Use generateStaticParams for dynamic routes you want pre-rendered at build time.

FAQ

Do I have to migrate my Pages Router app?

No. The two routers work side by side in the same project. Migrate route by route when it makes sense, not all at once.

Are Server Components slower because they run on the server?

Usually the opposite. They ship less JavaScript, so the browser has less to parse and execute. The server work is fast and cacheable.

Can I use my favorite data-fetching library like React Query?

Yes, inside client components. But for most server-rendered data you won't need it — a direct await is simpler.

How do I share state across server and client components?

You don't share React state across that boundary. Pass data down as props from server to client, and lift interactive state into the client subtree.

The verdict

If you're starting a new project in 2026, use the App Router. The server-first model removes a whole category of boilerplate, and the caching controls give you fine-grained performance without ejecting to custom infrastructure. Keep client components small, lean on revalidate for content, and you'll ship faster with less JavaScript.

Written by Jordan Alvarez
Founder & Lead Engineer

Jordan is a full-stack engineer with over a decade of experience shipping production web applications for US startups and enterprises. He specializes in the React/Next.js ecosystem, serverless architectures on Vercel, and Postgres-backed products on Supabase. He founded this site to cut through marketing noise and test developer tools against real workflows.

More from Jordan Alvarez

Related Reading