Getting Started with Next.js 15: Comprehensive Guide

9 min readCong Dinh
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:

  1. Practice: Create a small project to get familiar with the concepts
  2. Read Documentation: Next.js Official Docs
  3. Explore Advanced Features: Middleware, Internationalization, Analytics
  4. Join Community: Next.js Discord

Additional Resources


Questions? Leave a comment below or contact via email. Good luck with Next.js 15!

Cong Dinh

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