Next.js transforms React into a complete suite of tools for modern web development. Its latest version brings revolutionary features that change how we build web applications. Let's discover why Next.js has become essential!
Next.js 15 Key Features 🎯
- App Router - A modern file system-based routing system
- Server Components - React components rendered on the server by default
- Server Actions - Server mutations directly from your components
- Streaming - Progressive page loading for smooth UX
- Smart Cache - Automatic data caching
- Zero config - Ready to use with built-in optimizations
Quick Start ⚙️
1# Create a new project
2npx create-next-app@latest my-app --typescript --tailwind --app
3
4# Start the development server
5cd my-app
6npm run dev
Modern Architecture 📂
The App Router introduces a more intuitive project structure:
1my-app/
2├── app/
3│ ├── layout.tsx # Root layout
4│ ├── page.tsx # Home page (/)
5│ ├── loading.tsx # Global loading
6│ ├── error.tsx # Error handling
7│ ├── not-found.tsx # 404 page
8│ ├── blog/
9│ │ ├── page.tsx # /blog
10│ │ └── [slug]/
11│ │ └── page.tsx # /blog/:slug
12│ └── api/
13│ └── route.ts # API Routes
14├── components/
15│ ├── server/ # Server components
16│ ├── client/ # Client components
17│ └── ui/ # UI components
18└── lib/
19 └── actions.ts # Server Actions
Server Components ⚡️
Components are server-rendered by default for better performance:
1// app/blog/page.tsx
2import { PostList } from '@/components/server/PostList';
3import { Suspense } from 'react';
4
5export default async function BlogPage() {
6 return (
7 <main className="container mx-auto px-4 py-8">
8 <h1 className="text-4xl font-bold mb-8">Our Blog</h1>
9 <Suspense fallback={<p>Loading posts...</p>}>
10 <PostList />
11 </Suspense>
12 </main>
13 );
14}
15
16// components/server/PostList.tsx
17async function getPosts() {
18 const posts = await db.post.findMany();
19 return posts;
20}
21
22export async function PostList() {
23 const posts = await getPosts();
24
25 return (
26 <div className="grid gap-6 md:grid-cols-2">
27 {posts.map(post => (
28 <article key={post.id} className="p-4 rounded-lg border">
29 <h2 className="text-xl font-semibold">{post.title}</h2>
30 <p className="mt-2 text-gray-600">{post.excerpt}</p>
31 </article>
32 ))}
33 </div>
34 );
35}
Client Components 🔄
For components that require interactivity:
1// components/client/SearchBar.tsx
2'use client';
3
4import { useState } from 'react';
5import { useRouter } from 'next/navigation';
6
7export function SearchBar() {
8 const [query, setQuery] = useState('');
9 const router = useRouter();
10
11 function handleSearch(e: React.FormEvent) {
12 e.preventDefault();
13 router.push(`/search?q=${encodeURIComponent(query)}`);
14 }
15
16 return (
17 <form onSubmit={handleSearch} className="flex gap-2">
18 <input
19 type="search"
20 value={query}
21 onChange={(e) => setQuery(e.target.value)}
22 placeholder="Search..."
23 className="px-4 py-2 rounded border"
24 />
25 <button type="submit" className="px-4 py-2 bg-blue-500 text-white rounded">
26 Search
27 </button>
28 </form>
29 );
30}
Server Actions ⚡️
Server-side mutations directly from your components:
1// lib/actions.ts
2'use server';
3
4import { revalidatePath } from 'next/cache';
5import { redirect } from 'next/navigation';
6import { z } from 'zod';
7
8const PostSchema = z.object({
9 title: z.string().min(3, "Title must contain at least 3 characters"),
10 content: z.string().min(10, "Content must contain at least 10 characters")
11});
12
13export async function createPost(formData: FormData) {
14 const data = {
15 title: formData.get('title'),
16 content: formData.get('content')
17 };
18
19 const result = PostSchema.safeParse(data);
20
21 if (!result.success) {
22 return { error: result.error.flatten() };
23 }
24
25 const post = await db.post.create({
26 data: result.data
27 });
28
29 revalidatePath('/blog');
30 redirect('/blog');
31}
32
33// app/blog/new/page.tsx
34import { createPost } from '@/lib/actions';
35
36export default function NewPostPage() {
37 return (
38 <form action={createPost} className="space-y-4 max-w-lg mx-auto">
39 <div>
40 <label htmlFor="title" className="block text-sm font-medium">
41 Title
42 </label>
43 <input
44 type="text"
45 name="title"
46 id="title"
47 required
48 className="mt-1 block w-full rounded-md border-gray-300"
49 />
50 </div>
51 <div>
52 <label htmlFor="content" className="block text-sm font-medium">
53 Content
54 </label>
55 <textarea
56 name="content"
57 id="content"
58 required
59 rows={5}
60 className="mt-1 block w-full rounded-md border-gray-300"
61 />
62 </div>
63 <button
64 type="submit"
65 className="w-full py-2 px-4 bg-blue-500 text-white rounded-md"
66 >
67 Publish
68 </button>
69 </form>
70 );
71}
Modern API Routes 🛠️
1// app/api/posts/route.ts
2import { NextResponse } from 'next/server';
3import { z } from 'zod';
4
5const PostSchema = z.object({
6 title: z.string().min(3),
7 content: z.string().min(10),
8});
9
10export async function GET(request: Request) {
11 const { searchParams } = new URL(request.url);
12 const query = searchParams.get('q');
13
14 const posts = await db.post.findMany({
15 where: query
16 ? {
17 OR: [
18 { title: { contains: query } },
19 { content: { contains: query } },
20 ],
21 }
22 : undefined,
23 });
24
25 return NextResponse.json({ posts });
26}
27
28export async function POST(request: Request) {
29 try {
30 const body = await request.json();
31 const result = PostSchema.safeParse(body);
32
33 if (!result.success) {
34 return NextResponse.json(
35 { error: result.error.flatten() },
36 { status: 400 },
37 );
38 }
39
40 const post = await db.post.create({
41 data: result.data,
42 });
43
44 return NextResponse.json(post, { status: 201 });
45 } catch (error) {
46 return NextResponse.json({ error: 'Server error' }, { status: 500 });
47 }
48}
Optimizations and Best Practices 🚀
1. Streaming with Suspense
1import { Suspense } from 'react';
2import { PostList } from '@/components/server/PostList';
3import { SidebarNav } from '@/components/server/SidebarNav';
4import { Loading } from '@/components/ui/Loading';
5
6export default function BlogLayout({
7 children
8}: {
9 children: React.ReactNode
10}) {
11 return (
12 <div className="flex gap-8">
13 <Suspense fallback={<Loading />}>
14 <SidebarNav />
15 </Suspense>
16 <main className="flex-1">
17 <Suspense fallback={<Loading />}>
18 {children}
19 </Suspense>
20 </main>
21 <aside className="w-64">
22 <Suspense fallback={<Loading />}>
23 <PostList type="recent" />
24 </Suspense>
25 </aside>
26 </div>
27 );
28}
2. Error Handling
1// app/error.tsx
2'use client';
3
4export default function Error({
5 error,
6 reset
7}: {
8 error: Error & { digest?: string };
9 reset: () => void;
10}) {
11 return (
12 <div className="flex flex-col items-center justify-center min-h-[400px]">
13 <h2 className="text-2xl font-bold mb-4">
14 An error has occurred!
15 </h2>
16 <button
17 onClick={() => reset()}
18 className="px-4 py-2 bg-blue-500 text-white rounded"
19 >
20 Try again
21 </button>
22 </div>
23 );
24}
3. Optimized Image Loading
1import Image from 'next/image';
2
3export function Avatar({ src, alt }: { src: string; alt: string }) {
4 return (
5 <div className="relative w-10 h-10">
6 <Image
7 src={src}
8 alt={alt}
9 fill
10 sizes="40px"
11 className="rounded-full object-cover"
12 priority
13 />
14 </div>
15 );
16}
Going Further 🎈
Summary ✨
Next.js 15 represents a major evolution in React web development with:
- Server-first: Server components by default for better performance
- Simplicity: An intuitive file-based architecture
- Performance: Automatic optimizations and streaming
- Improved DX: Modern Server Actions and API Routes
The combination of App Router, Server Components, and Server Actions provides a solid foundation for building modern, efficient, and maintainable web applications.
Now that you understand the basics of Next.js 14, you're ready to create modern and efficient web applications! Don't hesitate to check the official documentation to dive deeper into each concept. Happy coding! 🚀