Controlli sul vibe coding in Next a Giugno 2026

10 (personali) ottimizzazioni per progetti in vibe coding con Next.js

Una lista di ciò che io preferisco controllare quando un AGENT scrive in Next, spesso noto che certi passaggi vengono persi, per questo mi sono segnato questi 10 punti di controllo a cui preferisco sempre prestare una particolare attenzione.

Versione 16.2.x20 giugno 20269 min di letturaSviluppo
Attenzione!Questa è una lista personale che io utilizzo al giorno della pubblicazione di questo articolo, prendete sempre tutto con le pinze e con ogni ragionevole dubbio, non sono il creatore di Next, solo un utilizzatore.


Lo scenario

00Cosa è cambiato in Next 16

Next.js 16 è arrivato e quando il codice è “vibe” può capitare che certe parti facciano riferimento a delle vecchie istanze del framework.

Next 16:
Le tre cose che cambiano davvero il modo in cui scrivi codice ogni giorno sono il nuovo modello di caching, il rendering ibrido di default e la ridenominazione del middleware.

Il cambio di mentalita piu grande e questo: in Next 16 il caching e opt-in. Nelle versioni precedenti l’App Router cachava automaticamente. Oggi, di default, tutto e dinamico finchè non chiedi esplicitamente use cache.

Sul fronte performance, Turbopack e ora il bundler di default e stabile sia in sviluppo che in produzione. La 16.2 accelera ulteriormente il dev server. Runtime minimo Node.js 20+ e React 19.2.

Facendo ormai il tanto amato vibe coding oppure affidandosi troppo alle intelligenze artificiali per la stesura del codice, può spesso capitare che esse facciano riferimento ad una nuova versione di Next, per questo lascio in questa lista, che è un reminder anche per me, quelle 10 cose che secondo me sono sempre da tenere sotto controllo.

Come leggere questa guidaOgni capitolo ha spiegazione, esempio commentato e riquadri: Attenzione per errori comuni, Strategia per scelte di fondo, Check AI per i controlli sul codice generato. Il tono imperativo di certe affermazioni non è un obbligo di "questo è l'unico modo ed è giusto seguire per forza questa strada in ogni caso", ma è per finialità di prompting agli agenti.



Caching

01Cache Components & use cache

I Cache Components sono il cuore di Next 16: tu decidi cosa cachare marcando pagina, componente o funzione con 'use cache'.

Per attivare il modello, accendi il flag in config. Da quel momento ogni codice dinamico gira a ogni richiesta, a meno che non venga cachato.

next.config.tsts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,   // attiva i Cache Components (+ PPR di default)
}
export default nextConfig

La direttiva funziona a tre livelli (file, componente, funzione). A livello funzione ottieni controllo granulare e pratico.

app/prodotti/page.tsxtsx
import { cacheLife, cacheTag } from 'next/cache'

async function getProdotti(categoria: string) {
  'use cache'
  cacheLife('hours')
  cacheTag(`prodotti:${categoria}`)

  const res = await fetch(`https://api.shop/p?cat=${categoria}`)
  return res.json()
}

export default async function Page({ searchParams }) {
  const { cat } = await searchParams
  const prodotti = await getProdotti(cat ?? 'tutti')
  return <Griglia prodotti={prodotti} />
}

Tre funzioni accompagnano la direttiva: cacheLife() (freschezza), cacheTag() (invalidazione mirata) e revalidateTag() (butta via le entry col tag).

La regola d'oroCacha solo cio che e lento da calcolare e uguale per tutti gli utenti: cataloghi, marketing, contenuti editoriali e query condivise.
Migri da Next 15?Il pattern export const revalidate lascia posto a use cache + cacheLife. Attiva il flag in staging e leggi output di next build.



Sicurezza della cache

02Mai cache condivisa sui dati utente

La 'use cache' normale crea una cache condivisa: il risultato calcolato per un utente puo essere servito ad altri.

La soluzione e esplicita: 'use cache: private' per isolare le entry per sessione utente.

app/dashboard/saldo.tsxtsx
// Dati personali -> cache PRIVATA, isolata per utente
async function Saldo() {
  'use cache: private'
  cacheLife('seconds')
  const me = await getUtenteCorrente()
  return <p>Saldo: {me.saldo} €</p>
}

// NON farlo MAI: dato personale dietro cache condivisa
async function SaldoSbagliato() {
  'use cache'
  const me = await getUtenteCorrente()
  return <p>Saldo: {me.saldo} €</p>
}
Test mentalePrima di scrivere 'use cache' chiediti: questo output e corretto per qualunque utente? Se no, private oppure dinamico.
Check sul codice AIQuando un assistente aggiunge use cache, verifica sempre che non legga sessione, cookie o utente corrente.



Rendering

03Shell statico, dati in streaming

Con Cache Components, la Partial Prerendering diventa il comportamento di default. Non devi piu scegliere tra tutta statica o tutta dinamica.

Next pre-renderizza uno shell statico e streamma le parti dinamiche in <Suspense> man mano che sono pronte.

app/page.tsxtsx
import { Suspense } from 'react'

export default function Page() {
  return (
    <>
      <Header />
      <Hero />
      <Suspense fallback={<FeedSkeleton />}>
        <FeedPersonale />
      </Suspense>
      <Footer />
    </>
  )
}

Il fallback non è estetica: e quello che l utente vede mentre arrivano i dati. Uno skeleton fatto bene riduce CLS e migliora la percezione di velocità.

Dove mettere il confineDisegna Suspense attorno alle isole lente/dinamiche e tieni statico il guscio attorno.



Data fetching

04Carica i dati sul server, in parallelo

Nell’App Router i componenti sono Server Components di default: fetch vicino a dove servono i dati, niente useEffect + useState per caricare dati pagina.

Errore comune: waterfall sequenziale. Se le chiamate sono indipendenti, usa Promise.all.

app/profilo/page.tsxtsx
// Waterfall
const profilo = await getProfilo()
const ordini  = await getOrdini()

// Parallelo
const [profilo2, ordini2] = await Promise.all([
  getProfilo(),
  getOrdini(),
])

return <Vista profilo={profilo2} ordini={ordini2} />

Se una delle due chiamate è lenta ma non blocca il resto della pagina, non aspettarla nel componente: spostala in un sotto-componente avvolto in <Suspense> e lasciala streammare. Server Components per i dati, Suspense per i pezzi lenti: e la combinazione che tiene le pagine reattive.

Check sul codice AIGli assistenti tendono a fare fetch in fila e useEffect ovunque: il fetch dati pagina va riportato sul server.



Proxy (ex middleware)

05proxy.ts

In Next 16 middleware.ts diventa proxy.ts. La funzione esportata si chiama proxy e resta utile per redirect, rewrite e header.

proxy.tsts
import { NextResponse, type NextRequest } from 'next/server'

export function proxy(req: NextRequest) {
  const url = req.nextUrl
  if (url.pathname === '/old') {
    return NextResponse.redirect(new URL('/new', req.url))
  }
  return NextResponse.next()
}

export const config = { matcher: ['/old', '/app/:path*'] }

Due dettagli pratici della migrazione: il proxy gira solo sul runtime Node.js — l’edge non è supportato. E i flag con "middleware" sono rinominati: skipMiddlewareUrlNormalize diventa skipProxyUrlNormalize. Il codemod ufficiale della 16 fa quasi tutto il lavoro.

Il punto più importante è pero strategico: il proxy non è un confine di sicurezza. Non metterci l'autenticazione!

L'autorizzazione va vicino ai dati — in Server Actions, Route Handler e Data Access Layer centralizzato.

Regola nettaProxy per routing. Server Action / Route Handler / DAL per autorizzazione.



Assets

06Immagini e font senza layout shift

Per immagini usa sempre next/image con priority sulla hero e sizes corretti. Per font usa next/font per self-host e meno CLS.

app/page.tsxtsx
import Image from 'next/image'
import hero from './hero.jpg'

<Image
  src={hero}
  alt="Dashboard del prodotto"
  priority
  sizes="100vw"
  placeholder="blur"
/>
app/layout.tsxtsx
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'], display: 'swap' })

export default function RootLayout({ children }) {
  return <html className={inter.className}><body>{children}</body></html>
}
Novita 16Le immagini locali con query string richiedono images.localPatterns.search configurato.



Bundle & client

07Spedisci meno JavaScript

Ogni componente con 'use client' e tutto cio che importa finisce nel bundle browser. Tienilo sui componenti interattivi.

app/articolo/page.tsxtsx
import BottoneLike from './bottone-like'

export default async function Page() {
  const articolo = await getArticolo()
  return (
    <article>
      <Contenuto dati={articolo} />
      <BottoneLike id={articolo.id} />
    </article>
  )
}
components/grafico-lazy.tsxtsx
import dynamic from 'next/dynamic'

const Grafico = dynamic(() => import('./Grafico'), {
  loading: () => <p>Carico il grafico...</p>,
})
Check sul codice AICerca 'use client' in page/layout: quasi sempre e troppo in alto.



Mutazioni

08Server Actions per scrivere dati

Le validazioni una volta venivano fatte lato back, poi lato front, ora si ha una doppia validazione spesso, sia front che back, ma in un app fullstack tutta in Next, la validazione che io preferisco è quella lato server action.

Le validazioni in zod stanno diventando sempre più importanti anche nell’era della generative UI per verificare le risposte degli llm, secondo me ne vale la pena comprendere questo pacchetto.

app/azioni.tsts
'use server'
import { z } from 'zod'
import { revalidateTag } from 'next/cache'

const Schema = z.object({ id: z.string(), prezzo: z.number().positive() })

export async function aggiornaPrezzo(form: FormData) {
  const utente = await richiediAuth()
  if (!utente.puoModificare) throw new Error('Non autorizzato')

  const dati = Schema.parse({
    id: form.get('id'),
    prezzo: Number(form.get('prezzo')),
  })

  await db.prodotti.update(dati)
  revalidateTag(`prodotti:${dati.id}`)
}
Lo schema delle 4 mosseautorizza -> valida -> scrivi -> invalida



Qualità & codice AI

09Non fidarti dell'input (ne dell'AI)

Eccoci al cuore del controllare il codice generato dall'AI. Il principio di base vale sempre: ogni dato che entra dal mondo esterno va validato al confine - body delle richieste, query string, FormData, parametri.

Lo strumento standard e zod (o equivalente): definisci la forma attesa e .parse() rifiuta tutto cio che non combacia, dandoti anche i tipi TypeScript gratis.

validazione.tsts
import { z } from 'zod'

const Iscrizione = z.object({
  email: z.string().email(),
  eta: z.number().int().min(18),
})

const dati = Iscrizione.parse(input)  // throw se non valido
// da qui in poi dati e tipizzato e sicuro

Quando rivedi codice scritto da un assistente, oltre alla validazione ci sono due controlli mirati che intercettano la grande maggioranza dei problemi di sicurezza.


Verifica

10Misura, non indovinare

L'ultima ottimizzazione lega tutte le altre: non ottimizzare a sensazione, misura. Hai tre strumenti, dal piu semplice al piu automatico.

L'output di next build ti dice rotta per rotta cosa e statico e cosa e dinamico. Dopo aver attivato i Cache Components e la prima cosa da guardare: spesso scopri una pagina creduta statica ma dinamica per cookie o data runtime.

terminalebash
next build

# DevTools MCP (Next 16)
{ "mcpServers": { "next-devtools": {
    "command": "npx",
    "args": ["-y", "next-devtools-mcp@latest"]
}}}

I Web Vitals sono il termometro onesto: LCP (contenuto principale), INP (reattivita input), CLS (stabilita layout).

Infine il gate automatico: le regole che non puoi seguire quasi sempre vivono in CI. ESLint, tsc --noEmit e test su ogni PR bloccano il codice AI difettoso prima del merge.

Documento vs pipelineUna regola che, se violata, bloccherebbe il merge -> mettila in CI. Una regola da review -> mettila nei file di istruzioni AI. Le due cose si completano.



Testing

TTesting con Jest, da zero

Cos'e un test (e cos'e Jest)

Il capitolo 10 diceva metti i test in CI. Ma se non hai mai scritto un test, da dove parti? Qui l'idea e partire da esempi copiabili.

Un test e codice che verifica altro codice: se passi questo input, ti aspetti questo output. Se in futuro rompi il comportamento, il test fallisce prima che lo vedano gli utenti.

Jest e il motore che esegue i test, li trova e segnala verdi/rossi. Next.js lo supporta in modo nativo con next/jest, quindi il setup e piu rapido.

L'anatomia di un test: describe / it / expect

Tre keyword bastano per iniziare: describe raggruppa, it descrive il caso, expect contiene l asserzione.

Schema mentale AAA: Arrange (prepara), Act (esegui), Assert (verifica).

somma.test.tsts
describe('somma', () => {
  it('addiziona due numeri', () => {
    const a = 2, b = 3
    const risultato = a + b
    expect(risultato).toBe(5)
  })
})

Se il risultato differisce, Jest segna rosso e mostra differenza atteso/reale.

Configurare Jest con Next 16

Primo passo: installa Jest e Testing Library come dipendenze di sviluppo.

terminalebash
npm install -D jest jest-environment-jsdom   @testing-library/react @testing-library/dom @testing-library/jest-dom   @testing-library/user-event ts-node @types/jest
jest.config.tsts
import type { Config } from 'jest'
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({ dir: './' }) // carica la config di Next

const config: Config = {
  coverageProvider: 'v8',
  testEnvironment: 'jsdom',          // simula il browser nei test
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
}

export default createJestConfig(config)

jsdom e un browser finto in Node: serve ai componenti React che si aspettano document/DOM.

jest.setup.tsts
import '@testing-library/jest-dom'
package.jsonjson
"scripts": {
  "test": "jest",
  "test:watch": "jest --watch"
}

Primo test: una funzione pura

Il modo piu semplice per partire e testare funzioni pure: input dentro, output fuori, zero effetti collaterali.

utils/prezzo.ts + utils/prezzo.test.tsts
export function formattaPrezzo(centesimi: number) {
  return (centesimi / 100).toFixed(2) + ' €'
}

import { formattaPrezzo } from './prezzo'

describe('formattaPrezzo', () => {
  it('converte i centesimi in euro', () => {
    expect(formattaPrezzo(1599)).toBe('15.99 €')
  })
  it('gestisce lo zero', () => {
    expect(formattaPrezzo(0)).toBe('0.00 €')
  })
})

Lancia npm test e vedrai i casi verdi. Anche gli schemi zod sono candidati perfetti: input valido passa, input errato throw.

Testare un componente: React Testing Library

Per i componenti usa RTL: testa come un utente, non i dettagli interni. Cerca elementi per ruolo/testo, simula click, verifica output.

bottone-like.test.tsxtsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import BottoneLike from './bottone-like'

describe('BottoneLike', () => {
  it('incrementa il contatore al click', async () => {
    render(<BottoneLike />)
    const bottone = screen.getByRole('button', { name: /mi piace/i })
    await userEvent.click(bottone)
    expect(bottone).toHaveTextContent('Mi piace (1)')
  })
})

getByRole('button', { name: /mi piace/i }) usa ruolo + testo visibile. toHaveTextContent arriva da jest-dom.

Limite da conoscere subitoJest non supporta Server Components async. Per componenti async la documentazione Next consiglia E2E con Playwright.
Cosa vale la pena testareNon inseguire il 100% coverage: punta su trasformazioni dati, validazioni, calcoli e rendering condizionato. I flussi completi lasciali agli E2E.
Test & codice AIChiedere all'assistente di scrivere test Jest e un uso ad alto valore: se l'AI rompe comportamento, il test rosso lo blocca prima del merge.

Avvio rapido

terminalebash
npx create-next-app --example with-jest mia-app-jest



Bonus

+React Compiler: memoizzazione gratis

In Next 16 il supporto al React Compiler e stabile (dopo release 1.0). Memoizza automaticamente i componenti e riduce re-render inutili.

Non e attivo di default: conviene abilitarlo, misurare tempi build e Web Vitals, poi decidere con numeri alla mano.

next.config.tsts
const nextConfig = {
  reactCompiler: true,   // memoizzazione automatica, zero codice manuale
}
export default nextConfig
Quando provarloAttivalo, misura build e Web Vitals e decidi sui numeri.



Automatizzare

Dare queste regole all'AI

Tutto ciò che hai letto può diventare regole versionate per Cursor e Claude Code. Principio: file letto a ogni sessione come contratto di comportamento.

Premessa chiave: piu le regole sono specifiche e imperative, piu ti puoi fidare. MAI X / SEMPRE Y batte consigli vaghi.

Per questo motivo, in questa lista il tutto spesso è imperativo + esempio, così che anche io possa copiarlo in una fare di “code review”.

Cursor -> .cursor/rules/*.mdc

Cursor usa file .mdc in .cursor/rules con frontmatter YAML e globs di attivazione. Il vecchio file unico non basta in Agent mode.

.cursor/rules/nextjs16.mdcmdc
con finalità di esempio non di regola:

---
description: Regole vincolanti Next.js 16 (App Router)
globs:
  - "**/*.ts"
  - "**/*.tsx"
  - "next.config.*"
alwaysApply: false
---

# Regole Next.js 16
- Opt-in cache: use cache solo su dati condivisi
- Dati utente -> use cache: private
- Fetch in Server Components, Promise.all per indipendenti
- Auth in Server Action/Route Handler/DAL, mai nel proxy
Estensione obbligatoriaIn Cursor il file deve essere .mdc con frontmatter: un .md puro viene ignorato.

Claude Code -> CLAUDE.md + AGENTS.md

Claude Code legge CLAUDE.md all avvio. Strategia pulita: regole in AGENTS.md come fonte unica e CLAUDE.md che rimanda li.

CLAUDE.mdmd
# CLAUDE.md

Progetto Next.js 16 (App Router).

Le regole di sviluppo sono in @AGENTS.md — applicale sempre.

Le due regole che fanno la differenza

Definition of donePrima di considerare finito: tsc --noEmit, eslint, jest (+ E2E se toccati), input validato, query parametrizzate, autorizzazione, nessun use cache su dati utente.
Mai produrre'use client' su page/layout, await sequenziali su chiamate indipendenti, auth nel proxy.ts, SQL concatenato, useEffect per dati pagina, pattern Next 14/15.
Manutenzione & limitiAggiorna regole in AGENTS.md e riallinea i .mdc. Le regole guidano, la CI garantisce.

Riepilogo

Un esempio di checklist del codice (anche chiedendo all’AI)

  1. Cache esplicita solo su dati lenti e identici per tutti.
  2. Dati per-utente con use cache: private, mai in cache condivisa.
  3. Parti dinamiche dentro Suspense con fallback reale.
  4. Fetch sul server, indipendenti in Promise.all.
  5. Auth nelle Server Action / DAL, mai nel proxy.ts; Next aggiornato.
  6. next/image con priority+sizes e font con next/font.
  7. 'use client' solo sulle foglie; pesanti con next/dynamic.
  8. Server Action: autorizza -> valida -> scrivi -> invalida.
  9. Input validato con zod; query parametrizzate; autorizzazione su ogni accesso.
  10. next build + Web Vitals + lint/tipi/test in CI.

Blog

Altri articoli

Cosa ne pensi dell'articolo? Parliamone...