React e Next.js: le differenze che contano davvero

"Ma quindi Next.js è un React più bello?"

È la domanda che prima o poi arriva in ogni chat di lavoro, di solito venerdì alle 18. La risposta breve è no. La risposta lunga è questo articolo.

React e Next.js non sono due alternative sullo stesso piano: Next.js usa React. Confrontarli è un po' come confrontare un motore con un'automobile. Il motore da solo è una meraviglia di ingegneria, ma se vuoi andare a fare la spesa ti serve anche il resto.

Vediamo quindi cosa ti dà il "resto".

1. Libreria vs framework

React si occupa di una cosa sola, e la fa molto bene: descrivere l'interfaccia in funzione dello stato. Componenti, props, hook, riconciliazione. Fine.

Tutto quello che sta intorno — come navighi tra le pagine, dove prendi i dati, come costruisci il bundle, come lo metti online — lo scegli tu, pezzo per pezzo. È libertà totale, con tutti i pregi e i mal di testa del caso.

Next.js fa le scelte al posto tuo. Routing, build, rendering lato server, ottimizzazioni: tutto già in scatola, con delle convenzioni da rispettare. Meno decisioni da prendere, meno libertà di prenderle.

2. Il routing: da libreria esterna a struttura di cartelle

In React "puro" il routing semplicemente non esiste. Lo aggiungi tu, tipicamente con React Router:

import { BrowserRouter, Routes, Route } from 'react-router-dom'

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/blog/:slug" element={<Articolo />} />
      </Routes>
    </BrowserRouter>
  )
}

In Next.js le rotte sono le cartelle del progetto:

app/
  page.tsx              ->  /
  blog/
    page.tsx            ->  /blog
    [slug]/
      page.tsx          ->  /blog/il-mio-articolo

Nessun file di configurazione da tenere allineato: sposti una cartella e hai cambiato URL. Comodissimo, finché non rinomini una cartella per sbaglio e ti chiedi perché il sito ha una pagina in meno.

Nello stesso sistema entrano anche layout.tsx (layout annidati e persistenti), loading.tsx (stato di caricamento automatico) ed error.tsx (error boundary per rotta) — roba che in un'app React classica ti scrivi a mano.

3. Dove gira il codice: client, server, o tutti e due

Questa è la differenza più grossa, e quella che cambia davvero il modo di ragionare.

Un'app React classica (Vite, Create React App e simili) è una SPA: il server manda un index.html quasi vuoto, il browser scarica il JavaScript, e solo a quel punto compare qualcosa. Ottimo per una dashboard dietro login. Molto meno ottimo per un blog che vuole essere indicizzato.

Next.js ti lascia scegliere, anche pagina per pagina:

  • Statico: l'HTML viene generato in fase di build. Perfetto per articoli e pagine di marketing.
  • Server-side: l'HTML viene generato a ogni richiesta, con dati sempre freschi.
  • Rigenerazione incrementale: statico, ma che si aggiorna da solo dopo un certo tempo. Il meglio dei due mondi, senza dover rifare il deploy a ogni virgola corretta.
  • Client: quando serve interattività vera, si torna al comportamento che già conosci.

4. Server Components: la parte che spiazza all'inizio

Nell'App Router di Next.js i componenti sono server per default. Girano sul server, non finiscono nel bundle del browser, e possono essere async:

// Questo componente gira sul server: niente useEffect, niente stato di loading
export default async function Articoli() {
  const articoli = await db.query('SELECT * FROM articoli')

  return (
    <ul>
      {articoli.map((a) => (
        <li key={a.slug}>{a.titolo}</li>
      ))}
    </ul>
  )
}

Quando ti serve interattività — uno stato, un evento, un hook — dichiari esplicitamente il confine con 'use client':

'use client'

import { useState } from 'react'

export default function Contatore() {
  const [n, setN] = useState(0)
  return <button onClick={() => setN(n + 1)}>Hai cliccato {n} volte</button>
}

Il dettaglio importante: i Server Components fanno parte di React, non di Next.js. React definisce il modello, ma qualcuno deve implementarlo — e Next.js è oggi il modo più diffuso per usarlo. È il classico caso in cui la riga di confine tra i due progetti diventa sfumata.

Detto sinceramente, all'inizio si sbaglia. Metti un onClick in un Server Component, l'errore ti spiega gentilmente che no, non si fa, e piano piano ti entra in testa la domanda giusta: questo codice deve girare dove?

5. I dati: da useEffect a "li ho già"

Il pattern React che tutti abbiamo scritto mille volte:

const [dati, setDati] = useState(null)
const [loading, setLoading] = useState(true)

useEffect(() => {
  fetch('/api/articoli')
    .then((r) => r.json())
    .then((d) => {
      setDati(d)
      setLoading(false)
    })
}, [])

Tre stati, un waterfall di richieste e uno spinner. In un Server Component diventa un await e basta: i dati sono già lì quando l'HTML arriva al browser.

Attenzione a una cosa: il comportamento della cache di Next.js è cambiato parecchio tra una major e l'altra. Nelle versioni recenti le fetch non sono più memorizzate in cache per default, quindi prima di dare per scontato qualcosa, controlla la documentazione della versione che stai usando davvero.

6. Il backend che non sapevi di avere

Con React puro, se ti serve un endpoint, ti serve un altro progetto. Con Next.js scrivi API e logica server nello stesso repository:

// app/api/newsletter/route.js
export async function POST(request) {
  const { email } = await request.json()
  await salvaIscrizione(email)
  return Response.json({ ok: true })
}

Ci sono anche le Server Actions: funzioni che scrivi sul server e richiami direttamente da un form, senza costruirti l'endpoint a mano. E il middleware, che intercetta le richieste prima che arrivino alla pagina — redirect, autenticazione, gestione della lingua.

Non diventa un sostituto di un backend serio, ma per un blog, un form di contatto o un piccolo e-commerce spesso è più che sufficiente.

7. Le ottimizzazioni comprese nel prezzo

Cose che in React implementi tu (o che rimandi a "dopo", e poi arriva Lighthouse a ricordartelo):

  • next/image: ridimensionamento, formati moderni, lazy loading.
  • next/font: font self-hosted senza salti di layout.
  • Metadata API: title, description e Open Graph definiti per rotta.
  • Code splitting e prefetch dei link già attivi.

8. Il deploy

Un'app React è alla fine una cartella di file statici: la metti ovunque, anche su un hosting di vent'anni fa.

Next.js, appena usi rendering dinamico, ha bisogno di un runtime Node.js. Su Vercel è questione di collegare il repository; altrove funziona lo stesso, ma un container o un adapter te lo devi gestire. Se il progetto è statico al 100% puoi comunque esportarlo, rinunciando però alla parte server.

Quindi, quale scegliere?

React da solo ha senso per dashboard e applicazioni interne dietro login, dove la SEO non conta, per widget da incorporare in siti esistenti e per quando vuoi controllo totale sull'architettura.

Next.js ha senso per qualsiasi cosa pubblica che debba essere indicizzata, per siti con molti contenuti, per progetti che hanno bisogno anche di un po' di backend e per quando preferisci scrivere funzionalità invece di configurazione.

La cosa da tenere a mente è che passare a Next.js non significa dimenticare React: componenti, props e hook restano identici. Cambia il contesto in cui girano, e con esso il modo di pensare al confine tra client e server.

E sì, la prima settimana metterai 'use client' un po' ovunque per far smettere di lamentarsi gli errori. È una fase. Passa.