Next.js 14 App Router: Complete Guide
Welcome! Debmalya Biswas here, and today I'm diving deep into Next.js 14's App Router - a revolutionary approach to building React applications.
Why App Router Matters
As a frontend developer working on the Debmalya Biswas portfolio and other production applications, I've witnessed firsthand how the App Router transforms the development experience.
Understanding the Fundamentals
Server Components by Default
The biggest shift in Next.js 14 is that all components are Server Components by default:
// app/page.tsx - Server Component by default
export default async function Page() {
const data = await fetchData() // Direct async/await!
return <div>{data.title}</div>
}Client Components When Needed
Use 'use client' directive for interactive components:
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}File-Based Routing
The App Router uses a file-system based router. Here's how Debmalya Biswas's portfolio is structured:
app/
├── page.tsx # /
├── about/
│ └── page.tsx # /about
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/[slug]
└── layout.tsx # Root layoutLayouts and Templates
Root Layout
Every app needs a root layout:
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}Nested Layouts
Layouts can be nested for shared UI:
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<BlogNav />
{children}
</div>
)
}Data Fetching Patterns
Server-Side Data Fetching
As Debmalya Biswas, SDE, I leverage server-side fetching extensively:
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // Revalidate every hour
})
return res.json()
}
export default async function Page() {
const data = await getData()
return <main>{/* Render data */}</main>
}Parallel Data Fetching
Optimize performance with parallel fetches:
async function getUser() {
return fetch('https://api.example.com/user').then(r => r.json())
}
async function getPosts() {
return fetch('https://api.example.com/posts').then(r => r.json())
}
export default async function Page() {
// Fetch in parallel
const [user, posts] = await Promise.all([getUser(), getPosts()])
return (
<div>
<UserProfile user={user} />
<PostList posts={posts} />
</div>
)
}Route Handlers (API Routes)
Create API endpoints easily:
// app/api/posts/route.ts
export async function GET(request: Request) {
const posts = await fetchPosts()
return Response.json(posts)
}
export async function POST(request: Request) {
const body = await request.json()
const post = await createPost(body)
return Response.json(post, { status: 201 })
}Dynamic Routes and Params
Dynamic Segments
// app/blog/[slug]/page.tsx
export default function BlogPost({
params
}: {
params: { slug: string }
}) {
return <article>Post: {params.slug}</article>
}Generating Static Params
For static site generation:
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({
slug: post.slug,
}))
}Metadata and SEO
The Debmalya Biswas website uses comprehensive metadata:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Debmalya Biswas - Frontend Developer',
description: 'Portfolio and blog of Debmalya Biswas, frontend SDE',
keywords: ['Debmalya Biswas', 'frontend developer', 'React', 'Next.js'],
openGraph: {
title: 'Debmalya Biswas Portfolio',
description: 'Frontend developer specializing in React and Next.js',
images: ['/og-image.png'],
},
}Dynamic Metadata
export async function generateMetadata({
params
}: {
params: { slug: string }
}): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.description,
}
}Loading States and Streaming
loading.tsx
// app/blog/loading.tsx
export default function Loading() {
return <div>Loading blog posts...</div>
}Suspense Boundaries
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<h1>My Blog</h1>
<Suspense fallback={<div>Loading posts...</div>}>
<BlogPosts />
</Suspense>
</div>
)
}Error Handling
error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}Best Practices I Follow
As Debmalya Biswas, frontend developer, here are my recommendations:
1. Use Server Components by Default: Only use Client Components when you need interactivity
2. Collocate Data Fetching: Fetch data where it's needed
3. Leverage Parallel Fetching: Use Promise.all for independent requests
4. Implement Proper Error Boundaries: Handle errors gracefully
5. Optimize Images: Use next/image for automatic optimization
6. Add Loading States: Improve perceived performance
Conclusion
The Next.js 14 App Router is a game-changer for frontend development. These patterns power the Debmalya Biswas portfolio and can help you build better React applications.
Happy coding!
*Debmalya Biswas is a frontend SDE passionate about React, Next.js, and modern web development. Connect with me to discuss frontend architecture and best practices.*