La plupart des tutoriels Next.js pour blog supposent que vous avez déjà choisi un CMS. Celui-ci montre le câblage à partir de zéro. Vous verrez les fichiers exacts, les appels fetch exacts et les étapes de déploiement exactes qui vous mènent d'un projet Next.js vide à un blog en ligne qui lit son contenu depuis Kamaan. L'objectif est une route /blog fonctionnelle, des pages /blog/[slug] fonctionnelles, des routes multilingues et un cache ISR, en moins de soixante minutes.
En bref
- Une intégration Next.js + Kamaan demande environ six fichiers : un helper fetch, la configuration d'images dans
next.config.js, une page liste, une page article unique, une configuration ISR et une variante multilingue. - L'API REST de Kamaan renvoie du JSON avec
id,title,slug,content,excerpt,featured_image_urletlanguage. Pas de GraphQL, pas de SDK, pas d'en-tête d'authentification pour le contenu publié. - App Router est la forme recommandée parce que
generateStaticParamsetrevalidatevous donnent l'ISR sans tuyauterie supplémentaire. - Lister les domaines d'images dans
next.config.jsest la cause la plus fréquente des builds qui cassent après que le premier fetch fonctionne. - Les blogs multilingues n'ont besoin que d'un segment de route supplémentaire (
[lang]) et d'un paramètre de requête sur l'appel fetch.
Pourquoi ce guide existe
Les tutoriels Next.js pour blog actuels commencent en général par "d'abord, installez Contentful" ou "d'abord, installez Sanity". Ils sautent la partie où vous comparez les options CMS, puis sautent la partie où vous gérez la couche de routage. Cela laisse un trou : les développeurs qui connaissent déjà Next.js mais veulent un backend de contenu qui n'exige pas de migration de schéma, de playground GraphQL ni de dépôt de configuration.
Kamaan occupe ce trou. C'est un headless CMS avec REST API Delivery, Auto-Multilingual Delivery intégré, et un endpoint MCP Server pour que Claude ou ChatGPT puissent écrire directement dans votre blog. Côté Next.js, l'intégration a la même forme que Contentful ou Sanity : récupérer du JSON, rendre du markdown, configurer l'ISR. La différence est ce que vous évitez : pas d'installation de SDK, pas de rotation de token pour la lecture, pas de fichier de schéma. Vous lisez les articles par ID de site et slug.
Si vous hésitez encore entre les CMS, lisez d'abord Qu'est-ce qu'un headless CMS et Meilleur headless CMS pour startups. Si vous avez déjà choisi Kamaan, le reste de ce guide est pour vous.
Voici la carte de setup en un coup d'oeil avant de consacrer du temps au câblage.

Benchmark du temps de mise en place
Côté Kamaan, publier votre premier article prend environ 14 minutes (compte, site, premier article, langues en ligne). Côté Next.js, câbler les six fichiers ci-dessous représente à peu près 40 à 60 minutes pour un développeur qui le fait pour la première fois. Le détail :
| Étape | Ce qui tourne | Où ça tourne | Temps estimé |
|---|---|---|---|
| 1. Helper fetch | lib/kamaan.ts |
Serveur | 5 min |
| 2. Variables d'environnement + config images | .env.local, next.config.js |
Temps de build | 5 min |
| 3. Vue liste | app/blog/page.tsx |
Server Component | 10 min |
| 4. Article unique | app/blog/[slug]/page.tsx + generateStaticParams |
Build + revalidate | 15 min |
| 5. Configuration ISR | export const revalidate = 60 |
Runtime Edge ou Node | 5 min |
| 6. Route multilingue | app/[lang]/blog/[slug]/page.tsx |
Server Component | 20 min |
Les quinze minutes de l'étape 4 incluent le rendu markdown et la gestion de l'image à la une, là où la plupart des projets s'arrêtent. Si vous sautez le multilingue, vous livrez en quarante minutes. Le travail sur le dashboard Kamaan lui-même (créer le site, rédiger le premier article, voir arriver les traductions) est le benchmark d'environ 14 min cité sur kamaan.io.
Étape 1 : helper fetch
Créez lib/kamaan.ts. Ce fichier est le seul endroit qui connaît la forme de l'endpoint REST de Kamaan. Tous les autres fichiers importent depuis ici.
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 = "fr"): 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 = "fr"): 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;
}
Deux points à remarquer. Premièrement, l'indication next: { revalidate: 60 } dit à Next.js de cacher la réponse pendant 60 secondes. C'est votre couche ISR gratuite, sans configuration séparée. Deuxièmement, pas besoin d'en-tête d'authentification pour le contenu publié. Si vous voulez récupérer des brouillons, vous ajoutez un en-tête Authorization: Bearer <token>. Le token vit dans .env.local, jamais dans NEXT_PUBLIC_*.
Étape 2 : variables d'environnement et domaines d'images
Créez .env.local :
NEXT_PUBLIC_KAMAAN_SITE_ID=6a15f18577639d2385209d06
Puis ouvrez next.config.js et ajoutez le domaine d'images Kamaan à images.remotePatterns. C'est l'échec de build le plus fréquent. L'erreur indique 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;
Redémarrez le serveur de dev après avoir édité next.config.js. Le hot reload ne détecte pas les changements de configuration d'images.
Étape 3 : la vue liste
app/blog/page.tsx lit tous les articles publiés et rend des cartes. Server Component, aucun état client requis.
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("fr");
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 au niveau de la page double l'indication revalidate au niveau du fetch. L'un ou l'autre seul fonctionne. Les deux ensemble vont aussi, et rendent l'intention évidente pour quiconque lit le fichier plus tard.
Étape 4 : la page article unique
app/blog/[slug]/page.tsx lit un article par slug, renvoie 404 si absent et rend du markdown. C'est le fichier où la plupart des projets s'arrêtent, parce que trois choses doivent s'aligner : le paramètre de route dynamique, la génération de static params et le moteur de rendu 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("fr");
return articles.map((a) => ({ slug: a.slug }));
}
export default async function ArticlePage({ params }: { params: { slug: string } }) {
const article = await getArticleBySlug(params.slug, "fr");
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 tourne au moment du build. Il tire tous les slugs publiés pour que Next.js puisse pré-rendre chaque article en HTML statique. Les nouveaux articles publiés après le déploiement fonctionnent quand même grâce à l'ISR : la première requête tape l'API, cache le résultat 60 secondes, et sert le HTML caché à tous les autres.
Installez les dépendances markdown :
npm i remark remark-html
Si vous préférez MDX, remplacez remark-html par @next/mdx et ajustez la forme du composant. La plupart des blogs n'ont pas besoin de MDX. Markdown simple plus la classe Tailwind prose couvre 90% des cas.
Étape 5 : ISR, invalidation de cache et le piège
revalidate = 60 veut dire qu'un changement d'article apparaît dans les 60 secondes suivant la prochaine requête. Pour la plupart des blogs, ça suffit. Si vous voulez une publication instantanée, deux pistes :
- Mettez
revalidatesur un intervalle plus long et déclenchez la revalidation on-demand depuis un webhook Kamaan. Kamaan peut faire un POST surhttps://votredomaine.com/api/revalidate?secret=xxx&path=/blog/[slug]chaque fois qu'un article est publié. Votre route handler appellerevalidatePath()et renvoie 200. - Utilisez
revalidate = 0(SSR à chaque requête). Plus lent pour les utilisateurs, pas de couche de cache, mais toujours frais.
La plupart des équipes prennent l'option 1 une fois le trafic devenu sérieux. Jusque-là, revalidate = 60 fait l'affaire.
Avertissement honnête : le Data Cache de Vercel et le Full Route Cache de Next.js ne s'invalident pas toujours ensemble. Si vous changez un article et que la nouvelle version apparaît sur /blog/[slug] mais que l'ancien excerpt reste sur /blog, c'est le Full Route Cache de la page index qui retarde. Déclenchez revalidate sur /blog et sur /blog/[slug] depuis le même webhook.
Étape 6 : routes multilingues
C'est là que Kamaan vous fait gagner le plus de temps. La diffusion multilingue automatique de Kamaan veut dire qu'un POST crée un article en anglais, et que les traductions vers l'espagnol, l'allemand, le français et l'italien sont disponibles sur le même slug sous un paramètre de requête language. Votre côté Next.js gère le segment de route.
Déplacez vos pages de blog sous app/[lang]/blog/. Ajoutez la validation de langue dans la 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();
// ... rendre comme avant
}
Les slugs restent en anglais dans toutes les langues. C'est un choix SEO délibéré : ça garde votre structure d'URL cohérente, simplifie l'analyse et correspond à ce que fait chaque blog SaaS majeur (Stripe, Notion, Linear). Si vous avez besoin de slugs traduits, stockez-les dans une carte de slugs et résolvez-les en middleware. La plupart des équipes ne le font pas.
Voici les six étapes de setup mappées sur une seule image que vous pouvez garder ouverte pendant que vous construisez.

Scénarios concrets
Scénario 1 : blog SaaS B2B, 40 articles, anglais plus espagnol
Une équipe de deux personnes a migré d'un blog markdown-dans-le-repo vers Kamaan parce que chaque mise à jour de contenu exigeait un déploiement. La mise en place a pris 50 minutes à l'ingénieur senior avec ce guide. La version espagnole des 40 articles a été générée automatiquement par l'Auto-Multilingual Delivery de Kamaan. Temps total traductions incluses : 70 minutes. Le déploiement a été instantané parce que l'app Next.js existante était déjà sur Vercel.
Scénario 2 : fondatrice solo, pas encore d'articles, multilingue dès le premier jour
Une développeuse solo voulait l'anglais plus quatre langues dès le lancement. Elle a complètement sauté la phase markdown-dans-le-repo. La mise en place a été de 45 minutes côté Next.js et 15 minutes pour le premier article (écrit dans Kamaan, auto-traduit, publié). L'endpoint MCP Server lui a permis d'écrire les articles suivants en parlant à Claude, qui postait directement dans Kamaan.
Déploiement sur Vercel
Trois étapes :
- Poussez le repo sur GitHub.
- Importez dans Vercel. Vercel détecte Next.js automatiquement.
- Ajoutez
NEXT_PUBLIC_KAMAAN_SITE_IDaux variables d'environnement de Vercel. Redéployez.
C'est tout le déploiement. L'ISR fonctionne sur Vercel out of the box. Le build exécute generateStaticParams, pré-rend chaque article, et le runtime gère les nouveaux articles via l'indication revalidate.
Si vous déployez ailleurs (Netlify, Cloudflare Pages, votre propre serveur Node), la sémantique ISR varie. Cloudflare Pages avec @cloudflare/next-on-pages fonctionne, mais utilise KV comme couche de cache. Les serveurs Node auto-hébergés ont besoin du mode standalone output et d'un système de fichiers persistant pour que le cache survive aux redémarrages. Vercel est le chemin le plus simple.
FAQ
Combien de temps prend vraiment une configuration Next.js plus Kamaan ?
Quarante à soixante minutes pour un développeur Next.js expérimenté qui suit ce guide. Le plus gros consommateur de temps est le rendu markdown, qui ajoute environ dix minutes si vous ne l'avez pas déjà fait. Le multilingue en ajoute encore vingt.
Ai-je besoin de TypeScript ?
Non. Chaque exemple ici fonctionne de la même façon en JavaScript pur. Le type Article est de la documentation, pas une exigence d'exécution. La plupart des équipes qui utilisent Next.js utilisent déjà TypeScript, donc les exemples le supposent.
Puis-je utiliser Pages Router au lieu d'App Router ?
Oui. Le helper fetch reste identique. Remplacez app/blog/page.tsx par pages/blog/index.tsx, utilisez getStaticProps pour l'index, et getStaticParams plus getStaticProps avec revalidate: 60 pour l'article unique. App Router est recommandé pour les nouveaux projets parce que l'ergonomie ISR y est plus propre.
Qu'en est-il du RSS, du sitemap et de l'Open Graph ?
Kamaan génère automatiquement le RSS sur https://api.kamaan.io/v1/sites/{site_id}/rss. Le sitemap est sur /sites/{site_id}/sitemap.xml. Les deux se mettent à jour à chaque publication. Pour l'Open Graph, définissez le titre, la description et featured_image_url dans les champs SEO de Kamaan, puis lisez-les dans votre fonction generateMetadata Next.js.
Comment prévisualiser des brouillons ?
Passez un en-tête Authorization: Bearer <token> sur votre appel fetch et ajoutez &status=draft à l'URL. La plupart des équipes protègent cela derrière une route /blog/preview/[slug] avec un cookie secret. L'API Kamaan supporte les tokens de preview directement, donc vous n'avez pas à en construire un vous-même.
Que se passe-t-il si je veux ajouter un CMS comme Contentful ou Sanity plus tard ?
Le helper fetch est le seul fichier qui change. Vous échangez l'URL de l'API et le parsing JSON, vous gardez les fichiers de page. C'est la valeur de traiter le CMS comme une source JSON plutôt que comme une dépendance SDK.
Cela fonctionne-t-il avec React Server Components ?
Oui. Chaque exemple ci-dessus est un Server Component. Pas besoin de directive "use client" à moins d'ajouter des widgets interactifs comme une barre de recherche.
Puis-je faire tourner cela sans Vercel ?
Oui, avec des nuances. Cloudflare Pages, Netlify, AWS Amplify et Node auto-hébergé supportent Next.js. L'ISR fonctionne out of the box sur Vercel et Cloudflare. Sur Netlify, l'ISR exige leurs On-Demand Builders. Auto-hébergé exige le mode standalone output.
En lien sur Kamaan
- Comment ajouter un blog à votre SaaS
- Qu'est-ce qu'un headless CMS
- Meilleur headless CMS pour startups
- Comment construire un blog multilingue
Commencez à construire avec Kamaan
Un blog Next.js plus Kamaan, c'est six fichiers et une heure de travail. Inscrivez-vous sur Kamaan, créez un site, copiez l'ID du site et suivez ce guide. Si vous bloquez sur les domaines d'images ou le cache ISR, la doc couvre les deux avec des projets d'exemple que vous pouvez forker.

