Next.js trasforma React in una suite completa di strumenti per lo sviluppo web moderno. La sua ultima versione porta funzionalità rivoluzionarie che cambiano il nostro modo di costruire applicazioni web. Scopriamo perché Next.js è diventato indispensabile!
I punti di forza di Next.js 15 🎯
- App Router - Un sistema di routing moderno basato sul filesystem
- Server Components - Componenti React renderizzati lato server di default
- Server Actions - Mutazioni server direttamente dai tuoi componenti
- Streaming - Caricamento progressivo delle pagine per una UX fluida
- Cache intelligente - Caching automatico dei dati
- Zero config - Pronto all'uso con ottimizzazioni integrate
Avvio rapido ⚙️
1# Creazione di un nuovo progetto
2npx create-next-app@latest mia-app --typescript --tailwind --app
3
4# Avvio del server di sviluppo
5cd mia-app
6npm run dev
Architettura moderna 📂
L'App Router introduce una nuova struttura di progetto più intuitiva:
1mia-app/
2├── app/
3│ ├── layout.tsx # Layout root
4│ ├── page.tsx # Pagina home (/)
5│ ├── loading.tsx # Caricamento globale
6│ ├── error.tsx # Gestione errori
7│ ├── not-found.tsx # Pagina 404
8│ ├── blog/
9│ │ ├── page.tsx # /blog
10│ │ └── [slug]/
11│ │ └── page.tsx # /blog/:slug
12│ └── api/
13│ └── route.ts # API Routes
14├── components/
15│ ├── server/ # Componenti server
16│ ├── client/ # Componenti client
17│ └── ui/ # Componenti UI
18└── lib/
19 └── actions.ts # Server Actions
Server Components ⚡️
I componenti sono renderizzati lato server di default per migliori prestazioni:
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">Il Nostro Blog</h1>
9 <Suspense fallback={<p>Caricamento articoli...</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 🔄
Per i componenti che necessitano di interattività:
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="Cerca..."
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 Cerca
27 </button>
28 </form>
29 );
30}
Server Actions ⚡️
Mutazioni lato server direttamente dai tuoi componenti:
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, "Il titolo deve contenere almeno 3 caratteri"),
10 content: z.string().min(10, "Il contenuto deve contenere almeno 10 caratteri")
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 Titolo
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 Contenuto
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 Pubblica
68 </button>
69 </form>
70 );
71}
Route API moderne 🛠️
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: 'Errore server' }, { status: 500 });
47 }
48}
Ottimizzazioni e best practices 🚀
1. Streaming con 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. Gestione degli errori
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 Si è verificato un errore!
15 </h2>
16 <button
17 onClick={() => reset()}
18 className="px-4 py-2 bg-blue-500 text-white rounded"
19 >
20 Riprova
21 </button>
22 </div>
23 );
24}
3. Caricamento ottimizzato delle immagini
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}
Per approfondire 🎈
In sintesi ✨
Next.js 15 rappresenta un'evoluzione importante nello sviluppo web React con:
- Server-first: Componenti server di default per migliori prestazioni
- Semplicità: Un'architettura intuitiva basata sui file
- Performance: Ottimizzazioni automatiche e streaming
- DX migliorata: Server Actions e API Routes moderne
La combinazione di App Router, Server Components e Server Actions offre una base solida per costruire applicazioni web moderne, performanti e manutenibili.
Ora che hai capito le basi di Next.js 14, sei pronto per creare applicazioni web moderne e performanti! Non esitare a consultare la documentazione ufficiale per approfondire ogni concetto. Buono sviluppo! 🚀