Next.js 14 App Router: Complete Guide
Next.js 14 introduced significant improvements to the App Router, making it more stable and feature-rich. This comprehensive guide will help you master the new routing system and build powerful applications.
Understanding the App Router
The App Router is built on React Server Components and provides a more intuitive file-based routing system. Unlike the Pages Router, it offers better performance and more flexible layouts.
Key Differences from Pages Router
- Server Components by default: Better performance and SEO
- Nested layouts: Share layouts between routes
- Loading and error boundaries: Better UX with loading states
- Streaming: Progressive page loading
- Parallel routes: Render multiple pages simultaneously
File Conventions
Basic Routing
app/
├── page.tsx Home page (/)
├── about/
│ └── page.tsx About page (/about)
├── blog/
│ ├── page.tsx Blog listing (/blog)
│ └── [slug]/
│ └── page.tsx Dynamic blog post (/blog/[slug])
└── layout.tsx Root layout
Special Files
app/
├── layout.tsx Root layout
├── page.tsx Home page
├── loading.tsx Loading UI
├── error.tsx Error UI
├── not-found.tsx 404 page
├── global.css Global styles
└── favicon.ico Favicon
Layouts and Nested Layouts
Root Layout
Every app must have a root layout:
tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'My App',
description: 'A Next.js 14 application',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Nested Layouts
Create layouts that apply to specific route segments:
tsx
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Server and Client Components
Server Components (Default)
Server Components run on the server and are rendered to HTML:
tsx
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'
export default async function PostsPage() {
const posts = await getPosts() // Runs on server
return (
All Posts
{posts.map(post => (
{post.title}
{post.excerpt}
))}
)
}
Client Components
Use "use client" directive for interactive components:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
Data Fetching Patterns
Server-Side Data Fetching
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
The App Router is built on React Server Components and provides a more intuitive file-based routing system. Unlike the Pages Router, it offers better performance and more flexible layouts.
Key Differences from Pages Router
- Server Components by default: Better performance and SEO
- Nested layouts: Share layouts between routes
- Loading and error boundaries: Better UX with loading states
- Streaming: Progressive page loading
- Parallel routes: Render multiple pages simultaneously
File Conventions
Basic Routing
app/
├── page.tsx Home page (/)
├── about/
│ └── page.tsx About page (/about)
├── blog/
│ ├── page.tsx Blog listing (/blog)
│ └── [slug]/
│ └── page.tsx Dynamic blog post (/blog/[slug])
└── layout.tsx Root layout
Special Files
app/
├── layout.tsx Root layout
├── page.tsx Home page
├── loading.tsx Loading UI
├── error.tsx Error UI
├── not-found.tsx 404 page
├── global.css Global styles
└── favicon.ico Favicon
Layouts and Nested Layouts
Root Layout
Every app must have a root layout:
tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'My App',
description: 'A Next.js 14 application',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Nested Layouts
Create layouts that apply to specific route segments:
tsx
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Server and Client Components
Server Components (Default)
Server Components run on the server and are rendered to HTML:
tsx
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'
export default async function PostsPage() {
const posts = await getPosts() // Runs on server
return (
All Posts
{posts.map(post => (
{post.title}
{post.excerpt}
))}
)
}
Client Components
Use "use client" directive for interactive components:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
Data Fetching Patterns
Server-Side Data Fetching
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
Basic Routing
app/
├── page.tsx Home page (/)
├── about/
│ └── page.tsx About page (/about)
├── blog/
│ ├── page.tsx Blog listing (/blog)
│ └── [slug]/
│ └── page.tsx Dynamic blog post (/blog/[slug])
└── layout.tsx Root layout
Special Files
app/
├── layout.tsx Root layout
├── page.tsx Home page
├── loading.tsx Loading UI
├── error.tsx Error UI
├── not-found.tsx 404 page
├── global.css Global styles
└── favicon.ico Favicon
Layouts and Nested Layouts
Root Layout
Every app must have a root layout:
tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'My App',
description: 'A Next.js 14 application',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Nested Layouts
Create layouts that apply to specific route segments:
tsx
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Server and Client Components
Server Components (Default)
Server Components run on the server and are rendered to HTML:
tsx
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'
export default async function PostsPage() {
const posts = await getPosts() // Runs on server
return (
All Posts
{posts.map(post => (
{post.title}
{post.excerpt}
))}
)
}
Client Components
Use "use client" directive for interactive components:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
Data Fetching Patterns
Server-Side Data Fetching
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
app/
├── page.tsx Home page (/)
├── about/
│ └── page.tsx About page (/about)
├── blog/
│ ├── page.tsx Blog listing (/blog)
│ └── [slug]/
│ └── page.tsx Dynamic blog post (/blog/[slug])
└── layout.tsx Root layout
app/
├── layout.tsx Root layout
├── page.tsx Home page
├── loading.tsx Loading UI
├── error.tsx Error UI
├── not-found.tsx 404 page
├── global.css Global styles
└── favicon.ico Favicon
Layouts and Nested Layouts
Root Layout
Every app must have a root layout:
tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'My App',
description: 'A Next.js 14 application',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Nested Layouts
Create layouts that apply to specific route segments:
tsx
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Server and Client Components
Server Components (Default)
Server Components run on the server and are rendered to HTML:
tsx
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'
export default async function PostsPage() {
const posts = await getPosts() // Runs on server
return (
All Posts
{posts.map(post => (
{post.title}
{post.excerpt}
))}
)
}
Client Components
Use "use client" directive for interactive components:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
Data Fetching Patterns
Server-Side Data Fetching
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
Every app must have a root layout:
tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'My App',
description: 'A Next.js 14 application',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Nested Layouts
Create layouts that apply to specific route segments:
tsx
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Server and Client Components
Server Components (Default)
Server Components run on the server and are rendered to HTML:
tsx
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'
export default async function PostsPage() {
const posts = await getPosts() // Runs on server
return (
All Posts
{posts.map(post => (
{post.title}
{post.excerpt}
))}
)
}
Client Components
Use "use client" directive for interactive components:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
Data Fetching Patterns
Server-Side Data Fetching
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
{children}
)
}
Server Components (Default)
Server Components run on the server and are rendered to HTML:
tsx
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'
export default async function PostsPage() {
const posts = await getPosts() // Runs on server
return (
All Posts
{posts.map(post => (
{post.title}
{post.excerpt}
))}
)
}
Client Components
Use "use client" directive for interactive components:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
Data Fetching Patterns
Server-Side Data Fetching
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'
export default async function PostsPage() {
const posts = await getPosts() // Runs on server
return (
All Posts
{posts.map(post => (
{post.title}
{post.excerpt}
))}
)
}
Use "use client" directive for interactive components:
tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
Count: {count}
)
}
Data Fetching Patterns
Server-Side Data Fetching
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
Fetch data directly in Server Components:
tsx
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store' // Always fetch fresh data
})
if (!res.ok) {
throw new Error('Failed to fetch users')
}
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
{users.map(user => (
{user.name}
))}
)
}
Caching Strategies
Control how data is cached:
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading and Error Handling
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// Revalidate every hour
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
})
// Always fetch fresh data
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
// Cache indefinitely
const data = await fetch('https://api.example.com/data', {
next: { revalidate: false }
})
Loading UI
Create loading states for route segments:
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Error Boundaries
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// app/dashboard/loading.tsx
export default function Loading() {
return (
Loading dashboard...
)
}
Handle errors gracefully:
tsx
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Something went wrong!
{error.message}
)
}
Not Found Pages
Custom 404 pages:
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Routes
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
Not Found
Could not find requested resource
Return Home
)
}
Dynamic Segments
Create dynamic routes with brackets:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Catch-All Routes
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export default async function PostPage({ params }: PostPageProps) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
{post.title}
)
}
Handle multiple segments:
tsx
// app/docs/[...slug]/page.tsx
interface DocsPageProps {
params: { slug: string[] }
}
export default function DocsPage({ params }: DocsPageProps) {
const slug = params.slug.join('/')
return (
Documentation: {slug}
{/ Render documentation content /}
)
}
Advanced Patterns
Parallel Routes
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
Render multiple pages simultaneously:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
{children}
{analytics}
{team}
)
}
Intercepting Routes
Show modals for certain routes:
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Metadata and SEO
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// app/@modal/(..)photo/[id]/page.tsx
export default function PhotoModal({
params,
}: {
params: { id: string }
}) {
return (
Photo {params.id}
{/ Modal content /}
)
}
Static Metadata
Define metadata at build time:
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Dynamic Metadata
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
tsx
// app/about/page.tsx
export const metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
}
export default function AboutPage() {
return About content
}
Generate metadata based on data:
tsx
// app/posts/[slug]/page.tsx
interface PostPageProps {
params: { slug: string }
}
export async function generateMetadata({ params }: PostPageProps) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.image],
},
}
}
Performance Optimization
Streaming
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
Progressive page loading:
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000))
return Slow content loaded!
}
export default function DashboardPage() {
return (
Dashboard
Loading slow content... }>
)
}
Code Splitting
Automatic code splitting with dynamic imports:
tsx
'use client'
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => Loading...
,
ssr: false
})
export default function Page() {
return (
My Page
)
}
Migration from Pages Router
Step-by-Step Migration
1. Create new app directory
2. Move pages to app directory
3. Update imports and exports
4. Convert to Server Components where possible
5. Update routing logic
6. Test thoroughly
Common Migration Patterns
tsx
// Pages Router
export async function getServerSideProps() {
const data = await fetchData()
return { props: { data } }
}
// App Router
export default async function Page() {
const data = await fetchData()
return {/ render data /}
}
Conclusion
The Next.js 14 App Router represents a significant evolution in React-based web development. By mastering these concepts and patterns, you can build more performant, maintainable, and user-friendly applications.
Key takeaways:
- Leverage Server Components for better performance
- Use nested layouts for consistent UI
- Implement proper loading and error states
- Take advantage of automatic code splitting
- Optimize for SEO with proper metadata
Start experimenting with these patterns in your projects, and you'll quickly see the benefits of the new App Router architecture.
tsx
'use client'
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => Loading...
,
ssr: false
})
export default function Page() {
return (
My Page
)
}
Step-by-Step Migration
1. Create new app directory
2. Move pages to app directory
3. Update imports and exports
4. Convert to Server Components where possible
5. Update routing logic
6. Test thoroughly
Common Migration Patterns
tsx
// Pages Router
export async function getServerSideProps() {
const data = await fetchData()
return { props: { data } }
}
// App Router
export default async function Page() {
const data = await fetchData()
return {/ render data /}
}
Conclusion
The Next.js 14 App Router represents a significant evolution in React-based web development. By mastering these concepts and patterns, you can build more performant, maintainable, and user-friendly applications.
Key takeaways:
- Leverage Server Components for better performance
- Use nested layouts for consistent UI
- Implement proper loading and error states
- Take advantage of automatic code splitting
- Optimize for SEO with proper metadata
Start experimenting with these patterns in your projects, and you'll quickly see the benefits of the new App Router architecture.
tsx
// Pages Router
export async function getServerSideProps() {
const data = await fetchData()
return { props: { data } }
}
// App Router
export default async function Page() {
const data = await fetchData()
return {/ render data /}
}