Lanciare un'app Next.js con Cursor, v0 o Bolt è veloce, ma la velocità nasconde falle che non vedi finché qualcuno non le trova al posto tuo. Questa checklist ripassa cosa controllare prima di premere deploy, con il perché di ogni punto, come verificarlo e il codice per correggerlo. Alla fine trovi la versione da due minuti.
Come Next.js traccia il confine tra server e client
Quasi tutte le falle di sicurezza in Next.js sono la stessa falla: qualcosa che doveva restare sul server è passato nel browser. I Server Components girano sul server e non inviano mai il loro codice al client; appena un file porta 'use client', tutto ciò che importa finisce nel bundle pubblico. I Route Handler (app/api/*/route.ts) e le Server Actions sono URL pubblici anche se non li vedi. Il middleware gira prima di tutto, ma non è una muraglia. Con quel confine chiaro, il resto della lista è meccanico.
1. Variabili d'ambiente: attenzione a NEXT_PUBLIC
Perché conta. Tutto ciò che inizia con NEXT_PUBLIC_ viene incorporato nel JavaScript del browser al momento della build. Chiunque può leggerlo con gli strumenti per sviluppatori. Non metterci mai una chiave API privata, un token di servizio né il segreto del tuo database.
Come verificare. Apri il tuo .env e la dashboard di Vercel e leggi ogni variabile con prefisso pubblico chiedendoti: succederebbe qualcosa se la pubblicassi su Twitter? Se la risposta è sì, toglile il prefisso e spostala sul server.
Come correggere. Le variabili senza prefisso esistono solo sul server: leggile da Server Components, Route Handler o Server Actions. Perché un modulo con segreti non possa mai essere importato da un componente client, aggiungi import 'server-only' nella prima riga: se qualcuno lo importa dal lato sbagliato, la build fallisce invece di far trapelare la chiave. Più dettagli in i segreti che la tua app fa trapelare nel browser.
2. Route Handler e Server Actions: valida e autorizza sempre
Perché conta. Una route API non è privata perché sta nella tua cartella. È un URL pubblico che chiunque può chiamare con curl. E una Server Action, anche se invocata da un pulsante, lo è altrettanto: Next.js la espone come endpoint POST con un identificativo. Un pulsante nascosto a un utente non impedisce a qualcuno di colpire l'endpoint direttamente.
Come correggere. Controlla la sessione e i permessi dentro ogni handler e ogni action, non solo nell'interfaccia. Valida il corpo della richiesta con uno schema (zod o simile) prima di toccare il database. Non accettare mai l'identificativo dell'utente dal client: prendilo dalla sessione. E metti un limite di richieste a ciò che costa denaro o invia email.
// app/actions/update-profile.ts
'use server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
const Schema = z.object({ name: z.string().min(1).max(80) })
export async function updateProfile(input: unknown) {
const session = await getSession()
if (!session) throw new Error('Unauthorized')
const data = Schema.parse(input)
// usa session.user.id, mai un id che arriva dal client
await db.user.update({ where: { id: session.user.id }, data })
}
3. Il middleware non è la tua unica autenticazione
Perché conta. È allettante proteggere tutto /dashboard da middleware.ts e dimenticarsene. A marzo 2025 è stata pubblicata la CVE-2025-29927: un header interno (x-middleware-subrequest) permetteva di saltare completamente il middleware nelle versioni interessate, e con esso qualsiasi controllo di sessione che vivesse solo lì. È stata corretta in Next.js 15.2.3 e 14.2.25 (con patch per 13 e 12), ma la lezione resta.
Come correggere. Aggiorna Next.js a una versione corretta e usa il middleware come primo livello (reindirizzare chi non ha sessione), mai come l'unico. Il controllo che protegge davvero i dati va nella pagina, nell'handler o nell'action che li serve.
4. Header di sicurezza
Perché conta. Di default Next.js non invia header difensivi. Senza, la tua app è esposta a clickjacking, al fatto che il browser esegua come script ciò che non lo è e al downgrade a HTTP.
Come correggere. In next.config.js:
// next.config.js
const securityHeaders = [
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]
module.exports = {
poweredByHeader: false,
productionBrowserSourceMaps: false,
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }]
},
}
La Content-Security-Policy merita un discorso a parte: Next.js inietta script inline per idratare la pagina, quindi una CSP rigorosa ha bisogno di un nonce generato nel middleware e passato ai componenti. Inizia in modalità Report-Only. Il dettaglio di ogni header è in gli header di sicurezza HTTP spiegati.
5. Segreti che passano nei Client Components
Perché conta. È facile che una chiave privata finisca in un componente client per errore, soprattutto quando l'IA riscrive file e sposta import. Se compare nel JavaScript servito, è già compromessa.
Come verificare. Dopo next build, cerca in .next/static stringhe come sk_live, service_role, sk-ant o URL di webhook. Se compaiono, ruotale e spostale sul server con import 'server-only' come rete di sicurezza.
6. Immagini remote e richieste a URL dell'utente
Perché conta. next/image ottimizza le immagini attraverso il tuo server. Se in images.remotePatterns permetti qualsiasi host, chiunque può usare il tuo server come proxy di immagini e consumare la tua quota. Lo stesso con una route API che scarica l'URL che l'utente le passa: può puntare a servizi interni della tua infrastruttura (SSRF).
Come correggere. Limita remotePatterns ai domini che usi davvero. Se una route deve scaricare URL esterni, valida il protocollo (solo https), rifiuta IP privati e nomi interni, e imposta un tempo massimo.
7. Redirect aperti
Perché conta. Il classico redirect(searchParams.next) dopo il login permette di costruire link del tuo dominio che finiscono su un sito di phishing: tuodominio.com/login?next=https://cattivo.example.
Come correggere. Accetta solo percorsi relativi che iniziano con / (e non con //), oppure confronta con una lista di destinazioni permesse.
8. File che non dovrebbero essere accessibili
Perché conta. Tutto ciò che sta in public/ viene servito così com'è: un .env copiato lì per errore, un dump del database o un service-account.json sono a un URL di distanza. E le source map in produzione permettono di leggere il tuo codice client con i nomi originali.
Come verificare. Prova tuodominio.com/.env, /.git/HEAD e apri un file JS del bundle per vedere se termina con una riga sourceMappingURL che punta a un .map raggiungibile. Guarda la cartella public/ con gli occhi di uno sconosciuto.
9. Cookie di sessione e librerie di autenticazione
Perché conta. Auth.js (NextAuth), Clerk o Supabase con il suo pacchetto server configurano bene i cookie di default. Quello che fallisce è ciò che aggiungi tu: un cookie tuo senza HttpOnly, un AUTH_SECRET debole o copiato da un tutorial, o la sessione salvata in localStorage.
Come correggere. Genera il segreto con openssl rand -base64 32, salvalo senza prefisso pubblico, e per qualsiasi cookie tuo usa Secure, HttpOnly e SameSite=Lax.
10. Dipendenze e versione di Next.js
Perché conta. La tua app si porta dietro centinaia di pacchetti, e Next.js stesso pubblica correzioni di sicurezza più volte l'anno. Il caso del middleware del 2025 ha colpito chi non aggiornava.
Come correggere. npm audit prima di ogni deploy, Dependabot o Renovate sul repository, e una regola semplice: aggiornare Next.js nello stesso mese in cui esce una patch di sicurezza.
Deploy su Vercel: tre dettagli
- Ambienti. Le variabili hanno un ambito (Production, Preview, Development). Non dare ai deploy di preview le credenziali di produzione: una preview è un URL pubblico con il tuo codice più recente.
- Protezione delle preview. Attiva la Deployment Protection perché gli URL di preview richiedano il login di Vercel; altrimenti chiunque abbia il link vede il branch in corso.
- Header. Quelli di
next.config.jssi applicano su Vercel senza altro; se usi anchevercel.json, non duplicarli.
La tua checklist da 2 minuti
- Nessuna variabile
NEXT_PUBLIC_contiene un segreto; i moduli con chiavi portanoimport 'server-only'. - Ogni Route Handler e Server Action controlla la sessione e valida l'input; l'id dell'utente viene dalla sessione.
- Next.js in una versione corretta; il middleware non è l'unico livello di auth.
- Header di sicurezza in
next.config.js;poweredByHeader: false; CSP almeno in modalità report. - Bundle senza
sk_live,service_rolené webhook; source map disattivate. remotePatternslimitati; nulla scarica URL dell'utente senza validazione.- Redirect solo verso percorsi relativi.
public/senza file sensibili;.enve.gitirraggiungibili.- Cookie con Secure, HttpOnly e SameSite;
AUTH_SECRETcasuale. npm auditpulito; preview protette e senza segreti di produzione.
Verifica tutto questo in 30 secondi
Ripassare questa lista a mano a ogni deploy è noioso e si dimentica. Scanaris esegue controlli in sola lettura sul tuo sito pubblicato: header, segreti esposti nel JavaScript, file sensibili raggiungibili, source map, cookie e CORS. Ogni risultato arriva con un prompt pronto da incollare in Cursor o v0 con la correzione esatta per Next.js. Se lo lasci in monitoraggio, ti avvisa quando una sessione di vibe coding riapre qualcosa. Per approfondire il tuo stack, c'è la pagina di sicurezza per Next.js e la checklist generale di lancio.
Domande frequenti
Le Server Actions sono sicure di default?
Sono più comode di una route API, non più sicure. Next.js le espone come endpoint pubblici; l'autenticazione e la validazione le metti tu dentro ciascuna.
Il middleware protegge le mie route?
Serve per reindirizzare e per controlli economici, ma non deve essere l'unica barriera. Dopo il caso del 2025, la regola è controllare la sessione anche dove i dati vengono serviti.
Mi serve la CSP se uso Next.js?
Sì, ma con nonce, e iniziando in modalità report. Senza nonce, una CSP rigorosa blocca gli script inline del framework stesso e la pagina resta bianca.