
Introduction to Next.js
Next.js has become the go-to framework for building production-ready React applications. Created by Vercel, it solves many of the challenges developers face when building modern web applications, offering features like server-side rendering, static site generation, and automatic code splitting out of the box.
What is Next.js?
Next.js is a React framework that provides a complete solution for building web applications. While React is a library focused on building user interfaces, Next.js extends it with a robust set of features for routing, data fetching, optimization, and deployment.
Think of it this way: React gives you the tools to build components, while Next.js gives you the complete infrastructure to build and deploy full-stack applications.
Why Choose Next.js?
Built-in Routing
Unlike traditional React apps that require libraries like React Router, Next.js uses file-system based routing. Simply create files in your app directory, and they automatically become routes.
app/
├── page.tsx # Home page (/)
├── about/
│ └── page.tsx # About page (/about)
├── blog/
│ ├── page.tsx # Blog listing (/blog)
│ └── [slug]/
│ └── page.tsx # Dynamic blog post (/blog/my-post)
└── dashboard/
└── page.tsx # Dashboard (/dashboard)
Multiple Rendering Strategies
Next.js supports various rendering methods, letting you choose the best approach for each page:
Server-Side Rendering (SSR): Pages are rendered on the server for each request.
Static Site Generation (SSG): Pages are pre-rendered at build time.
Incremental Static Regeneration (ISR): Static pages can be updated after deployment without rebuilding.
Client-Side Rendering (CSR): Traditional React rendering in the browser.
Performance Optimization
Next.js automatically optimizes your application:
- Automatic Code Splitting: Only load JavaScript needed for the current page
- Image Optimization: Built-in
<Image>component with lazy loading and automatic resizing - Font Optimization: Automatic font loading with zero layout shift
- Script Optimization: Control when third-party scripts load
Core Features
1. App Router (Next.js 13+)
The modern App Router uses React Server Components by default:
// app/page.tsx
export default function HomePage() {
return (
<div>
<h1>Welcome to Next.js</h1>
<p>This is a Server Component by default!</p>
</div>
)
}
2. Server Components vs Client Components
Server Components (default):
- Render on the server
- Can directly access databases and APIs
- Don't increase client bundle size
- Great for static content
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
Client Components:
- Add
'use client'directive - Can use hooks and browser APIs
- Interactive elements
// components/Counter.tsx
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
)
}
3. Dynamic Routes
Create dynamic routes with brackets:
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return (
<article>
<h1>Blog Post: {params.slug}</h1>
</article>
)
}
// Generates static pages for these slugs at build time
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json())
return posts.map((post) => ({
slug: post.slug,
}))
}
4. Layouts
Shared layouts wrap multiple pages:
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
<main>{children}</main>
<footer>© 2025 My App</footer>
</body>
</html>
)
}
Nested layouts for specific sections:
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<aside className="w-64 bg-gray-100">
<nav>Sidebar navigation</nav>
</aside>
<div className="flex-1">{children}</div>
</div>
)
}
5. Data Fetching
Next.js makes data fetching simple and powerful:
// Server Component - fetch directly
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
next: { revalidate: 3600 } // Revalidate every hour
})
return res.json()
}
export default async function UserProfile({ params }: { params: { id: string } }) {
const user = await getUser(params.id)
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
)
}
6. API Routes
Build backend APIs within your Next.js app:
// app/api/hello/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({ message: 'Hello from Next.js API!' })
}
export async function POST(request: Request) {
const body = await request.json()
// Process the data
return NextResponse.json({ success: true, data: body })
}
7. Metadata and SEO
Easy SEO optimization with metadata:
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
}
}
Image Optimization
The Next.js Image component automatically optimizes images:
import Image from 'next/image'
export default function ProfilePage() {
return (
<Image
src="/profile.jpg"
alt="Profile picture"
width={500}
height={500}
priority // Load immediately for above-the-fold images
placeholder="blur" // Show blur while loading
/>
)
}
Benefits:
- Automatic responsive images
- Lazy loading by default
- Modern formats (WebP, AVIF)
- Prevents Cumulative Layout Shift (CLS)
Styling Options
Next.js supports multiple styling approaches:
CSS Modules
// components/Button.module.css
.button {
background-color: blue;
padding: 1rem 2rem;
border-radius: 0.5rem;
}
// components/Button.tsx
import styles from './Button.module.css'
export default function Button() {
return <button className={styles.button}>Click me</button>
}
Tailwind CSS
export default function Card() {
return (
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-2xl font-bold mb-4">Card Title</h2>
<p className="text-gray-600">Card content goes here</p>
</div>
)
}
CSS-in-JS
Next.js supports libraries like styled-components and Emotion.
Getting Started
Create a new Next.js app:
npx create-next-app@latest my-app
You'll be prompted to choose:
- TypeScript or JavaScript
- ESLint
- Tailwind CSS
- App Router or Pages Router
- Import aliases
Start the development server:
cd my-app
npm run dev
Your app runs at http://localhost:3000!
Project Structure
A typical Next.js 15 project:
my-app/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ ├── globals.css # Global styles
│ └── api/ # API routes
├── components/ # Reusable components
├── public/ # Static assets
├── package.json
└── next.config.js # Next.js configuration
Real-World Example: Blog
Here's a simple blog structure:
// app/blog/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function BlogPage() {
const posts = await getPosts()
return (
<div className="max-w-4xl mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-8">Blog</h1>
<div className="space-y-8">
{posts.map((post) => (
<article key={post.id} className="border-b pb-8">
<h2 className="text-2xl font-bold mb-2">
<a href={`/blog/${post.slug}`}>{post.title}</a>
</h2>
<p className="text-gray-600 mb-2">{post.date}</p>
<p className="text-gray-800">{post.excerpt}</p>
</article>
))}
</div>
</div>
)
}
// app/blog/[slug]/page.tsx
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
return res.json()
}
export default async function BlogPostPage({ params }) {
const post = await getPost(params.slug)
return (
<article className="max-w-3xl mx-auto px-4 py-8 prose-white dark:prose-invert">
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
<p className="text-gray-600 mb-8">{post.date}</p>
<div className="prose lg:prose-xl">{post.content}</div>
</article>
)
}
Deployment
Deploy to Vercel (the creators of Next.js) with zero configuration:
npm install -g vercel
vercel
Or deploy to other platforms:
- Netlify
- AWS
- Google Cloud
- Self-hosted with Node.js
Best Practices
Use Server Components by default: Only add 'use client' when you need interactivity or browser APIs.
Optimize images: Always use the <Image> component for better performance.
Fetch data where you need it: Server Components can fetch data directly without prop drilling.
Use TypeScript: Next.js has excellent TypeScript support built-in.
Implement proper error handling: Use error.tsx and loading.tsx files for better UX.
Conclusion
Next.js has evolved from a simple React framework into a comprehensive solution for building modern web applications. Whether you're building a static blog, a dynamic e-commerce site, or a complex dashboard, Next.js provides the tools and optimizations you need to succeed.
The combination of Server Components, file-based routing, built-in optimizations, and excellent developer experience makes Next.js an excellent choice for your next React project.
Resources
Happy coding with Next.js! 🚀