WordPress Headless con Next.js: Guida Completa 2026
Perché WordPress Headless nel 2026
Nel 2026, il confine tra CMS tradizionale e frontend moderno è sempre più sfumato — ma non scomparso. WordPress alimenta ancora oltre il 40% del web, ma il paradigma headless ha trasformato radicalmente come i developer lo usano. Invece di affidarsi ai temi PHP e al loop di WordPress, oggi puoi sfruttare la REST API (o GraphQL con WPGraphQL) come backend headless, e costruire il frontend con Next.js: performance al massimo, DX moderna, deploy su Vercel in pochi secondi.
Perché questa combinazione vince? Primo, il cliente conosce già il pannello WordPress — nessuna curva di apprendimento per chi pubblica contenuti. Secondo, tu hai il controllo totale sul frontend: React Server Components, ISR, routing App Router, ottimizzazione immagini con next/image. Terzo, la SEO non soffre: Next.js genera HTML statico o server-side, Google indicizza tutto perfettamente. Se hai già lavorato con la REST API WordPress per frontend headless, questa guida è il passo successivo naturale: mettiamo tutto insieme in un progetto reale con Next.js 15+.
Il risultato finale è un sito che ha la flessibilità editoriale di WordPress e la velocità di un'app statica. Lighthouse score 95+, TTFB sotto i 200ms, e un flusso di lavoro che scala senza problemi. Iniziamo.
Architettura Headless: Come Funziona
Nel modello headless, WordPress fa una sola cosa: gestire i contenuti. Il frontend è completamente separato — un'app Next.js che legge i dati via REST API o GraphQL e li renderizza come vuole. Questo disaccoppiamento è il punto di forza dell'approccio.
- WordPress (backend): gestisce articoli, pagine, media, tassonomie, utenti. Accessibile via
https://tuosito.it/wp-json/wp/v2/ - Next.js (frontend): fetch dei dati, rendering SSG/ISR/SSR, routing, ottimizzazione immagini, deploy.
- CDN / Vercel: distribuisce il frontend in edge, cache globale, revalidazione automatica.
Il flusso tipico: un editor pubblica un post su WordPress → WordPress invia un webhook a Next.js → Next.js revalida le pagine interessate con revalidatePath() o revalidateTag(). In questo modo il sito rimane statico (velocissimo) ma aggiornato entro pochi secondi dalla pubblicazione.
Setup Iniziale: WordPress e Next.js 15
Prima di tutto, assicurati che WordPress abbia la REST API attiva (lo è per default) e che CORS sia configurato correttamente. Sul server WordPress, aggiungi questo snippet al file functions.php del tema child oppure a un plugin custom:
// Abilita CORS per il frontend Next.js
add_action( 'rest_api_init', function () {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function ( $value ) {
$origin = get_http_origin();
$allowed = [
'https://tuofrontend.vercel.app',
'https://tuodominio.com',
'http://localhost:3000',
];
if ( in_array( $origin, $allowed, true ) ) {
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
header( 'Access-Control-Allow-Credentials: true' );
}
return $value;
} );
}, 15 );
Sul lato Next.js, crea un nuovo progetto con l'App Router:
npx create-next-app@latest mio-blog-headless --typescript --tailwind --app --no-src-dir
cd mio-blog-headless
# Variabili d'ambiente
echo 'WORDPRESS_API_URL=https://tuosito.it/wp-json/wp/v2' > .env.local
echo 'WORDPRESS_REVALIDATE_SECRET=mio-segreto-sicuro' >> .env.local
Definisci ora i tipi TypeScript per i dati WordPress. Struttura chiara, zero any. Se hai già familiarità con Drizzle ORM e TypeScript type-safe, il concetto è lo stesso: modelli forti, errori a compile-time.
// lib/types.ts
export interface WPPost {
id: number;
slug: string;
status: string;
date: string;
modified: string;
title: { rendered: string };
content: { rendered: string; protected: boolean };
excerpt: { rendered: string };
featured_media: number;
categories: number[];
tags: number[];
_embedded?: {
'wp:featuredmedia'?: Array<{
source_url: string;
alt_text: string;
media_details: {
sizes: Record<string, { source_url: string; width: number; height: number }>;
};
}>;
'wp:term'?: Array<Array<{ id: number; name: string; slug: string }>>;
};
}
export interface WPCategory {
id: number;
name: string;
slug: string;
count: number;
description: string;
}
export interface PaginatedPosts {
posts: WPPost[];
totalPages: number;
total: number;
}
Fetch dei Dati con App Router e ISR
Il cuore dell'integrazione è il layer di fetch. Con Next.js 15 e App Router, le fetch() nelle Server Component supportano nativamente la cache e la revalidazione. Crea un modulo dedicato lib/wordpress.ts:
// lib/wordpress.ts
import type { WPPost, WPCategory, PaginatedPosts } from './types';
const API = process.env.WORDPRESS_API_URL!;
// Helper fetch con cache tag per revalidazione granulare
async function wpFetch<T>(endpoint: string, tag?: string): Promise<T> {
const res = await fetch(`${API}${endpoint}`, {
next: {
revalidate: 3600, // 1 ora di default
tags: tag ? [tag] : ['wordpress'],
},
});
if (!res.ok) {
throw new Error(`WP API error: ${res.status} ${endpoint}`);
}
return res.json() as Promise<T>;
}
// Tutti i post con _embed per immagini e categorie in una sola richiesta
export async function getPosts(page = 1, perPage = 10): Promise<PaginatedPosts> {
const res = await fetch(
`${API}/posts?_embed&page=${page}&per_page=${perPage}&status=publish`,
{ next: { revalidate: 600, tags: ['posts'] } }
);
if (!res.ok) throw new Error(`WP API error: ${res.status}`);
const posts = (await res.json()) as WPPost[];
const total = parseInt(res.headers.get('X-WP-Total') ?? '0', 10);
const totalPages = parseInt(res.headers.get('X-WP-TotalPages') ?? '1', 10);
return { posts, total, totalPages };
}
// Singolo post per slug
export async function getPostBySlug(slug: string): Promise<WPPost | null> {
const posts = await wpFetch<WPPost[]>(
`/posts?slug=${encodeURIComponent(slug)}&_embed&status=publish`,
`post-${slug}`
);
return posts[0] ?? null;
}
// Tutti gli slug per generateStaticParams
export async function getAllSlugs(): Promise<string[]> {
const posts = await wpFetch<Array<{ slug: string }>>(
'/posts?fields=slug&per_page=100&status=publish'
);
return posts.map((p) => p.slug);
}
// Categorie
export async function getCategories(): Promise<WPCategory[]> {
return wpFetch<WPCategory[]>('/categories?per_page=50', 'categories');
}
Questo approccio usa i cache tag di Next.js: quando WordPress pubblica un nuovo articolo e triggera il webhook, Next.js invalida solo il tag posts (o il tag specifico del post), senza ri-generare l'intera build. Molto più efficiente di un full rebuild.
Routing e Pagine: Blog, Singolo Post, Categorie
Con App Router, la struttura delle cartelle rispecchia le route. Ecco la struttura minima per un blog headless:
app/
layout.tsx # layout globale
page.tsx # homepage
blog/
page.tsx # lista articoli
[slug]/
page.tsx # singolo articolo
categoria/
[slug]/
page.tsx # archivio categoria
api/
revalidate/
route.ts # webhook endpoint
La pagina del singolo articolo è quella che fa la differenza in termini di SEO e performance. Usa generateStaticParams per pre-generare tutte le pagine a build time, e generateMetadata per i meta tag dinamici — fondamentale per la SEO tecnica nel 2026:
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { getPostBySlug, getAllSlugs } from '@/lib/wordpress';
import type { Metadata } from 'next';
interface Props {
params: Promise<{ slug: string }>;
}
// Pre-genera tutte le pagine a build time
export async function generateStaticParams() {
const slugs = await getAllSlugs();
return slugs.map((slug) => ({ slug }));
}
// Meta tag SEO dinamici da WordPress
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) return {};
const featuredImage =
post._embedded?.['wp:featuredmedia']?.[0]?.source_url;
return {
title: post.title.rendered,
description: post.excerpt.rendered.replace(/<[^>]+>/g, '').slice(0, 155),
openGraph: {
title: post.title.rendered,
type: 'article',
publishedTime: post.date,
modifiedTime: post.modified,
images: featuredImage ? [{ url: featuredImage }] : [],
},
alternates: {
canonical: `https://tuodominio.com/blog/${slug}`,
},
};
}
// Server Component: zero JS client-side per il contenuto
export default async function PostPage({ params }: Props) {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) notFound();
const featuredMedia = post._embedded?.['wp:featuredmedia']?.[0];
const categories = post._embedded?.['wp:term']?.[0] ?? [];
return (
<article className="max-w-3xl mx-auto px-4 py-12">
<header className="mb-8">
<div className="flex gap-2 mb-4">
{categories.map((cat) => (
<a
key={cat.id}
href={`/categoria/${cat.slug}`}
className="text-xs font-mono text-cyan-400 bg-cyan-400/10 px-3 py-1 rounded-full"
>
{cat.name}
</a>
))}
</div>
<h1
className="text-4xl font-bold mb-4"
dangerouslySetInnerHTML={{ __html: post.title.rendered }}
/>
<time className="text-slate-400 text-sm">
{new Date(post.date).toLocaleDateString('it-IT', {
year: 'numeric', month: 'long', day: 'numeric',
})}
</time>
</header>
{featuredMedia && (
<img
src={featuredMedia.source_url}
alt={featuredMedia.alt_text}
className="w-full rounded-lg mb-8"
width={1200}
height={630}
/>
)}
<div
className="prose prose-invert max-w-none"
dangerouslySetInnerHTML={{ __html: post.content.rendered }}
/>
</article>
);
}
Webhook e Revalidazione On-Demand
Il meccanismo che rende tutto magico è la revalidazione on-demand. Quando pubblichi o modifichi un articolo su WordPress, un webhook notifica Next.js che deve aggiornare quella pagina. Questo è il motivo per cui puoi tenere il sito statico (TTFB bassissimo) ma avere contenuti sempre freschi.
Crea l'endpoint webhook in Next.js:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
const SECRET = process.env.WORDPRESS_REVALIDATE_SECRET!;
export async function POST(req: NextRequest) {
const authHeader = req.headers.get('authorization');
if (authHeader !== `Bearer ${SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
let body: { post_type?: string; slug?: string; action?: string };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
}
const { post_type, slug, action } = body;
if (post_type === 'post' && slug) {
// Revalida la pagina specifica
revalidatePath(`/blog/${slug}`);
revalidateTag(`post-${slug}`);
}
// Revalida sempre lista articoli e homepage
revalidateTag('posts');
revalidatePath('/blog');
revalidatePath('/');
console.log(`[Revalidate] ${action ?? 'update'} - ${post_type}/${slug}`);
return NextResponse.json({
revalidated: true,
timestamp: new Date().toISOString(),
});
}
Su WordPress, configura il webhook con il plugin WP Webhooks oppure con un hook custom nel functions.php:
// Notifica Next.js quando un post viene pubblicato o aggiornato
add_action( 'save_post', function ( $post_id, $post ) {
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
if ( $post->post_type !== 'post' || $post->post_status !== 'publish' ) {
return;
}
$nextjs_url = 'https://tuofrontend.vercel.app/api/revalidate';
$revalidate_secret = 'mio-segreto-sicuro'; // uguale a WORDPRESS_REVALIDATE_SECRET
wp_remote_post( $nextjs_url, [
'headers' => [
'Authorization' => 'Bearer ' . $revalidate_secret,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( [
'post_type' => $post->post_type,
'slug' => $post->post_name,
'action' => 'save_post',
] ),
'timeout' => 10,
] );
}, 10, 2 );
Questa architettura è quella che uso anche per automatizzare i workflow editoriali — principi simili a quelli che ho descritto nella guida su Code Review automatico con GitHub Actions: automazione che si attiva su eventi, zero intervento manuale.
Deploy su Vercel e Ottimizzazioni
Il deploy su Vercel è quasi triviale, ma ci sono alcune ottimizzazioni specifiche per WordPress headless che fanno la differenza. Configura le variabili d'ambiente su Vercel e imposta il dominio personalizzato.
Nel file next.config.ts, abilita le ottimizzazioni per le immagini WordPress e configura le header di sicurezza:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'tuosito.it',
pathname: '/wp-content/uploads/**',
},
],
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 768, 1024, 1280, 1536],
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
],
},
];
},
// Redirect da /wp-admin verso il backend WP
async redirects() {
return [
{
source: '/wp-admin',
destination: 'https://tuosito.it/wp-admin',
permanent: false,
},
];
},
// Logging fetch in sviluppo
logging: {
fetches: {
fullUrl: process.env.NODE_ENV === 'development',
},
},
};
export default nextConfig;
Per il monitoraggio delle performance in produzione, integra Vercel Speed Insights e tieni d'occhio i Core Web Vitals. Un blog headless ben configurato dovrebbe avere LCP sotto 1.5 secondi e CLS a zero — risultati impossibili con WordPress tradizionale su hosting condiviso. Se gestisci l'infrastruttura, considera anche la combinazione con Supabase per features aggiuntive come commenti real-time o autenticazione utenti sul frontend.
Il vantaggio economico è reale: il frontend su Vercel è gratuito fino a 100GB di banda, WordPress rimane sul tuo hosting attuale. Come ho analizzato nello stack zero-cost per SaaS, questa architettura permette di scalare senza costi aggiuntivi fino a traffico molto elevato.
FAQ e Domande Frequenti
Devo installare plugin speciali su WordPress per usarlo headless?
No, la REST API di WordPress è attiva per default dalla versione 4.7. Non ti serve nessun plugin per le funzionalità base: post, pagine, media, categorie, tag sono tutti disponibili immediatamente via /wp-json/wp/v2/. Se vuoi GraphQL invece della REST API, installi WPGraphQL. Se hai bisogno di campi personalizzati ACF nel frontend, installa ACF to REST API o abilita la REST API direttamente nelle impostazioni di ACF Pro. Per i webhook, puoi usare WP Webhooks (plugin free) oppure codice custom come mostrato in questa guida. Il plugin Yoast SEO espone anche i suoi metadati via REST API con il plugin aggiuntivo Yoast SEO: REST API, utile se vuoi sincronizzare i meta tag SEO con il frontend Next.js.
GraphQL o REST API: quale scegliere per WordPress headless?
Per la maggior parte dei progetti, la REST API basta e avanza. È built-in, ben documentata, e con _embed riduci drasticamente le richieste HTTP. WPGraphQL ha senso in scenari specifici: contenuti con molte relazioni annidate (es. post → autore → avatar → categorie → articoli correlati), oppure quando hai una app mobile e un frontend web che consumano gli stessi dati e vuoi che ognuno richieda esattamente i campi che usa (niente over-fetching). La curva di apprendimento di GraphQL è più ripida, la configurazione più complessa. Per un blog o sito editoriale standard, REST API + TypeScript types è la scelta più pragmatica nel 2026.
Come gestire le preview dei draft prima della pubblicazione?
Next.js ha un sistema nativo chiamato Draft Mode (precedentemente Preview Mode). Quando un editor clicca “Preview” in WordPress, viene reindirizzato a un URL speciale del frontend che attiva la modalità draft, bypassa la cache e mostra il contenuto non ancora pubblicato. Sul lato WordPress, crei un URL di preview personalizzato nelle impostazioni del permalink o via plugin. Sul lato Next.js, crei una route /api/draft che valida un secret token, attiva il Draft Mode e reindirizza alla pagina. Nelle Server Component, controlli draftMode().isEnabled e usi credenziali authenticate per la fetch, in modo da vedere anche i post in stato draft. Questa è una feature avanzata ma molto apprezzata dai clienti editoriali.
WordPress headless è adatto per l'e-commerce?
Con WooCommerce come backend headless e Next.js come frontend, la combinazione funziona ma richiede più lavoro rispetto a un blog. WooCommerce espone prodotti, ordini, carrelli e checkout via REST API, ma la gestione del carrello lato client è complessa — tradizionalmente dipende da cookie di sessione PHP. Soluzioni: usa CartFlows o un servizio headless come Medusa.js per il checkout, oppure affidati a Stripe per i pagamenti diretti. Per e-commerce seri nel 2026, valuta se la complessità headless vale il guadagno in performance rispetto a soluzioni native come Shopify Hydrogen. Per progetti più semplici o clienti già su WooCommerce, l'approccio headless può comunque dare ottimi risultati — ti rimando alla guida su Stripe per developer per integrare i pagamenti sul frontend Next.js.
Conclusione
WordPress headless con Next.js è nel 2026 la scelta più sensata per chi vuole il meglio di entrambi i mondi: la maturità e familiarità di WordPress per la gestione dei contenuti, e la potenza di React e Next.js per il frontend. La curva di setup è più ripida rispetto a un tema WordPress classico, ma i benefici in termini di performance, DX e scalabilità sono concreti e misurabili.
Il pattern che abbiamo visto — REST API + TypeScript types + ISR + webhook revalidation — è collaudato in produzione su decine di progetti. Se gestisci blog editoriali, siti aziendali o portafogli per clienti, questa architettura ti permette di offrire un prodotto moderno senza stravolgere il workflow dei tuoi clienti. Il passo successivo è aggiungere l'autenticazione con NextAuth.js per le aree riservate, o esplorare l'integrazione con sistemi AI per la generazione automatica di contenuti.
Suggerimenti e Risorse
🔧 Pro tip: Usa sempre
_embednei tuoi endpoint REST API per recuperare featured image e categorie in una sola richiesta invece di fare chiamate separate. Riduce il numero di fetch da 3-4 a 1 sola.
💡 Consiglio: Per il content sanitization — il tuo Next.js renderizza l'HTML di WordPress con
dangerouslySetInnerHTML. Installa isomorphic-dompurify e sanitizza sempre il contenuto prima di renderizzarlo, specialmente se hai autori multipli o accettate contenuti da fonti esterne.
🎯 Strategia: Proponi questa architettura ai clienti che già hanno WordPress e si lamentano di lentezza o limitazioni del tema. Il frontend Next.js diventa un upgrade invisibile: loro continuano a usare lo stesso pannello, tu migliori drasticamente le performance. È uno degli argomenti più forti per alzare le tariffe da freelance proponendo valore concreto e misurabile.

