Full Stack 42:182025-12-10

Next.js App Router: The Complete Guide for 2026

Everything you need to know about the App Router — server components, client components, layouts, loading states, and data fetching patterns that actually work in production.

Watch on YouTube

Click to open in YouTube

Video Notes & Code

Key takeaways, code snippets, and resources from this video.

Server Components vs Client Components

The fundamental rule: if a component reads from a database, filesystem, or env variable — make it a server component. If it uses useState, useEffect, onClick, or browser APIs — add "use client".

tsx
// Server Component — no "use client" needed
async function ProductList() {
  const products = await db.query('SELECT * FROM products')
  return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}

// Client Component — needs browser interaction
"use client"
import { useState } from 'react'

function SearchBar() {
  const [query, setQuery] = useState('')
  return <input value={query} onChange={e => setQuery(e.target.value)} />
}

Data Fetching Patterns

  • Fetch data in server components directly — no useEffect needed
  • Use React cache() to deduplicate requests across the component tree
  • Use loading.tsx for streaming suspense boundaries
  • Use generateStaticParams for static routes with dynamic segments
tsx
// app/products/[id]/page.tsx
import { cache } from 'react'

const getProduct = cache(async (id: string) => {
  return db.query('SELECT * FROM products WHERE id = $1', [id])
})

export async function generateStaticParams() {
  const products = await db.query('SELECT id FROM products')
  return products.map(p => ({ id: p.id }))
}

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id)
  return <div>{product.name}</div>
}

Found this useful?

Subscribe for weekly content on AI engineering, SaaS building, and full stack development.

Book a Call