Kamaankamaan

Integrazione Headless CMS Next.js: configurazione passo dopo passo da zero a blog in produzione

La maggior parte dei tutorial Next.js presume che abbiate un CMS. Questa guida mostra il cablaggio da zero: helper fetch, pagine App Router, generateStaticParams, ISR e route multilingua per Kamaan, in meno di sessanta m

Junaid Khalid
Junaid Khalid
29 maggio 2026 · 12 min read

La maggior parte dei tutorial Next.js per blog presume che abbiate già scelto un CMS. Questo mostra il cablaggio da zero. Vedrete i file esatti, le chiamate fetch esatte e i passi di deploy esatti che vi portano da un progetto Next.js vuoto a un blog in produzione che legge i contenuti da Kamaan. L'obiettivo è una route /blog funzionante, pagine /blog/[slug] funzionanti, route multilingua e cache ISR, in meno di sessanta minuti.

In breve

  • Un'integrazione Next.js + Kamaan richiede circa sei file: un helper di fetch, la configurazione immagini in next.config.js, una pagina lista, una pagina articolo singolo, una configurazione ISR e una variante multilingua.
  • La REST API di Kamaan restituisce JSON con id, title, slug, content, excerpt, featured_image_url e language. Niente GraphQL, niente SDK, niente header di autenticazione per i contenuti pubblicati.
  • App Router è la forma consigliata perché generateStaticParams e revalidate vi danno l'ISR senza tubature extra.
  • Indicare i domini delle immagini in next.config.js è il motivo più frequente per cui le build falliscono dopo che il primo fetch funziona.
  • I blog multilingua hanno bisogno solo di un segmento di route in più ([lang]) e di un parametro query nella chiamata fetch.

Perché esiste questa guida

I tutorial Next.js per blog di solito iniziano con "prima, installate Contentful" o "prima, installate Sanity". Saltano la parte in cui confrontate le opzioni di CMS, poi saltano la parte in cui gestite il layer di routing. Questo lascia un buco: sviluppatori che già conoscono Next.js ma vogliono un backend dei contenuti che non richieda una migrazione di schema, un playground GraphQL o un repository di configurazione.

Kamaan riempie quel buco. È un headless CMS con REST API Delivery, Auto-Multilingual Delivery integrata e un endpoint MCP Server che permette a Claude o ChatGPT di scrivere direttamente nel vostro blog. Lato Next.js, l'integrazione ha la stessa forma di Contentful o Sanity: fare fetch del JSON, renderizzare markdown, configurare l'ISR. La differenza è cosa vi risparmiate: niente installazione SDK, niente rotazione di token per la lettura, niente file di schema. Leggete gli articoli per ID del sito e slug.

Se state ancora decidendo quale CMS usare, leggete prima Cos'è un headless CMS e Miglior headless CMS per startup. Se avete già scelto Kamaan, il resto della guida fa per voi.

Ecco la card di setup a colpo d'occhio prima di investire tempo nel cablaggio.

Card Next.js più headless CMS con configurazione App Router, ISR e route multilingua

Benchmark del tempo di setup

Lato Kamaan, pubblicare il vostro primo post richiede circa 14 minuti (account, sito, primo articolo, lingue in produzione). Lato Next.js, cablare i sei file qui sotto sono all'incirca 40-60 minuti per uno sviluppatore che lo fa per la prima volta. Il dettaglio:

Passo Cosa gira Dove gira Tempo stimato
1. Helper fetch lib/kamaan.ts Server 5 min
2. Variabili d'ambiente + configurazione immagini .env.local, next.config.js Tempo di build 5 min
3. Vista lista app/blog/page.tsx Server Component 10 min
4. Post singolo app/blog/[slug]/page.tsx + generateStaticParams Build + revalidate 15 min
5. Configurazione ISR export const revalidate = 60 Runtime Edge o Node 5 min
6. Route multilingua app/[lang]/blog/[slug]/page.tsx Server Component 20 min

I quindici minuti del passo 4 includono il rendering del markdown e la gestione dell'immagine in evidenza, dove la maggior parte dei progetti si blocca. Se saltate la parte multilingua, spedite in quaranta minuti. Il lavoro sulla dashboard di Kamaan in sé (creare il sito, redigere il primo articolo, vedere arrivare le traduzioni) è il benchmark di circa 14 min citato su kamaan.io.

Passo 1: helper fetch

Create lib/kamaan.ts. Questo file è l'unico posto che conosce la forma dell'endpoint REST di Kamaan. Tutti gli altri file importano da qui.

const BASE = "https://api.kamaan.io/v1";
const SITE_ID = process.env.NEXT_PUBLIC_KAMAAN_SITE_ID;

export type Article = {
  id: string;
  title: string;
  slug: string;
  content: string;
  excerpt: string;
  featured_image_url: string | null;
  language: string;
  published_at: string;
};

export async function listArticles(lang = "it"): Promise<Article[]> {
  const res = await fetch(
    `${BASE}/sites/${SITE_ID}/articles?language=${lang}&status=published`,
    { next: { revalidate: 60 } }
  );
  if (!res.ok) throw new Error(`Kamaan list failed: ${res.status}`);
  const data = await res.json();
  return data.articles;
}

export async function getArticleBySlug(slug: string, lang = "it"): Promise<Article | null> {
  const res = await fetch(
    `${BASE}/sites/${SITE_ID}/articles/by-slug/${slug}?language=${lang}`,
    { next: { revalidate: 60 } }
  );
  if (res.status === 404) return null;
  if (!res.ok) throw new Error(`Kamaan article failed: ${res.status}`);
  const data = await res.json();
  return data.article;
}

Due cose da notare. Primo, l'hint next: { revalidate: 60 } dice a Next.js di cacheare la risposta per 60 secondi. Questo è il vostro layer ISR gratis, senza configurazione separata. Secondo, non serve un header di autenticazione per i contenuti pubblicati. Se volete tirare le bozze, aggiungete un header Authorization: Bearer <token>. Il token vive in .env.local, mai in NEXT_PUBLIC_*.

Passo 2: variabili d'ambiente e domini immagini

Create .env.local:

NEXT_PUBLIC_KAMAAN_SITE_ID=6a15f18577639d2385209d06

Poi aprite next.config.js e aggiungete il dominio immagini di Kamaan a images.remotePatterns. Questo è il fallimento di build più comune. L'errore recita Invalid src prop on next/image, hostname "cdn.kamaan.io" is not configured under images in your next.config.js.

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      { protocol: "https", hostname: "cdn.kamaan.io" },
      { protocol: "https", hostname: "images.kamaan.io" },
    ],
  },
};

module.exports = nextConfig;

Riavviate il dev server dopo aver modificato next.config.js. L'hot reload non rileva i cambiamenti della configurazione immagini.

Passo 3: la vista lista

app/blog/page.tsx legge tutti gli articoli pubblicati e renderizza card. Server Component, nessuno stato client necessario.

import Link from "next/link";
import Image from "next/image";
import { listArticles } from "@/lib/kamaan";

export const revalidate = 60;

export default async function BlogIndex() {
  const articles = await listArticles("it");
  return (
    <main className="mx-auto max-w-3xl px-6 py-12">
      <h1 className="text-4xl font-bold mb-8">Blog</h1>
      <ul className="space-y-8">
        {articles.map((a) => (
          <li key={a.id}>
            <Link href={`/blog/${a.slug}`}>
              {a.featured_image_url && (
                <Image src={a.featured_image_url} alt={a.title} width={800} height={420} />
              )}
              <h2 className="text-2xl font-semibold mt-4">{a.title}</h2>
              <p className="text-gray-600 mt-2">{a.excerpt}</p>
            </Link>
          </li>
        ))}
      </ul>
    </main>
  );
}

export const revalidate = 60 a livello di pagina è cintura-e-bretelle insieme al revalidate a livello di fetch. Uno solo basta. Entrambi insieme va bene e rende l'intento chiaro a chi legge il file dopo.

Passo 4: la pagina articolo singolo

app/blog/[slug]/page.tsx legge un articolo per slug, restituisce 404 se manca e renderizza markdown. Questo è il file su cui la maggior parte dei progetti si blocca, perché tre cose devono allinearsi: il parametro di route dinamica, la generazione di static params e il renderer del markdown.

import { notFound } from "next/navigation";
import Image from "next/image";
import { remark } from "remark";
import html from "remark-html";
import { getArticleBySlug, listArticles } from "@/lib/kamaan";

export const revalidate = 60;

export async function generateStaticParams() {
  const articles = await listArticles("it");
  return articles.map((a) => ({ slug: a.slug }));
}

export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const article = await getArticleBySlug(params.slug, "it");
  if (!article) notFound();
  const processed = await remark().use(html).process(article.content);
  const contentHtml = processed.toString();
  return (
    <article className="mx-auto max-w-3xl px-6 py-12">
      <h1 className="text-4xl font-bold">{article.title}</h1>
      {article.featured_image_url && (
        <Image src={article.featured_image_url} alt={article.title} width={1200} height={630} className="my-6 rounded-lg" />
      )}
      <div className="prose mt-8" dangerouslySetInnerHTML={{ __html: contentHtml }} />
    </article>
  );
}

generateStaticParams gira a build time. Tira tutti gli slug pubblicati così Next.js può pre-renderizzare ogni post come HTML statico. I nuovi articoli pubblicati dopo il deploy funzionano comunque grazie all'ISR: la prima richiesta colpisce l'API, cacha il risultato per 60 secondi e serve l'HTML cachato a tutti gli altri.

Installate le dipendenze markdown:

npm i remark remark-html

Se preferite MDX, scambiate remark-html con @next/mdx e adattate la forma del componente. La maggior parte dei blog non ha bisogno di MDX. Markdown semplice più la classe Tailwind prose copre il 90% dei casi.

Passo 5: ISR, invalidazione cache e l'inghippo

revalidate = 60 significa che una modifica di un post compare entro 60 secondi dalla prossima richiesta. Per la maggior parte dei blog va bene. Se volete pubblicazione istantanea, due strade:

  1. Mettete revalidate su un intervallo più lungo e fate scattare la revalidation on-demand da un webhook di Kamaan. Kamaan può fare POST a https://vostrodominio.com/api/revalidate?secret=xxx&path=/blog/[slug] ogni volta che pubblicate un articolo. Il vostro route handler chiama revalidatePath() e restituisce 200.
  2. Usate revalidate = 0 (SSR su ogni richiesta). Più lento per gli utenti, niente layer di cache, ma sempre fresco.

La maggior parte dei team sceglie l'opzione 1 quando il traffico cresce. Fino ad allora, revalidate = 60 va bene.

Avvertenza sincera: il Data Cache di Vercel e il Full Route Cache di Next.js non si invalidano sempre insieme. Se cambiate un articolo e la nuova versione compare su /blog/[slug] ma il vecchio excerpt resta su /blog, è il Full Route Cache della pagina indice che ritarda. Fate scattare revalidate sia su /blog che su /blog/[slug] dallo stesso webhook.

Passo 6: route multilingua

Qui Kamaan vi fa risparmiare più tempo. La distribuzione multilingue automatica di Kamaan significa che un POST crea un articolo in inglese e le traduzioni in spagnolo, tedesco, francese e italiano sono disponibili sullo stesso slug sotto un parametro di query language. Il lato Next.js gestisce il segmento di route.

Spostate le pagine del blog sotto app/[lang]/blog/. Aggiungete la validazione della lingua nella route:

const SUPPORTED = ["en", "es", "de", "fr", "it"] as const;
type Lang = (typeof SUPPORTED)[number];

export async function generateStaticParams() {
  const params = [];
  for (const lang of SUPPORTED) {
    const articles = await listArticles(lang);
    for (const a of articles) {
      params.push({ lang, slug: a.slug });
    }
  }
  return params;
}

export default async function ArticlePage({ params }: { params: { lang: Lang; slug: string } }) {
  if (!SUPPORTED.includes(params.lang)) notFound();
  const article = await getArticleBySlug(params.slug, params.lang);
  if (!article) notFound();
  // ... renderizzate come prima
}

Gli slug restano in inglese in tutte le lingue. È una scelta SEO deliberata: tiene la struttura degli URL coerente, semplifica l'analytics e combacia con quello che fa ogni blog SaaS importante (Stripe, Notion, Linear). Se vi servono slug tradotti, conservateli in una mappa di slug e risolveteli in middleware. La maggior parte dei team non lo fa.

Ecco i sei passi di setup mappati su un'unica immagine che potete tenere aperta mentre costruite.

Flusso di integrazione Next.js più Kamaan in sei passi: helper fetch, config env, vista lista, articolo singolo, ISR, route multilingua

Scenari reali

Scenario 1: blog SaaS B2B, 40 articoli, inglese più spagnolo

Un team di due persone è migrato da un blog markdown-nel-repo a Kamaan perché ogni aggiornamento dei contenuti richiedeva un deploy. Il setup ha richiesto 50 minuti all'ingegnere senior seguendo questa guida. La versione spagnola di tutti i 40 articoli è stata generata automaticamente da Auto-Multilingual Delivery di Kamaan. Tempo totale incluse le traduzioni: 70 minuti. Il deploy è stato istantaneo perché l'app Next.js esistente era già su Vercel.

Scenario 2: founder solo, ancora nessun articolo, multilingua dal primo giorno

Una sviluppatrice solo voleva inglese più quattro lingue dal lancio. Ha saltato del tutto la fase markdown-nel-repo. Il setup sono stati 45 minuti lato Next.js e 15 minuti per il primo articolo (scritto in Kamaan, auto-tradotto, pubblicato). L'endpoint MCP Server le ha permesso di scrivere gli articoli successivi parlando con Claude, che postava direttamente in Kamaan.

Deploy su Vercel

Tre passi:

  1. Pushate il repository su GitHub.
  2. Importatelo in Vercel. Vercel riconosce Next.js automaticamente.
  3. Aggiungete NEXT_PUBLIC_KAMAAN_SITE_ID alle variabili d'ambiente di Vercel. Rieffettuate il deploy.

Questo è tutto il deploy. L'ISR funziona su Vercel out of the box. Il build esegue generateStaticParams, pre-renderizza ogni articolo e il runtime gestisce i nuovi articoli tramite l'hint revalidate.

Se deployate altrove (Netlify, Cloudflare Pages, server Node vostro), la semantica ISR varia. Cloudflare Pages con @cloudflare/next-on-pages funziona ma usa KV come layer di cache. I server Node auto-ospitati hanno bisogno della modalità standalone output e di un filesystem persistente perché la cache sopravviva ai riavvii. Vercel è la strada con meno attrito.

FAQ

Quanto tempo richiede davvero un setup Next.js più Kamaan?

Quaranta-sessanta minuti per uno sviluppatore Next.js esperto che segue questa guida. Il maggior consumatore di tempo è il rendering del markdown, che aggiunge circa dieci minuti se non lo avete mai fatto. Il multilingua ne aggiunge altri venti.

Mi serve TypeScript?

No. Ogni esempio qui funziona allo stesso modo in JavaScript puro. Il tipo Article è documentazione, non un requisito a runtime. La maggior parte dei team che usa Next.js usa già TypeScript, perciò gli esempi lo assumono.

Posso usare Pages Router invece di App Router?

Sì. L'helper fetch resta identico. Sostituite app/blog/page.tsx con pages/blog/index.tsx, usate getStaticProps per l'indice e getStaticParams più getStaticProps con revalidate: 60 per l'articolo singolo. App Router è consigliato per i nuovi progetti perché l'ergonomia ISR è più pulita.

Che dire di RSS, sitemap e Open Graph?

Kamaan genera automaticamente RSS su https://api.kamaan.io/v1/sites/{site_id}/rss. La sitemap è su /sites/{site_id}/sitemap.xml. Entrambe si aggiornano ogni volta che pubblicate. Per l'Open Graph, impostate titolo, descrizione e featured_image_url nei campi SEO di Kamaan, poi leggeteli nella vostra funzione generateMetadata di Next.js.

Come faccio l'anteprima delle bozze?

Passate un header Authorization: Bearer <token> alla vostra chiamata fetch e aggiungete &status=draft all'URL. La maggior parte dei team protegge questo dietro una route /blog/preview/[slug] con un cookie segreto. L'API di Kamaan supporta i token di preview direttamente, così non dovete costruirne uno vostro.

Cosa succede se voglio aggiungere un CMS come Contentful o Sanity più avanti?

L'helper fetch è l'unico file che cambia. Scambiate l'URL dell'API e il parsing del JSON, tenete uguali i file di pagina. Questo è il valore di trattare il CMS come una fonte JSON invece che come una dipendenza SDK.

Funziona con React Server Components?

Sì. Ogni esempio sopra è un server component. Nessuna direttiva "use client" necessaria, a meno che non aggiungiate widget interattivi come una barra di ricerca.

Posso farlo girare senza Vercel?

Sì, con avvertenze. Cloudflare Pages, Netlify, AWS Amplify e Node auto-ospitato supportano Next.js. L'ISR funziona out of the box su Vercel e Cloudflare. Su Netlify, l'ISR richiede i loro On-Demand Builders. Auto-ospitato richiede la modalità standalone output.

Collegati su Kamaan

Iniziate a costruire con Kamaan

Un blog Next.js più Kamaan sono sei file e un'ora di lavoro. Iscrivetevi a Kamaan, create un sito, copiate l'ID del sito e seguite questa guida. Se vi bloccate sui domini delle immagini o sulla cache ISR, la doc copre entrambi con progetti di esempio che potete forkare.

Frequently asked

FAQ · 8 ITEMS
Quanto tempo richiede davvero un setup Next.js più Kamaan?

Quaranta-sessanta minuti per uno sviluppatore Next.js esperto che segue questa guida. Il maggior consumatore di tempo è il rendering del markdown, che aggiunge circa dieci minuti se non lo avete mai fatto. Il multilingua ne aggiunge altri venti.

Mi serve TypeScript?

No. Ogni esempio qui funziona allo stesso modo in JavaScript puro. Il tipo `Article` è documentazione, non un requisito a runtime. La maggior parte dei team che usa Next.js usa già TypeScript, perciò gli esempi lo assumono.

Posso usare Pages Router invece di App Router?

Sì. L'helper fetch resta identico. Sostituite `app/blog/page.tsx` con `pages/blog/index.tsx`, usate `getStaticProps` per l'indice e `getStaticParams` più `getStaticProps` con `revalidate: 60` per l'articolo singolo. App Router è consigliato per i nuovi progetti perché l'ergonomia ISR è più pulita.

Che dire di RSS, sitemap e Open Graph?

Kamaan genera automaticamente RSS su `https://api.kamaan.io/v1/sites/{site_id}/rss`. La sitemap è su `/sites/{site_id}/sitemap.xml`. Entrambe si aggiornano ogni volta che pubblicate. Per l'Open Graph, impostate titolo, descrizione e `featured_image_url` nei campi SEO di Kamaan, poi leggeteli nella vostra funzione `generateMetadata` di Next.js.

Come faccio l'anteprima delle bozze?

Passate un header `Authorization: Bearer <token>` alla vostra chiamata fetch e aggiungete `&status=draft` all'URL. La maggior parte dei team protegge questo dietro una route `/blog/preview/[slug]` con un cookie segreto. L'API di Kamaan supporta i token di preview direttamente, così non dovete costruirne uno vostro.

Cosa succede se voglio aggiungere un CMS come Contentful o Sanity più avanti?

L'helper fetch è l'unico file che cambia. Scambiate l'URL dell'API e il parsing del JSON, tenete uguali i file di pagina. Questo è il valore di trattare il CMS come una fonte JSON invece che come una dipendenza SDK.

Funziona con React Server Components?

Sì. Ogni esempio sopra è un server component. Nessuna direttiva `"use client"` necessaria, a meno che non aggiungiate widget interattivi come una barra di ricerca.

Posso farlo girare senza Vercel?

Sì, con avvertenze. Cloudflare Pages, Netlify, AWS Amplify e Node auto-ospitato supportano Next.js. L'ISR funziona out of the box su Vercel e Cloudflare. Su Netlify, l'ISR richiede i loro On-Demand Builders. Auto-ospitato richiede la modalità standalone output.

Junaid Khalid
Written by
Junaid Khalid

Junaid Khalid is the founder of Kamaan, a headless blog CMS that auto-publishes in five languages and lets you manage every product blog from one dashboard.