Getting Started with Next.js 15: Comprehensive Guide
Getting Started with Next.js 15: Comprehensive Guide
Next.js has become the leading React framework for building modern web applications, and version 15 brings significant improvements in performance and developer experience. In this article, we'll explore the standout features of Next.js 15 and how to start building your application.
Introduction
What is Next.js?
Next.js is an open-source React framework developed by Vercel, providing features like server-side rendering (SSR), static site generation (SSG), and built-in routing. This framework helps developers build high-performance web applications with an excellent development experience.
Why Choose Next.js 15?
Next.js 15 is not just a regular update - it's a major leap forward with many important improvements:
- Better Performance: Significantly faster build times with Turbopack
- Improved Developer Experience: Better tooling and error messages
- React 19 Support: Full support for the latest React features
- Stability: Production-ready with many bug fixes and optimizations
Who Should Read This?
This article is suitable for:
- Developers with basic React knowledge
- Those wanting to learn Next.js from scratch
- Developers wanting to upgrade from older Next.js versions to v15
Key Features of Next.js 15
1. App Router - New Approach to Routing
App Router is a completely new routing architecture introduced in Next.js 13 and perfected in v15. Unlike the old Pages Router, App Router uses folder structure to define routes and supports React Server Components by default.
Advantages of App Router:
- Folder-based Routing: Each folder in the
app
directory represents a route segment - Layouts: Share UI across multiple pages without re-rendering
- Loading States: Built-in support for loading and error states
- Nested Routing: Easily create nested routes with nested layouts
// app/layout.tsx - Root Layout
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<header>
<nav>Navigation Bar</nav>
</header>
<main>{children}</main>
<footer>Footer Content</footer>
</body>
</html>
);
}
2. React Server Components - Performance Optimization
React Server Components (RSC) is a powerful feature that allows rendering components on the server, helping reduce JavaScript bundle size sent to the client and improving page load performance.
Benefits of Server Components:
- Reduced Bundle Size: Server component code is not sent to the client
- Direct Data Access: Access database and API directly without API routes
- Better Performance: Faster initial page load and improved SEO
- Security: Keep sensitive code (API keys, database queries) on the server
// app/blog/page.tsx - Server Component
async function BlogPage() {
// Fetch data directly in component
const posts = await db.query('SELECT * FROM posts ORDER BY date DESC');
return (
<div>
<h1>Blog Posts</h1>
<div className="grid gap-6">
{posts.map((post) => (
<BlogCard key={post.id} post={post} />
))}
</div>
</div>
);
}
export default BlogPage;
3. Server Actions - Simplified Form Handling
Server Actions is a new feature that allows you to call server functions directly from the client without creating separate API endpoints. This significantly simplifies form handling and mutations.
// app/actions.ts
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
// Validate data
if (!title || !content) {
return { error: 'Title and content are required' };
}
// Save to database
await db.posts.create({
data: { title, content, createdAt: new Date() }
});
return { success: true };
}
// app/new-post/page.tsx
import { createPost } from '../actions';
export default function NewPostPage() {
return (
<form action={createPost}>
<input type="text" name="title" placeholder="Post Title" required />
<textarea name="content" placeholder="Post Content" required />
<button type="submit">Create Post</button>
</form>
);
}
4. Turbopack - Faster Builds
Turbopack is a new bundler written in Rust, replacing Webpack in Next.js 15. It delivers significantly faster build speeds, especially for large projects.
Speed Improvements:
- Fast Refresh 700x faster than Webpack
- Cold starts 10x faster
- Optimized incremental builds
Practical Guide
Installing Next.js 15
To get started with Next.js 15, you only need one command:
npx create-next-app@latest my-next-app
During installation, you'll be asked several questions:
✔ Would you like to use TypeScript? Yes
✔ Would you like to use ESLint? Yes
✔ Would you like to use Tailwind CSS? Yes
✔ Would you like to use `src/` directory? No
✔ Would you like to use App Router? Yes
✔ Would you like to customize the default import alias? No
Recommendation: Choose TypeScript and App Router to take full advantage of new features.
Creating Your First Page
After installation, the project structure will look like this:
my-next-app/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home page
│ └── globals.css # Global styles
├── public/ # Static assets
├── package.json
└── next.config.ts
Let's create a simple "About" page:
// app/about/page.tsx
export default function AboutPage() {
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-4xl font-bold mb-4">About Us</h1>
<p className="text-lg text-gray-700">
Welcome to our Next.js 15 application!
</p>
</div>
);
}
To access this page, simply open http://localhost:3000/about
- Next.js automatically creates routes based on folder structure.
Fetching Data with Server Component
One of the strengths of Server Components is the ability to fetch data directly:
// app/users/page.tsx
interface User {
id: number;
name: string;
email: string;
}
async function getUsers(): Promise<User[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/users', {
// Cache data for 1 hour
next: { revalidate: 3600 }
});
if (!res.ok) {
throw new Error('Failed to fetch users');
}
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-6">Users</h1>
<div className="grid gap-4">
{users.map((user) => (
<div key={user.id} className="p-4 border rounded-lg">
<h2 className="text-xl font-semibold">{user.name}</h2>
<p className="text-gray-600">{user.email}</p>
</div>
))}
</div>
</div>
);
}
Creating a Form with Server Action
Building a complete contact form:
// app/contact/actions.ts
'use server';
export async function submitContactForm(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const message = formData.get('message') as string;
// Validation
if (!name || !email || !message) {
return {
success: false,
error: 'All fields are required'
};
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return {
success: false,
error: 'Invalid email address'
};
}
try {
// Send email or save to database
console.log('Contact form submitted:', { name, email, message });
return {
success: true,
message: 'Thank you for contacting us!'
};
} catch (error) {
return {
success: false,
error: 'Failed to submit form. Please try again.'
};
}
}
// app/contact/page.tsx
import { submitContactForm } from './actions';
export default function ContactPage() {
return (
<div className="container mx-auto px-4 py-8 max-w-2xl">
<h1 className="text-3xl font-bold mb-6">Contact Us</h1>
<form action={submitContactForm} className="space-y-4">
<div>
<label htmlFor="name" className="block mb-2 font-medium">
Name
</label>
<input
type="text"
id="name"
name="name"
required
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label htmlFor="email" className="block mb-2 font-medium">
Email
</label>
<input
type="email"
id="email"
name="email"
required
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div>
<label htmlFor="message" className="block mb-2 font-medium">
Message
</label>
<textarea
id="message"
name="message"
rows={5}
required
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<button
type="submit"
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Send Message
</button>
</form>
</div>
);
}
Best Practices
1. When to Use Server Components
Server Components are the default and should be used when:
- Data Fetching: Fetch data from database or API
- Backend Logic: Handle complex business logic
- Security: Code contains sensitive information
- Large Dependencies: Use large libraries (only run on server)
// Using Server Component (default)
async function ProductList() {
const products = await fetchProducts(); // Fetch directly
return <div>{/* Render products */}</div>;
}
2. When to Use Client Components
Client Components are necessary when:
- Interactivity: Use event listeners (onClick, onChange, etc.)
- Browser APIs: Need to access window, localStorage, etc.
- React Hooks: useState, useEffect, useContext
- Real-time Updates: WebSocket connections
'use client'; // Declare Client Component
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
3. Error Handling
Next.js 15 provides built-in error boundaries:
// app/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
<p className="text-gray-600 mb-4">{error.message}</p>
<button
onClick={reset}
className="px-4 py-2 bg-blue-600 text-white rounded"
>
Try again
</button>
</div>
);
}
4. TypeScript Setup
Leverage TypeScript for better type safety:
// types/index.ts
export interface Post {
id: string;
title: string;
content: string;
author: {
name: string;
email: string;
};
createdAt: Date;
tags: string[];
}
// app/blog/[id]/page.tsx
import type { Post } from '@/types';
async function getPost(id: string): Promise<Post> {
// Fetch post with type safety
const res = await fetch(`/api/posts/${id}`);
return res.json();
}
Conclusion
Next.js 15 represents a major step forward in building modern web applications. With App Router, React Server Components, Server Actions, and Turbopack, this framework provides a powerful toolkit for building high-performance applications with an excellent development experience.
Key Takeaways:
- App Router: New routing architecture with folder-based organization
- Server Components: Reduce bundle size and improve performance
- Server Actions: Simplify form handling and mutations
- Turbopack: Significantly faster builds and hot reload
Next Steps
To continue your Next.js 15 learning journey:
- Practice: Create a small project to get familiar with the concepts
- Read Documentation: Next.js Official Docs
- Explore Advanced Features: Middleware, Internationalization, Analytics
- Join Community: Next.js Discord
Additional Resources
- Next.js 15 Release Notes
- React Server Components RFC
- Vercel Deployment Guide
- Next.js GitHub Repository
Questions? Leave a comment below or contact via email. Good luck with Next.js 15!

Cong Dinh
Technology Consultant | Trainer | Solution Architect
With over 10 years of experience in web development and cloud architecture, I help businesses build modern and sustainable technology solutions. Expertise: Next.js, TypeScript, AWS, and Solution Architecture.
Related Posts
TypeScript Best Practices 2025: Writing Clean and Safe Code
Explore modern TypeScript patterns, utility types, and best practices to write type-safe and maintainable code that helps teams develop more effectively.

TypeScript Best Practices for React Developers
A comprehensive guide on best practices when using TypeScript in React projects, including typing patterns, generic types, and advanced techniques
Optimizing Next.js Performance: Comprehensive Guide 2025
Learn how to optimize your Next.js application to achieve Lighthouse score >95 with image optimization, code splitting, font optimization and Core Web Vitals monitoring.