Cos'è Next.js e quando usarlo
Cos'è Next.js e quando usarlo: routing basato su file, Server Component e Client Component, caricamento dati, ottimizzazioni integrate e API route spiegate.
Hai imparato React e ti accorgi che non basta: per fare un'applicazione vera servono navigazione tra pagine, dati presi dal server, immagini ottimizzate, pagine che Google riesca a leggere. Ognuna di queste cose è una libreria da scegliere e da incollare alle altre. Next.js è la risposta a questo assemblaggio. In questo articolo trovi cosa aggiunge esattamente a React, la distinzione tra Server Component e Client Component — oggi il concetto più frainteso dell'ecosistema — e quando invece è troppo.
Cos'è
Next.js è un meta-framework costruito sopra React: prende la libreria che si occupa solo dell'interfaccia e ci aggiunge routing, rendering lato server, ottimizzazioni e backend, con le scelte già fatte.
"Meta-framework" significa esattamente questo: un framework che ne avvolge un altro strumento invece di partire da zero. Sotto c'è React, con i suoi componenti e i suoi hook. Quello che hai imparato resta valido.
Cosa aggiunge a React
React di suo non ha routing, non ha un modo di caricare dati, non ha un sistema di build e non impone una struttura di cartelle. Next.js mette quei quattro buchi a posto e aggiunge un server.
Routing basato su file. Non configuri le rotte: le crei come cartelle. Il percorso sul disco è il percorso nell'URL.
app/
page.tsx → /
chi-siamo/page.tsx → /chi-siamo
blog/page.tsx → /blog
blog/[slug]/page.tsx → /blog/qualsiasi-cosa
layout.tsx → involucro comune a tutte le pagine
Le parentesi quadre indicano un segmento dinamico. layout.tsx avvolge tutto quello che sta nella sua cartella e in quelle sotto, e — dettaglio importante — non viene rimontato quando navighi tra pagine che lo condividono: lo stato di una barra laterale sopravvive al cambio pagina.
Rendering lato server. Le pagine possono essere generate sul server, così il browser riceve HTML già pronto: meglio per l'indicizzazione e per il tempo che passa prima che si veda qualcosa. Le strategie disponibili — statica, a ogni richiesta, rigenerazione incrementale — sono spiegate nel dettaglio in SSG, SSR e ISR a confronto; qui basta sapere che in Next.js le scegli per singola pagina e non per l'intero progetto.
Un backend nello stesso progetto. Puoi scrivere endpoint HTTP accanto alle pagine, senza un secondo repository e un secondo deploy.
Ottimizzazioni già configurate. Immagini, font e script hanno componenti dedicati che fanno il lavoro noioso al posto tuo.
App Router: Server Component e Client Component
Questa è la parte che conta di più e quella su cui girano più fraintendimenti. Prenditi due minuti.
Nell'App Router (la struttura basata sulla cartella app/, quella attuale), ogni componente è per impostazione predefinita un Server Component. Gira sul server, produce HTML, e il suo codice non viene mai inviato al browser.
Un Client Component è un componente che viene anche eseguito nel browser. Lo dichiari mettendo "use client" come prima riga del file.
| Server Component | Client Component | |
|---|---|---|
| Predefinito? | Sì | No, serve "use client" |
| Dove gira | Solo sul server | Server (prima resa) e browser |
| JavaScript inviato al browser | Nessuno | Sì, pesa sul pacchetto |
Può usare useState, useEffect | No | Sì |
Può gestire onClick | No | Sì |
| Può leggere il database, usare chiavi segrete | Sì | No, mai |
Può essere async | Sì | No |
La conseguenza pratica: più cose tieni nei Server Component, meno JavaScript scarica il visitatore. Un componente che mostra dati e basta non ha motivo di esistere nel browser.
Quando serve davvero "use client"
Ti serve quando il componente usa qualcosa che esiste solo nel browser:
- stato e ciclo di vita:
useState,useEffect,useReducer - gestori di eventi:
onClick,onChange,onSubmit - API del browser:
window,localStorage,IntersectionObserver - librerie che internamente fanno una di queste cose
Non ti serve per: mostrare dati, fare un ciclo su un array, applicare CSS, usare un componente <Link>.
L'errore più comune è mettere "use client" in cima al layout o alla pagina principale. Sembra che risolva l'errore e infatti lo fa, ma "use client" si propaga verso il basso: tutto quello che quel file importa diventa client. Metti una direttiva in cima all'albero e hai appena trasformato l'intera applicazione in una normale app React lato client, buttando via il motivo per cui usavi Next.js. L'errore non si vede, perché tutto funziona — semplicemente il pacchetto JavaScript è tre volte più grande del necessario.
La tecnica giusta è spingere il confine verso le foglie. Se una pagina ha bisogno di un solo pulsante interattivo, il Client Component è il pulsante, non la pagina:
// app/prodotti/page.tsx — Server Component, nessuna direttiva
import BottoneCarrello from "./bottone-carrello";
export default async function Prodotti() {
const prodotti = await getProdotti();
return (
<ul>
{prodotti.map((p) => (
<li key={p.id}>
<h2>{p.nome}</h2>
<BottoneCarrello id={p.id} />
</li>
))}
</ul>
);
}
// app/prodotti/bottone-carrello.tsx
"use client";
import { useState } from "react";
export default function BottoneCarrello({ id }) {
const [aggiunto, setAggiunto] = useState(false);
return (
<button onClick={() => setAggiunto(true)}>
{aggiunto ? "Aggiunto" : "Aggiungi al carrello"}
</button>
);
}
Un dettaglio che confonde parecchi: un Server Component può essere passato dentro un Client Component come contenuto figlio. Il confine riguarda le importazioni, non l'annidamento. Un layout client con {children} non rende client i figli che gli arrivano da un genitore server.
Il caricamento dati nei Server Component
Qui Next.js semplifica parecchio rispetto a React puro. Un Server Component può essere async e aspettare i dati direttamente:
export default async function Articolo({ params }) {
const { slug } = await params;
const articolo = await db.articoli.findUnique({ where: { slug } });
if (!articolo) notFound();
return (
<article>
<h1>{articolo.titolo}</h1>
<div dangerouslySetInnerHTML={{ __html: articolo.html }} />
</article>
);
}
Niente useEffect, niente stato di caricamento, niente doppio viaggio browser–server. La query parte dove i dati sono già vicini, e al browser arriva solo il risultato in HTML.
Due cose da tenere a mente:
- Le chiavi segrete sono al sicuro. Il codice di un Server Component non raggiunge mai il browser, quindi puoi usare variabili d'ambiente e credenziali del database. In un Client Component sarebbe una falla di sicurezza.
- Attenzione alle chiamate in sequenza. Tre
awaituno dopo l'altro sono tre attese sommate. Se sono indipendenti, lanciale insieme conPromise.all.
Per le scritture (moduli, aggiornamenti) ci sono le Server Action: funzioni marcate "use server" che il client può invocare ma che eseguono sul server.
Le ottimizzazioni integrate
Sono la parte meno appariscente e quella che fa più differenza sui Core Web Vitals.
Immagini. Il componente <Image> ridimensiona, converte in formati moderni, serve la dimensione giusta per lo schermo e riserva lo spazio in pagina per evitare che il contenuto salti al caricamento.
import Image from "next/image";
<Image src="/foto.jpg" alt="Descrizione" width={800} height={600} priority />;
width e height servono proprio a riservare quello spazio: sono le proporzioni, non la dimensione a schermo, che resta compito del CSS. priority va messo solo sull'immagine principale visibile subito; metterlo ovunque annulla il beneficio. Sul tema dei formati: quali formati immagine usare sul web.
Font. next/font scarica il font in fase di build e lo serve dal tuo dominio: niente richiesta a Google, niente testo invisibile mentre carica.
Script. next/script ti fa decidere quando caricare gli script di terze parti, così l'analytics non ritarda il contenuto.
Divisione del codice. Ogni rotta scarica solo il JavaScript che le serve. Non devi configurare nulla.
Nessuna di queste cose è impossibile da fare a mano. Semplicemente, a mano, quasi nessuno le fa tutte. Il tema più ampio è in come velocizzare un sito web.
Le API route
Nella cartella app/ un file route.ts definisce un endpoint HTTP. È il tuo backend, nello stesso progetto:
// app/api/iscritti/route.ts
export async function POST(request) {
const { email } = await request.json();
if (!email?.includes("@")) {
return Response.json({ errore: "Email non valida" }, { status: 400 });
}
await db.iscritti.create({ data: { email } });
return Response.json({ ok: true }, { status: 201 });
}
Esporti una funzione per ogni metodo HTTP: GET, POST, PATCH, DELETE. Se il concetto di endpoint è nuovo, il riferimento è cos'è una REST API.
Con i Server Component e le Server Action, però, le API route servono meno di prima. Hanno senso per i webhook (Stripe che ti notifica un pagamento), per un'API pubblica consumata da altri, o per un'app mobile che parla con lo stesso backend. Per leggere dati da mostrare nelle tue pagine, non passare da una API route: interroga direttamente dal Server Component, altrimenti stai facendo fare al server una chiamata HTTP a sé stesso.
Quando Next.js è la scelta giusta
Ha senso quando ti servono insieme contenuti indicizzabili e interattività vera. Il caso da manuale: un SaaS con pagine di marketing pubbliche e un'applicazione dietro login. Oppure un e-commerce, un marketplace, un portale con account utente.
Ha senso anche quando vuoi frontend e backend in un progetto solo, con un deploy solo. Per capire come si incastra il resto, vedi frontend e backend e come scegliere lo stack.
Quando è troppo
Per un sito statico semplice. Un portfolio, una vetrina aziendale, un blog personale: Next.js ti fa portare un server (o un runtime serverless), l'idratazione di React e un modello mentale non banale, per un risultato che un generatore statico produce più leggero e più veloce. Astro è la scelta più ragionevole per i siti dove il contenuto domina.
Per una dashboard privata senza SEO. Se tutto sta dietro un login, il rendering lato server non ti regala niente. Vite più React è più veloce da sviluppare e da distribuire.
Se ti serve solo il backend. Un'API e basta sta meglio con Fastify o Express su Node.js.
Una nota onesta sull'hosting: Next.js è sviluppato da Vercel ed è lì che l'esperienza è più liscia — vedi la guida a Vercel. Funziona anche altrove, ma le funzionalità che dipendono dal server (rigenerazione incrementale, middleware, ottimizzazione immagini) richiedono più attenzione. Se esporti tutto come sito statico quelle funzionalità semplicemente non ci sono — e a quel punto vale la domanda se ti serviva Next.js.
In sintesi
Next.js è React più tutto il resto: routing sui file, rendering sul server, ottimizzazioni e API nello stesso progetto.
Il concetto chiave dell'App Router è il confine tra server e client. Tutto è Server Component finché non scrivi "use client", e quella direttiva si propaga a tutto ciò che il file importa.
Tieni i Client Component sulle foglie. Il pulsante interattivo è client, la pagina che lo contiene no.
Nei Server Component i dati si caricano con un await diretto, senza useEffect e senza esporre credenziali.
Per un sito statico semplice o una dashboard privata è sovradimensionato. Non tutto quello che si scrive in React ha bisogno di un server.
Per ripassare le basi su cui è costruito, vedi cos'è React; per il runtime che lo fa girare, cos'è Node.js.