Next.js - The React Framework for Production
The React Framework for building full-stack web applications with server components, app router, and optimized production deployments.
- Step 1
Overview
Next.js is a React framework for building full-stack web applications. You use React Components to build user interfaces, and Next.js for additional features and optimizations. It automatically configures bundlers, compilers, and provides file-system based routing, server-side rendering (SSR), static site generation (SSG), and React Server Components out of the box.
- Step 2
Technology Stack
Next.js is built with the following technologies:
Language: TypeScript/JavaScript License: MIT Stars: ~124,000 Owner: vercel Repo: https://github.com/vercel/next.js Website: https://nextjs.org Core Dependencies: - React (>=19) - UI library - Node.js (>=20.9) - Runtime environment - Turbopack - Next-generation bundler (default in Next.js 15) - Babel/SWC - JavaScript compiler Key Features: - App Router with file-system based routing - React Server Components (RSC) - Server Actions for mutations - Automatic code splitting - Built-in CSS and Sass support - Image optimization - Font optimization - TypeScript support - API Routes & Route Handlers - Middleware for request interception - Incremental Static Regeneration (ISR) - Static Site Generation (SSG) - Server-Side Rendering (SSR) - Edge Runtime support - Streaming and Suspense - Turbopack for fast HMR - Step 3
Installation
Create a new Next.js app using create-next-app, which sets up everything automatically.
# Interactive setup npx create-next-app@latest # With specific options npx create-next-app@latest my-app --typescript --tailwind --app --turbopack # Using other package managers pnpm create next-app@latest yarn create next-app bun create next-app # Navigate to project cd my-app # Start development server npm run dev - Step 4
Project Structure
Next.js 15 uses the App Router by default. Here's the standard project structure:
my-app/ ├── app/ # App Router (replaces pages/) │ ├── layout.tsx # Root layout (required) │ ├── page.tsx # Homepage route │ ├── globals.css # Global styles │ ├── about/ │ │ └── page.tsx # /about route │ ├── blog/ │ │ ├── page.tsx # /blog route │ │ └── [slug]/ │ │ └── page.tsx # /blog/[slug] dynamic route │ └── api/ │ └── route.ts # API route handler ├── components/ # React components ├── lib/ # Utility functions ├── public/ # Static assets ├── next.config.ts # Next.js configuration ├── tsconfig.json # TypeScript config └── package.json # Dependencies - Step 5
Basic Configuration
Configure Next.js through next.config.ts (or next.config.js).
// next.config.ts import type { NextConfig } from 'next' const nextConfig: NextConfig = { // Enable React Compiler (experimental) experimental: { reactCompiler: true, }, // Image optimization domains images: { remotePatterns: [ { protocol: 'https', hostname: 'example.com', }, ], }, // Redirects async redirects() { return [ { source: '/old-page', destination: '/new-page', permanent: true, }, ] }, } export default nextConfig - Step 6
Creating Pages (App Router)
In the App Router, each folder in app/ represents a route segment. Use page.tsx to make a route publicly accessible.
// app/page.tsx (Homepage at /) export default function HomePage() { return ( <main> <h1>Welcome to Next.js</h1> <p>The React Framework for Production</p> </main> ) } // app/about/page.tsx (Route at /about) export default function AboutPage() { return <h1>About Us</h1> } // app/blog/[slug]/page.tsx (Dynamic route at /blog/[slug]) type Props = { params: Promise<{ slug: string }> } export default async function BlogPost({ params }: Props) { const { slug } = await params return <h1>Blog Post: {slug}</h1> } - Step 7
Layouts
Layouts wrap multiple pages and persist across navigation. The root layout (app/layout.tsx) is required.
// app/layout.tsx (Root layout - required) import './globals.css' export const metadata = { title: 'My Next.js App', description: 'Built with Next.js 15', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <nav> <a href="/">Home</a> <a href="/about">About</a> </nav> {children} </body> </html> ) } // app/blog/layout.tsx (Nested layout for /blog/*) export default function BlogLayout({ children, }: { children: React.ReactNode }) { return ( <div> <aside>Blog Sidebar</aside> <main>{children}</main> </div> ) } - Step 8
React Server Components
By default, all components in the App Router are React Server Components. They run only on the server, can access databases directly, and don't ship JavaScript to the client.
// app/posts/page.tsx (Server Component - default) import { db } from '@/lib/db' // Async server component export default async function PostsPage() { // Fetch data directly on the server const posts = await db.query('SELECT * FROM posts') return ( <div> <h1>Posts</h1> <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ) } // No useEffect, no loading states - data is ready server-side - Step 9
Client Components
Use 'use client' directive for components that need interactivity, hooks, or browser APIs.
// components/Counter.tsx (Client Component) '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> ) } // Use cases for 'use client': // - useState, useEffect, other React hooks // - Event handlers (onClick, onChange, etc.) // - Browser APIs (window, document, localStorage) // - Custom hooks that use the above - Step 10
Data Fetching
Server Components can fetch data using async/await. Next.js extends the native fetch API with automatic caching.
// Server Component data fetching export default async function Page() { // Cached by default (revalidate every hour) const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } }) const json = await data.json() return <div>{json.title}</div> } // Force no cache (always fresh) const dynamicData = await fetch('https://api.example.com/live', { cache: 'no-store' }) // Static (cached indefinitely) const staticData = await fetch('https://api.example.com/static', { cache: 'force-cache' }) // Direct database access (common pattern) import { db } from '@/lib/db' const posts = await db.post.findMany() - Step 11
Route Handlers (API Routes)
Create API endpoints using route.ts files with HTTP method exports.
// app/api/posts/route.ts import { NextRequest, NextResponse } from 'next/server' // GET /api/posts export async function GET(request: NextRequest) { const posts = await db.post.findMany() return NextResponse.json(posts) } // POST /api/posts export async function POST(request: NextRequest) { const body = await request.json() const post = await db.post.create({ data: body }) return NextResponse.json(post, { status: 201 }) } // app/api/posts/[id]/route.ts (Dynamic route) type Params = { params: Promise<{ id: string }> } export async function GET( request: NextRequest, { params }: Params ) { const { id } = await params const post = await db.post.findUnique({ where: { id } }) return NextResponse.json(post) } - Step 12
Server Actions
Server Actions are asynchronous functions that run on the server. Use them for mutations, form submissions, and data updates without creating API routes.
// app/actions.ts 'use server' import { revalidatePath } from 'next/cache' export async function createPost(formData: FormData) { const title = formData.get('title') as string const content = formData.get('content') as string await db.post.create({ data: { title, content } }) // Revalidate the posts page to show new post revalidatePath('/posts') } // app/posts/new/page.tsx import { createPost } from '@/app/actions' export default function NewPost() { return ( <form action={createPost}> <input name="title" placeholder="Title" required /> <textarea name="content" placeholder="Content" required /> <button type="submit">Create Post</button> </form> ) } - Step 13
Navigation and Linking
Use the Link component for client-side navigation with automatic prefetching.
// components/Navigation.tsx import Link from 'next/link' export default function Navigation() { return ( <nav> <Link href="/">Home</Link> <Link href="/about">About</Link> <Link href="/blog">Blog</Link> </nav> ) } // Dynamic links <Link href={`/blog/${post.slug}`}>{post.title}</Link> // Programmatic navigation (Client Component) 'use client' import { useRouter } from 'next/navigation' export default function Button() { const router = useRouter() return ( <button onClick={() => router.push('/about')}> Go to About </button> ) } - Step 14
Image Optimization
The Next.js Image component automatically optimizes images with lazy loading, responsive sizes, and modern formats.
// components/ProductImage.tsx import Image from 'next/image' export default function ProductImage() { return ( <div> {/* Local image (in public/ folder) */} <Image src="/hero.jpg" alt="Hero image" width={800} height={600} priority // Disable lazy loading for above-fold images /> {/* Remote image */} <Image src="https://example.com/image.jpg" alt="Remote image" width={400} height={300} placeholder="blur" // Show blur while loading blurDataURL="data:image/..." // Base64 blur image /> {/* Fill container (requires parent with position: relative) */} <div style={{ position: 'relative', height: '300px' }}> <Image src="/background.jpg" alt="Background" fill style={{ objectFit: 'cover' }} /> </div> </div> ) } - Step 15
Metadata and SEO
Define metadata for each page using the metadata export or generateMetadata function.
// app/page.tsx (Static metadata) import type { Metadata } from 'next' export const metadata: Metadata = { title: 'Home - My App', description: 'Welcome to my Next.js application', openGraph: { title: 'Home - My App', description: 'Welcome to my Next.js application', images: ['/og-image.jpg'], }, } // app/blog/[slug]/page.tsx (Dynamic metadata) type Props = { params: Promise<{ slug: string }> } export async function generateMetadata({ params }: Props): Promise<Metadata> { const { slug } = await params const post = await db.post.findUnique({ where: { slug } }) return { title: post.title, description: post.excerpt, openGraph: { title: post.title, description: post.excerpt, images: [post.coverImage], }, } } - Step 16
Loading States
Create loading.tsx files to show loading UI while page content streams in.
// app/blog/loading.tsx export default function Loading() { return ( <div> <p>Loading blog posts...</p> <div className="spinner" /> </div> ) } // app/blog/page.tsx (This page triggers the loading state) export default async function BlogPage() { // Simulating slow data fetch await new Promise(resolve => setTimeout(resolve, 2000)) const posts = await db.post.findMany() return ( <div> {posts.map(post => ( <article key={post.id}>{post.title}</article> ))} </div> ) } - Step 17
Error Handling
Create error.tsx files to handle errors and provide fallback UI.
// app/blog/error.tsx 'use client' // Error components must be Client Components export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return ( <div> <h2>Something went wrong!</h2> <p>{error.message}</p> <button onClick={() => reset()}> Try again </button> </div> ) } // app/not-found.tsx (Custom 404 page) export default function NotFound() { return ( <div> <h1>404 - Page Not Found</h1> <p>The page you're looking for doesn't exist.</p> </div> ) } - Step 18
Middleware
Middleware runs before requests are completed. Use it for authentication, redirects, and request modifications.
// middleware.ts (in project root) import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // Check authentication const token = request.cookies.get('token') if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)) } // Add custom header const response = NextResponse.next() response.headers.set('x-custom-header', 'value') return response } // Run middleware on specific paths export const config = { matcher: [ '/dashboard/:path*', '/api/:path*', ], } - Step 19
Environment Variables
Use environment variables for configuration. Prefix with NEXT_PUBLIC_ to expose to the browser.
# .env.local (gitignored - for local development) DATABASE_URL=postgresql://localhost:5432/mydb API_SECRET=your-secret-key # Public variables (accessible in browser) NEXT_PUBLIC_API_URL=https://api.example.com NEXT_PUBLIC_SITE_NAME=My App # .env.production (for production builds) DATABASE_URL=postgresql://prod-server:5432/mydb # Usage in code: // Server-side (Server Components, API Routes) const dbUrl = process.env.DATABASE_URL const secret = process.env.API_SECRET // Client-side (any component) const apiUrl = process.env.NEXT_PUBLIC_API_URL - Step 20
Static Site Generation (SSG)
Generate static pages at build time using generateStaticParams for dynamic routes.
// app/blog/[slug]/page.tsx // Generate static pages for all blog posts at build time export async function generateStaticParams() { const posts = await db.post.findMany() return posts.map(post => ({ slug: post.slug, })) } // This page will be pre-rendered at build time export default async function BlogPost({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await db.post.findUnique({ where: { slug } }) return ( <article> <h1>{post.title}</h1> <div>{post.content}</div> </article> ) } - Step 21
Incremental Static Regeneration (ISR)
Revalidate static pages at runtime without rebuilding the entire site.
// app/blog/[slug]/page.tsx // Revalidate this page every 60 seconds export const revalidate = 60 export default async function BlogPost({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await db.post.findUnique({ where: { slug } }) return <article>{post.title}</article> } // On-demand revalidation using Server Actions import { revalidatePath, revalidateTag } from 'next/cache' // Revalidate specific path await revalidatePath('/blog/my-post') // Revalidate by cache tag await revalidateTag('posts') // Use tags with fetch fetch('https://api.example.com/posts', { next: { tags: ['posts'] } }) - Step 22
Deployment to Vercel
Vercel is the recommended platform for Next.js deployment with zero configuration.
# Install Vercel CLI npm i -g vercel # Login to Vercel vercel login # Deploy to preview vercel # Deploy to production vercel --prod # Or connect Git repository: # 1. Push code to GitHub/GitLab/Bitbucket # 2. Import project at vercel.com/new # 3. Auto-deploys on git push # Environment variables in Vercel: # Dashboard → Project → Settings → Environment Variables # - Add DATABASE_URL, API_SECRET, etc. # - Select Environment: Production, Preview, Development - Step 23
Build Commands
Common commands for building and running Next.js applications.
# Development server (with Turbopack) npm run dev # or npm run dev --turbopack # Production build npm run build # Start production server npm run start # Type checking npm run build # TypeScript errors will fail the build # Linting npm run lint # Analyze bundle size npm run build -- --analyze - Step 24
Performance Optimization
Best practices for optimizing Next.js applications.
1. Use Server Components by default - Zero client-side JavaScript 2. Implement proper caching strategies - Use revalidate and cache tags 3. Use next/image for all images - Automatic optimization and lazy loading 4. Use next/font for font optimization - No layout shift 5. Leverage static generation when possible - Pre-render at build time 6. Implement code splitting - Automatic with dynamic imports 7. Enable compression - Automatic in production 8. Use Edge Runtime for fast response times 9. Implement proper loading states - Use loading.tsx and Suspense 10. Monitor Core Web Vitals - Use Vercel Analytics or Google Lighthouse 11. Minimize client-side JavaScript - Only use 'use client' when necessary 12. Use streaming for long data fetches - Suspense boundaries 13. Optimize third-party scripts - Use next/script component 14. Enable Turbopack for faster builds - Default in Next.js 15 - Step 25
Key Features
Next.js capabilities for modern web development:
1. File-System Routing: Automatic routing from folder structure 2. Server Components: React components that run only on server 3. App Router: Modern routing with nested layouts 4. Server Actions: Server-side mutations without API routes 5. Streaming: Progressive rendering with Suspense 6. Image Optimization: Automatic image optimization and lazy loading 7. Font Optimization: Automatic font optimization with next/font 8. Code Splitting: Automatic per-route code splitting 9. TypeScript: Built-in TypeScript support 10. Fast Refresh: Instant feedback on edits 11. Turbopack: Fast bundler with HMR 12. SSR/SSG/ISR: Multiple rendering strategies 13. API Routes: Backend API endpoints 14. Middleware: Request interception and modification 15. Edge Runtime: Deploy to edge for low latency - Step 26
Use Cases
Next.js is ideal for:
1. Marketing Websites: SEO-optimized static/hybrid sites 2. E-commerce Platforms: Fast, SEO-friendly product pages 3. SaaS Applications: Full-stack applications with auth 4. Blogs and Content Sites: Static generation with dynamic updates 5. Dashboards: Server-rendered data visualizations 6. Documentation Sites: Static with search and navigation 7. Landing Pages: Optimized for performance and SEO 8. Web Applications: Full-stack React applications 9. Mobile Web Apps: Progressive Web Apps (PWA) 10. API-First Applications: Backend and frontend in one 11. Multi-tenant Applications: Dynamic routing and data 12. Real-time Applications: With WebSocket support 13. JAMstack Sites: Static generation with API integration 14. Headless CMS Frontends: Integrate with any CMS - Step 27
Migration from Pages Router
Migrating from Next.js Pages Router to App Router:
Step 1: Create app/ directory alongside pages/ Step 2: Move pages/ routes to app/ incrementally Step 3: Convert getServerSideProps → async Server Components Step 4: Convert getStaticProps → async Server Components with revalidate Step 5: Convert getStaticPaths → generateStaticParams Step 6: Convert _app.tsx → layout.tsx Step 7: Convert _document.tsx → layout.tsx (html/body) Step 8: Convert API routes → route.ts with HTTP method exports Step 9: Add 'use client' to interactive components Step 10: Update imports (next/router → next/navigation) Step 11: Test thoroughly, then remove pages/ directory Both routers can coexist during migration. - Step 28
Resources
Additional resources for learning Next.js:
Documentation: https://nextjs.org/docs GitHub: https://github.com/vercel/next.js Learning: https://nextjs.org/learn Examples: https://github.com/vercel/next.js/tree/canary/examples Blog: https://nextjs.org/blog Showcase: https://nextjs.org/showcase Discord: https://nextjs.org/discord Vercel: https://vercel.com React Docs: https://react.dev App Router Docs: https://nextjs.org/docs/app - Step 29
File Locations
Key files and directories in a Next.js project:
app/ - App Router pages and routes app/layout.tsx - Root layout (required) app/page.tsx - Homepage app/loading.tsx - Loading UI app/error.tsx - Error UI app/not-found.tsx - 404 page public/ - Static assets components/ - React components lib/ - Utility functions middleware.ts - Middleware (root level) next.config.ts - Next.js configuration tsconfig.json - TypeScript config .env.local - Local environment variables .next/ - Build output (gitignored) node_modules/ - Dependencies
Feature requests
Sign in to suggest features or vote on existing ones.
No feature requests yet.
Discussion
Sign in to join the discussion.
No comments yet.