January 24, 2024
15 min read
Leo de Jesús
DevelopmentFeatured

Next.js 14 App Router: Complete Guide

Master the new App Router in Next.js 14 with this comprehensive guide covering routing, layouts, and advanced patterns.

Next.jsReactFull-StackPerformance

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}

Footer



)
}


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...
}>



)
}


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.

¿Te gustó este artículo?

Conectemos y sigamos la conversación en mis redes sociales