Complete Guide to Next.js App Router: Layouts, Loading, and Error Handling
Next.js 14's App Router represents a fundamental shift in how React applications are built. Server Components, nested layouts, streaming, and server actions provide a powerful mental model for building modern web applications. But the App Router also introduces new patterns that take time to learn.
This guide covers everything you need to know about the Next.js App Router — layouts, loading states, error boundaries, parallel routes, intercepting routes, and server actions — with production-ready code examples.
The App Router Mental Model
The App Router is built on file-system routing where folders define routes and special files define the UI for each segment:
- `layout.tsx` — Shared UI that persists across routes
- `page.tsx` — The unique UI for a route segment
- `loading.tsx` — Loading UI shown during data fetching
- `error.tsx` — Error UI for unexpected errors
- `not-found.tsx` — 404 UI for missing resources
- `template.tsx` — Like layout but re-renders on navigation
Nested Layouts
Layouts are the most powerful feature of the App Router. They persist across navigations and can be deeply nested:
// app/layout.tsx — Root layout
import { Inter } from 'next/font/google'
import { Providers } from './providers'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<Providers>
<nav className="border-b px-4 py-3">
<span className="text-xl font-bold">My App</span>
</nav>
{children}
</Providers>
</body>
</html>
)
}// app/dashboard/layout.tsx — Dashboard layout with sidebar
import { Sidebar } from './Sidebar'
import { Header } from './Header'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex h-screen">
<Sidebar />
<div className="flex flex-1 flex-col">
<Header />
<main className="flex-1 overflow-y-auto p-6">
{children}
</main>
</div>
</div>
)
}Loading States with Streaming
The App Router supports streaming with Suspense boundaries:
// app/dashboard/loading.tsx
export default function DashboardLoading() {
return (
<div className="animate-pulse space-y-6 p-6">
<div className="h-8 w-48 rounded bg-gray-200" />
<div className="grid gap-6 sm:grid-cols-3">
{[1, 2, 3].map(i => (
<div key={i} className="h-32 rounded-xl bg-gray-200" />
))}
</div>
<div className="h-64 rounded-xl bg-gray-200" />
</div>
)
}Streaming Specific Components
For granular streaming, wrap individual components in Suspense:
// app/dashboard/page.tsx
import { Suspense } from 'react'
import { RevenueChart } from './RevenueChart'
import { RecentOrders } from './RecentOrders'
import { UserTable } from './UserTable'
export default function DashboardPage() {
return (
<div className="space-y-6">
<h1 className="text-2xl font-bold">Dashboard</h1>
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<div className="grid gap-6 lg:grid-cols-2">
<Suspense fallback={<OrderSkeleton />}>
<RecentOrders />
</Suspense>
<Suspense fallback={<UserSkeleton />}>
<UserTable />
</Suspense>
</div>
</div>
)
}Error Boundaries
Error boundaries catch errors and prevent the entire app from crashing:
'use client'
import { useEffect } from 'react'
import { AlertTriangle, RefreshCw } from 'lucide-react'
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
console.error(error)
}, [error])
return (
<div className="flex min-h-[400px] flex-col items-center justify-center">
<AlertTriangle className="h-12 w-12 text-red-400" />
<h2 className="mt-4 text-xl font-semibold">Something went wrong</h2>
<p className="mt-2 text-gray-500">{error.message}</p>
<button
onClick={reset}
className="mt-4 inline-flex items-center gap-2 rounded-xl bg-brand-600 px-6 py-2.5 text-sm font-semibold text-white"
>
<RefreshCw className="h-4 w-4" />
Try Again
</button>
</div>
)
}Parallel Routes
Parallel routes let you render multiple pages in the same layout simultaneously:
// app/dashboard/@analytics/page.tsx
export default function Analytics() {
return <div>Analytics Content</div>
}
// app/dashboard/@orders/page.tsx
export default function Orders() {
return <div>Orders Content</div>
}
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
orders,
}: {
children: React.ReactNode
analytics: React.ReactNode
orders: React.ReactNode
}) {
return (
<div className="grid gap-6 lg:grid-cols-2">
{analytics}
{orders}
</div>
)
}Server Actions
Server Actions let you mutate data directly from the client without API routes:
// app/actions/update-profile.ts
'use server'
import { prisma } from '@/lib/prisma'
import { auth } from '@/lib/auth'
import { revalidatePath } from 'next/cache'
import { z } from 'zod'
const schema = z.object({
name: z.string().min(2).max(50),
bio: z.string().max(500).optional(),
})
export async function updateProfile(formData: FormData) {
const session = await auth()
if (!session?.user) throw new Error('Unauthorized')
const data = schema.parse({
name: formData.get('name'),
bio: formData.get('bio'),
})
await prisma.user.update({
where: { id: session.user.id },
data,
})
revalidatePath('/settings')
}The Bottom Line
The Next.js App Router provides a powerful, convention-based approach to building web applications. Nested layouts persist state across navigations, streaming improves perceived performance, error boundaries prevent crashes, and server actions simplify data mutations. The SaaS Starter Kit demonstrates all of these patterns in a production-ready application.
Related Template
Try this production-ready starter kit to build your project faster.
Launch Your SaaS in Days
Production-ready boilerplate with auth, billing, team management, and a beautiful dashboard.
Auth & Users
Email, Google, GitHub login
Team Management
Invite & manage members
Dashboard
Analytics & metrics
Stripe Billing
Subscriptions & invoices
Simple, Transparent Pricing
Starter
$29/mo
- 3 projects
- 5 team members
- Basic analytics
- Email support
Pro
$79/mo
- Unlimited projects
- 20 team members
- Advanced analytics
- Priority support
- API access
Enterprise
$199/mo
- Everything in Pro
- Unlimited members
- SSO & SAML
- Dedicated support
- Custom integrations
Frequently Asked Questions
SaaS Starter Kit
Launch your SaaS in days, not months
Ready to Build?
Get started with our production-ready starter kits and ship your project faster.
Browse Starter Kits