Przejdź do treści głównej

Complete Next.js 15 Guide

Master the most powerful React framework for production applications. From fundamentals to advanced patterns, learn everything you need to build blazing-fast, SEO-optimized web applications with Next.js 15.

Author: Michał Wojciechowski··16 min read
Modern web development workspace with code on screen

What is Next.js and Why Should You Use It?

Next.js is a React framework that gives you the building blocks to create fast, production-ready web applications. Built by Vercel, it extends React with powerful features like server-side rendering, static site generation, and API routes – all with zero configuration.

While React is a library for building user interfaces, Next.js is a complete framework that handles routing, data fetching, rendering strategies, and deployment optimization out of the box. Think of it as React with superpowers for production applications. If you're deciding on a language, check out our TypeScript vs JavaScript guide.

Why developers choose Next.js:

  • Best-in-class performance – automatic code splitting, image optimization, and font optimization
  • SEO-friendly by default – server rendering ensures search engines can crawl your content
  • Developer experience – fast refresh, TypeScript support, and intuitive file-based routing
  • Full-stack capabilities – API routes and Server Actions let you build backend functionality
  • Production-ready – used by Netflix, TikTok, Twitch, Hulu, and thousands of companies

Next.js 15 New Features - What's Changed

Next.js 15, released in October 2024, brings groundbreaking improvements focused on performance, developer experience, and new rendering patterns. The App Router is now stable and production-ready, replacing the Pages Router with a more powerful paradigm.

React Server Components (RSC)

The biggest paradigm shift in React's history. Components render on the server by default, sending only HTML to the client – drastically reducing JavaScript bundle sizes and improving performance.

// Server Component (default)
async function BlogPost({ id }: { id: string }) {
  // Fetch data directly in component
  const post = await db.post.findUnique({ where: { id } });

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Benefits: Zero client JavaScript for static content, direct database access, automatic code splitting, and better security (API keys stay on server).

App Router with Server Actions

The new routing system uses the app/ directory with nested layouts and Server Actions for seamless data mutations without API routes.

// app/actions.ts - Server Action
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;

  await db.post.create({
    data: { title, content: formData.get('content') as string }
  });

  revalidatePath('/blog');
  redirect(`/blog/${title}`);
}

// app/blog/new/page.tsx - Client Component
'use client'

import { createPost } from '@/app/actions';

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Publish</button>
    </form>
  );
}

No need for API routes, fetch calls, or state management – forms work progressively even without JavaScript.

Partial Prerendering (Experimental)

Combines static and dynamic rendering on the same page – static shell loads instantly while dynamic parts stream in. Best of both worlds.

// next.config.ts
export default {
  experimental: {
    ppr: true // Partial Prerendering
  }
}

// Page with mixed rendering
export default async function Dashboard() {
  return (
    <>
      <StaticHeader /> {/* Prerendered */}
      <Suspense fallback={<Skeleton />}>
        <DynamicUserData /> {/* Streams in */}
      </Suspense>
    </>
  );
}

Turbopack (Beta)

Rust-based bundler that's 700x faster than Webpack and 10x faster than Vite for large applications. Fast refresh in milliseconds.

// package.json
{
  "scripts": {
    "dev": "next dev --turbo",
    "build": "next build"
  }
}

Other Next.js 15 improvements: enhanced fetch() caching, better error handling, improved TypeScript support, and native support for React 19 features like useTransition and useFormStatus.

Performance optimization and code efficiency

Rendering Strategies - SSR, SSG, ISR, and CSR Explained

Next.js gives you four rendering strategies that can be mixed on the same application. Choose the right strategy for each page based on data freshness requirements and traffic patterns.

Static Site Generation (SSG)

Pages are generated at build time and served as static HTML. Fastest possible performance.

// Generates static HTML at build
export default async function Page() {
  const data = await fetch('...');
  return <div>{data}</div>;
}

Best for: Marketing pages, blogs, documentation, product catalogs

Server-Side Rendering (SSR)

Pages render on every request with fresh data. Always up-to-date but slower than static.

// Force dynamic rendering
export const dynamic = 'force-dynamic';

export default async function Page() {
  const data = await fetch('...');
  return <div>{data}</div>;
}

Best for: Dashboards, user profiles, personalized content, real-time data

Incremental Static Regeneration (ISR)

Static pages that regenerate in the background. Fast like static, fresh like dynamic.

// Revalidate every 60 seconds
export const revalidate = 60;

export default async function Page() {
  const data = await fetch('...');
  return <div>{data}</div>;
}

Best for: E-commerce products, news sites, content platforms, high-traffic pages

Client-Side Rendering (CSR)

Renders in the browser with JavaScript. Use Client Components for interactive UI.

'use client'

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>
    {count}
  </button>;
}

Best for: Interactive components, client state, browser APIs, event handlers

Pro Tip: Composition Strategy

The best Next.js apps use all four strategies on different pages. Static homepage for speed, SSR dashboard for freshness, ISR product pages for balance, and Client Components for interactivity. Server Components by default, Client Components only when needed.

Routing and Navigation in Next.js 15

Next.js uses file-based routing – the file structure in your app/ directory automatically becomes your URL structure. No router configuration needed.

File System Routing Examples:

app/
├── page.tsx              → /
├── about/
│   └── page.tsx          → /about
├── blog/
│   ├── page.tsx          → /blog
│   └── [slug]/
│       └── page.tsx      → /blog/:slug (dynamic)
├── dashboard/
│   ├── layout.tsx        → Shared layout
│   ├── page.tsx          → /dashboard
│   └── settings/
│       └── page.tsx      → /dashboard/settings
└── api/
    └── users/
        └── route.ts      → /api/users (API route)

Dynamic Routes

Use square brackets [param] for dynamic segments:

// app/blog/[slug]/page.tsx
type Props = {
  params: Promise<{ slug: string }>;
};

export default async function BlogPost({ params }: Props) {
  const { slug } = await params;
  const post = await getPost(slug);

  return <article>{post.content}</article>;
}

// Generate static params at build time
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

Layouts and Templates

Layouts persist across route changes and don't re-render. Perfect for navigation, sidebars, and shared UI:

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="flex">
      <Sidebar /> {/* Shared sidebar */}
      <main className="flex-1">
        {children} {/* Page content */}
      </main>
    </div>
  );
}

Navigation with Link and useRouter

Client-side navigation with automatic prefetching for instant page transitions:

import Link from 'next/link';
import { useRouter } from 'next/navigation';

// Declarative navigation
<Link href="/blog/nextjs-guide">Read Guide</Link>

// Programmatic navigation
function NavigateButton() {
  const router = useRouter();

  return (
    <button onClick={() => router.push('/dashboard')}>
      Go to Dashboard
    </button>
  );
}

Data Fetching Patterns in Next.js 15

Next.js 15 revolutionizes data fetching with async Server Components, automatic request deduplication, and built-in caching. No need for getServerSideProps or getStaticProps anymore.

Server Component Data Fetching

Fetch data directly in components with async/await. Automatic caching and deduplication:

// Server Component - async by default
async function UserProfile({ userId }: { userId: string }) {
  // Fetches are automatically cached
  const user = await fetch(`https://api.example.com/users/${userId}`);

  // Or fetch from database directly
  const posts = await db.post.findMany({
    where: { authorId: userId },
    orderBy: { createdAt: 'desc' },
  });

  return (
    <div>
      <h1>{user.name}</h1>
      <PostList posts={posts} />
    </div>
  );
}

Parallel and Sequential Data Fetching

Optimize performance by fetching data in parallel when possible:

// Parallel fetching - starts at the same time
async function Dashboard() {
  const userPromise = getUser();
  const postsPromise = getPosts();
  const statsPromise = getStats();

  // Wait for all promises
  const [user, posts, stats] = await Promise.all([
    userPromise,
    postsPromise,
    statsPromise,
  ]);

  return <DashboardUI user={user} posts={posts} stats={stats} />;
}

// Sequential fetching - when data depends on previous result
async function UserPosts({ userId }: { userId: string }) {
  const user = await getUser(userId);
  // Fetch posts only after getting user
  const posts = await getPosts(user.region);

  return <PostsList posts={posts} />;
}

Streaming with Suspense

Show instant feedback while data loads – no more loading spinners blocking the entire page:

import { Suspense } from 'react';

export default function Page() {
  return (
    <>
      <Header /> {/* Shows immediately */}

      <Suspense fallback={<Skeleton />}>
        <SlowComponent /> {/* Streams in when ready */}
      </Suspense>

      <Suspense fallback={<Skeleton />}>
        <AnotherSlowComponent /> {/* Also streams independently */}
      </Suspense>

      <Footer /> {/* Shows immediately */}
    </>
  );
}

Revalidation Strategies

Control cache freshness with time-based or on-demand revalidation:

// Time-based revalidation
export const revalidate = 3600; // Revalidate every hour

// On-demand revalidation
import { revalidatePath, revalidateTag } from 'next/cache';

export async function updatePost(id: string) {
  await db.post.update({ where: { id }, data: { ... } });

  // Revalidate specific path
  revalidatePath('/blog');

  // Or revalidate by cache tag
  revalidateTag('posts');
}

// Tagged fetch for granular control
const posts = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'], revalidate: 60 }
});

API Routes and Server Actions - Building Full-Stack Apps

Next.js lets you build full-stack applications with backend functionality right in your React project. Two approaches: API Routes for REST/GraphQL endpoints and Server Actions for form submissions and mutations. If you need real-time communication, check out our article on SignalR and real-time apps.

API Routes

Create REST endpoints in app/api/ directory:

// app/api/posts/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const posts = await db.post.findMany();
  return NextResponse.json(posts);
}

export async function POST(request: Request) {
  const body = await request.json();
  const post = await db.post.create({ data: body });
  return NextResponse.json(post, { status: 201 });
}

// Dynamic route: app/api/posts/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const post = await db.post.findUnique({ where: { id } });

  if (!post) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }

  return NextResponse.json(post);
}

Server Actions - The Modern Way

Server Actions eliminate the need for API routes for most mutations. Works with forms, progressive enhancement, and optimistic updates:

// app/actions.ts
'use server'

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
  // Validate input
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  if (!title || !content) {
    return { error: 'Title and content are required' };
  }

  // Create post
  const post = await db.post.create({
    data: { title, content, authorId: '...' }
  });

  // Revalidate cache
  revalidatePath('/blog');

  // Redirect to new post
  redirect(`/blog/${post.slug}`);
}

// Usage in Client Component
'use client'

import { useFormStatus } from 'react-dom';
import { createPost } from './actions';

export function CreatePostForm() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <SubmitButton />
    </form>
  );
}

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Creating...' : 'Create Post'}
    </button>
  );
}

Optimistic Updates

Update UI instantly before server confirms – better perceived performance:

'use client'

import { useOptimistic } from 'react';
import { addTodo } from './actions';

export function TodoList({ todos }: { todos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (state, newTodo: string) => [
      ...state,
      { id: Date.now(), text: newTodo, pending: true }
    ]
  );

  async function formAction(formData: FormData) {
    const text = formData.get('todo') as string;
    addOptimisticTodo(text); // Update UI immediately
    await addTodo(text); // Save to server
  }

  return (
    <>
      <form action={formAction}>
        <input name="todo" />
        <button type="submit">Add</button>
      </form>

      <ul>
        {optimisticTodos.map(todo => (
          <li key={todo.id} className={todo.pending ? 'opacity-50' : ''}>
            {todo.text}
          </li>
        ))}
      </ul>
    </>
  );
}

Performance Optimization - Core Web Vitals and Beyond

Next.js optimizes for Core Web Vitals out of the box – the metrics Google uses for search ranking. But you can do even more to squeeze every millisecond of performance.

Built-in Optimizations:

  • Automatic Code Splitting – only load JavaScript needed for current page
  • Image Optimization – automatic WebP/AVIF conversion, lazy loading, responsive images
  • Font Optimization – automatic font subsetting and inlining
  • Route Prefetching – preload pages in viewport before user clicks
  • Script Optimization – control loading strategy for third-party scripts

Image Component

Use next/image for automatic optimization:

import Image from 'next/image';

// Automatic optimization and lazy loading
<Image
  src="/hero.jpg"
  alt="Hero image"
  width={1200}
  height={600}
  priority // Load above-the-fold images immediately
  placeholder="blur" // Show blur placeholder while loading
/>

// Responsive images
<Image
  src="/product.jpg"
  alt="Product"
  fill // Fill parent container
  sizes="(max-width: 768px) 100vw, 50vw"
  className="object-cover"
/>

Dynamic Imports for Code Splitting

Load heavy components only when needed:

import dynamic from 'next/dynamic';

// Load component lazily with loading state
const HeavyChart = dynamic(() => import('./HeavyChart'), {
  loading: () => <ChartSkeleton />,
  ssr: false // Client-side only
});

// Load third-party library only when needed
const DynamicModal = dynamic(() => import('./Modal'));

export default function Dashboard() {
  const [showModal, setShowModal] = useState(false);

  return (
    <>
      <button onClick={() => setShowModal(true)}>
        Open Modal
      </button>

      {/* Modal code only loaded when opened */}
      {showModal && <DynamicModal onClose={() => setShowModal(false)} />}
    </>
  );
}

Metadata and SEO

Type-safe metadata with automatic OpenGraph and Twitter cards:

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.coverImage }],
      type: 'article',
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  };
}
Cloud infrastructure and deployment

Deployment - Vercel, AWS, Azure, and Docker

Next.js deploys anywhere Node.js runs. Vercel (creators of Next.js) offers the best experience, but you can deploy to any cloud platform. For CI/CD automation, check out our GitHub Actions vs Azure DevOps guide.

Vercel (Recommended)

Zero-config deployment with automatic CI/CD, edge network, and analytics:

# Install Vercel CLI
npm i -g vercel

# Deploy to production
vercel --prod

# Or connect GitHub repo for automatic deploys

Features: Edge Functions, Analytics, Image Optimization CDN, Preview Deployments, Team Collaboration

Docker + Cloud Platforms

Deploy to AWS, Azure, GCP, or any container orchestrator:

# Dockerfile
FROM node:20-alpine AS base

# Install dependencies
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Build application
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000
CMD ["node", "server.js"]

Static Export

Export as static HTML for traditional hosting (CDN, S3, etc.):

// next.config.ts
export default {
  output: 'export',
  images: {
    unoptimized: true, // Required for static export
  },
}

// Build and export
npm run build
# Output in 'out' directory

# Deploy to any static host
aws s3 sync out/ s3://my-bucket --acl public-read

Self-Hosted with PM2

Run on your own server with process management:

# Install PM2
npm install -g pm2

# Build application
npm run build

# Start with PM2
pm2 start npm --name "nextjs-app" -- start

# Or use custom server
pm2 start npm --name "nextjs-app" -- run start:prod

# Save PM2 configuration
pm2 save
pm2 startup

Production Checklist

  • Enable environment variables for API keys and database URLs
  • Configure CDN for static assets and images
  • Set up monitoring and error tracking (Sentry, Datadog)
  • Enable HTTPS and security headers
  • Configure caching headers and revalidation
  • Test Core Web Vitals with Lighthouse and WebPageTest

Next.js vs Plain React - When to Choose Each

Next.js adds complexity and build steps to React. It's not always the right choice. Here's when to use each:

Choose Next.js when:

  • SEO is critical (marketing sites, blogs, e-commerce)
  • Performance matters (Core Web Vitals, page speed)
  • You need server-side rendering or static generation
  • Building full-stack app with API routes
  • Want zero-config setup (routing, optimization, etc.)
  • Need built-in image and font optimization
  • Multi-page application with routing

Choose Plain React when:

  • Building admin dashboards or internal tools (no SEO needed)
  • Single-page application (SPA) behind authentication
  • Component library or design system
  • Embedded widget or small interactive component
  • Learning React (keep it simple first)
  • Need maximum control over build configuration
  • Pure client-side app with separate backend API

The Hybrid Approach

Many companies use both – Next.js for public-facing marketing site and blog (SEO-critical), plain React SPA for the application dashboard (behind authentication). This gives best-in-class SEO where it matters while keeping the app simple where it doesn't.

Real-World Use Cases and Examples

Next.js powers some of the world's largest websites and applications. Here are proven patterns for common use cases:

E-Commerce Platform

Stack: Next.js + Stripe + Prisma + PostgreSQL

  • Product pages: ISR with 60-second revalidation for inventory updates
  • Homepage: SSG for maximum performance and SEO
  • Search: Client-side with Algolia for instant results
  • Checkout: Server Actions with Stripe integration
  • Admin: SSR dashboard with real-time order updates

Examples: Nike, Walmart, Doordash

SaaS Application

Stack: Next.js + tRPC + Clerk + Vercel + Planetscale

  • Marketing site: SSG for SEO and fast loading
  • Documentation: SSG with MDX for rich content
  • App dashboard: SSR with user-specific data
  • Real-time features: Server Components + WebSockets
  • API: tRPC for type-safe API calls

Examples: Vercel, Linear, Cal.com

Content Platform / Blog

Stack: Next.js + MDX + Contentful + Vercel

  • Articles: SSG with ISR for new content updates
  • Homepage: SSG with latest posts
  • Search: Static index with Fuse.js for client-side search
  • Comments: Server Actions for form submissions
  • Analytics: Edge Functions for view tracking

Examples: TechCrunch, IGN, HashNode

Developer Portfolio

Stack: Next.js + Tailwind + MDX + Vercel (simple and fast)

  • All pages: SSG for instant loading and perfect Lighthouse scores
  • Blog posts: MDX for rich, interactive content
  • Contact form: Server Action with email integration
  • Projects: Static content with dynamic GitHub stats

Perfect starter template for developers

Learning Resources and Next Steps

Ready to dive deeper? Here are the best resources to master Next.js:

Official Next.js Documentation

The official docs are exceptional – comprehensive, well-organized, and always up-to-date. Start here.

Read the docs

Next.js Learn Tutorial

Free interactive course from the Next.js team: Learn Next.js. Build a full-stack app from scratch.

Start learning

Vercel Blog

Vercel's blog announces new features, best practices, and real-world case studies.

Read the blog

React Documentation

Since Next.js is React, understanding React deeply is essential: react.dev

Learn React

Ready to Build with Next.js?

Next.js 15 represents the cutting edge of web development – combining the best of server rendering, static generation, and client-side interactivity. It's battle-tested by millions of websites, from small startups to global enterprises.

The framework handles the hard parts – routing, optimization, rendering strategies, deployment – so you can focus on building great products. Whether you're building a blog, e-commerce site, SaaS platform, or enterprise application, Next.js provides the foundation for success. Check out our Kinetiq case study to see Next.js in action.

Need Help with Your Next.js Project?

We specialize in building production-ready Next.js applications – from MVP to enterprise scale. Experts in Next.js 15, React Server Components, TypeScript, and cloud deployment (Vercel, AWS, Azure). Let's build something amazing together.

Related Articles

Complete Next.js 15 Guide - Modern React Framework Tutorial | Wojciechowski.app