Perché un'app React resa solo lato client è la scelta più veloce da costruire e la più cara da correggere, come l'App Router di Next.js separa ciò che deve essere trovato da ciò che deve solo essere usato, e il codice del confine fra i due.
di Nadia Maar ·
La prima decisione tecnica di uno startup quasi mai riguarda il prodotto: riguarda come si costruisce l'interfaccia, e di solito la prende chi ha più fretta di vedere qualcosa sullo schermo. Un'app React resa interamente lato client — nessun server coinvolto nel disegno della pagina — è la scelta più rapida da avviare: si scrive un componente, appare, si itera. È anche la scelta che rende la pagina dei prezzi invisibile a Google finché qualcuno non se ne accorge, di solito quando il primo investitore chiede «perché non uscite nelle ricerche del vostro stesso nome di prodotto».
Il problema non è tecnico nel senso stretto: è che la correzione arriva tardi, e arrivare tardi la rende costosa in un modo che una scelta iniziale diversa non sarebbe stata. Non si tratta di aggiungere un plugin: le pagine che devono essere trovate e le schermate che devono solo essere usate hanno bisogno di due modi diversi di essere costruite, e mescolarle in un'unica architettura scelta per la fretta di settimana uno costa una riscrittura in mese otto.
Questo pezzo è la decisione che evita la riscrittura: come l'App Router di Next.js tiene nello stesso progetto sia le pagine che un motore di ricerca deve poter leggere sia la dashboard interattiva che non deve interessargli affatto, il codice reale del confine fra le due, e il conto di che cosa costa scoprirlo tardi.
Un'app resa solo lato client manda al browser una pagina quasi vuota e un file JavaScript che, una volta eseguito, costruisce tutto il resto. Per una persona con una connessione buona la differenza si nota appena. Per un crawler che deve decidere in una frazione di secondo se una pagina merita di essere indicizzata, quella pagina vuota è spesso tutto quello che vede — e «forse» non è la risposta che porta traffico organico.
Non arriva alla messa online: arriva quando il marketing chiede perché la pagina dei prezzi non compare per il nome del prodotto, o quando un investitore nota che il blog — scritto, pubblicato, buono — non porta traffico. A quel punto il codice del prodotto e il codice del marketing sono già intrecciati, e separarli è la riscrittura, non una correzione.
L'errore non è scegliere React: è trattare ogni pagina allo stesso modo. Le pagine di marketing — landing, prezzi, blog — hanno bisogno di esistere già scritte quando arriva chi le legge, umano o crawler. La dashboard dietro al login ha bisogno esattamente del contrario: interattività immediata, e a un crawler non importa perché non ci entrerà mai. L'App Router di Next.js lascia scegliere per ogni pagina, nello stesso progetto, senza due codebase separate da mantenere.
| Solo lato client | Solo lato server | App Router · per pagina | |
|---|---|---|---|
| Pagina prezzi, indicizzata da Google | Rischiosa: dipende da quanto il crawler aspetta | Sì, per costruzione | Sì — Server Component, generata in anticipo |
| Dashboard con editor interattivo | Sì, per costruzione | Costosa: ogni interazione richiede andata e ritorno al server | Sì — Client Component, idratato solo dove serve |
| Peso scaricato dal visitatore della landing | Tutto il bundle dell'app, dashboard inclusa | Minimo, ma poca interattività ovunque | Solo il markup della pagina più i componenti che servono lì |
| Due codebase da mantenere | No, ma un solo modo compromette l'altro caso | No, stesso compromesso al contrario | No — un progetto solo, la scelta è per file |
La colonna che conta è l'ultima riga della prima colonna: non serve scegliere fra velocità di sviluppo e visibilità sui motori di ricerca. Il confine si traccia per pagina, con una riga in cima al file — non con un'architettura parallela da mantenere per sempre.
Una pagina dei prezzi è un Server Component per default: nessuna direttiva speciale, i dati arrivano con una `fetch` normale eseguita sul server, e quello che il browser riceve è già HTML completo — un crawler non deve aspettare né eseguire niente.
// Nessuna direttiva "use client": questo componente gira SUL SERVER,
// una volta per build (o ad ogni richiesta, con revalidate a zero).
export default async function PaginaPrezzi() {
const piani = await fetch("https://api.esempio.com/piani", {
next: { revalidate: 3600 }, // rigenerata al massimo ogni ora
}).then(r => r.json())
return (
<main>
<h1>Prezzi</h1>
{piani.map((p: { nome: string; prezzo: number }) => (
<div key={p.nome}>
<h2>{p.nome}</h2>
<p>{p.prezzo} € / mese</p>
{/* Isola interattiva DENTRO una pagina statica: solo questo
pezzo idrata JavaScript nel browser, il resto resta HTML. */}
<BottoneAbbonati piano={p.nome} />
</div>
))}
</main>
)
}Il crawler vede titolo, prezzi e struttura senza eseguire una riga di JavaScript, perché non ce n'è dentro questo componente — è già HTML quando lascia il server.
"use client" // Il confine: da qui in giù, JavaScript nel browser.
import { useState } from "react"
export default function BottoneAbbonati({ piano }: { piano: string }) {
const [caricamento, setCaricamento] = useState(false)
async function abbonati() {
setCaricamento(true)
await fetch("/api/checkout", { method: "POST", body: JSON.stringify({ piano }) })
// ...redirect al checkout
}
return (
<button onClick={abbonati} disabled={caricamento}>
{caricamento ? "Un momento…" : "Scegli questo piano"}
</button>
)
}"use client" è l'unica riga che separa le due strategie. Tutto quello che sta sopra questo confine nell'albero dei componenti resta HTML statico; tutto quello che sta sotto diventa JavaScript idratato nel browser — un pulsante, non l'intera pagina.
Riscrivere l'architettura di rendering otto mesi dopo il lancio non è mai «aggiungere l'SSR»: è una cascata, perché ogni scelta successiva presume quella iniziale. Il routing, il modo in cui si recuperano i dati, il confine fra pagina pubblica e area autenticata sono tutti stati scritti assumendo che tutto girasse nel browser.
€ 38.000 – 62.000 — costo tipico di una riscrittura di rendering su un prodotto con 40-60 pagine, sei mesi dopo il lancio
La cifra include il tempo perso a ritestare funzionalità che già funzionavano — non solo il codice nuovo.
Se state per partire con un nuovo prodotto, portate la lista delle pagine che immaginate: quali devono essere trovate e quali devono solo essere usate. In mezz'ora tracciamo il confine prima che diventi un'abitudine di codice difficile da disfare.
Se avete già un'app solo lato client che funziona e nessuno vi chiede ancora traffico organico, ve lo dico: non serve riscriverla oggi. Il momento giusto è prima che la prima pagina di marketing venga scritta sopra l'architettura sbagliata, non dopo che sessanta pagine lo sono già.
Perché il browser riceve una pagina quasi vuota e un file JavaScript che costruisce il contenuto DOPO il primo disegno. Un crawler che non aspetta abbastanza — o che valuta comunque la pagina in svantaggio — vede poco o niente, e i meta-tag per le anteprime social, che devono esistere prima che qualcosa venga eseguito, restano generici.
Sono componenti che girano sul server e producono HTML già completo, senza spedire JavaScript al browser per quella parte di pagina. Si usano per tutto ciò che deve essere trovato — landing, prezzi, blog — mentre la direttiva "use client" segna il confine verso i componenti interattivi (form, editor, dashboard) che devono girare nel browser.
Sì, ed è esattamente il vantaggio dell'App Router: la scelta si fa per file, non per progetto. Una pagina può essere quasi interamente statica con una sola isola interattiva — un pulsante, un form — che idrata JavaScript, mentre il resto resta HTML puro.
Su un prodotto con 40-60 pagine, tipicamente 38.000-62.000 €, sei mesi dopo il lancio — e non è solo codice nuovo: include il tempo di ritestare funzionalità che già funzionavano e il traffico organico perso nelle settimane in cui le pagine restavano poco indicizzabili, che non si recupera spostando in avanti la correzione.