Kamaankamaan

Integración de Headless CMS con Next.js: Configuración Paso a Paso de Cero a Blog en Vivo

La mayoría de tutoriales Next.js asumen que ya elegiste un CMS. Esta guía muestra el cableado desde cero: helper fetch, páginas App Router, generateStaticParams, ISR y rutas multilingües para Kamaan, en menos de sesenta

Junaid Khalid
Junaid Khalid
29 de mayo de 2026 · 13 min read

La mayoría de los tutoriales de configuración de blogs en Next.js asumen que ya elegiste un CMS. Este muestra el cableado desde cero. Verás los archivos exactos, las llamadas fetch exactas y los pasos de despliegue exactos que te llevan de un proyecto vacío de Next.js a un blog en vivo que lee contenido desde Kamaan. La meta es una ruta /blog funcional, páginas /blog/[slug] funcionales, rutas multilingües y caché ISR, en menos de sesenta minutos.

Puntos clave

  • Una integración Next.js + Kamaan necesita unos seis archivos: un helper de fetch, la configuración de imágenes en next.config.js, una página de listado, una página de post individual, una configuración ISR y una variante multilingüe.
  • La REST API de Kamaan devuelve JSON con id, title, slug, content, excerpt, featured_image_url y language. Sin GraphQL, sin SDK, sin cabeceras de autenticación para contenido publicado.
  • App Router es la forma recomendada porque generateStaticParams y revalidate te dan ISR sin plomería extra.
  • Listar dominios de imágenes en next.config.js es la razón más común por la que los builds fallan después de que el primer fetch funciona.
  • Los blogs multilingües solo necesitan un segmento de ruta extra ([lang]) y un parámetro de consulta en la llamada fetch.

Por qué existe esta guía

Los tutoriales actuales de blogs Next.js suelen empezar con "primero, instala Contentful" o "primero, instala Sanity". Saltan la parte donde comparas opciones de CMS y luego saltan la parte donde manejas la capa de rutas. Eso deja un hueco: desarrolladores que ya conocen Next.js pero quieren un backend de contenido que no necesite una migración de esquema, un playground de GraphQL ni un repositorio de configuración.

Kamaan ocupa ese hueco. Es un headless CMS con REST API Delivery, Auto-Multilingual Delivery incorporado, y un endpoint MCP Server para que Claude o ChatGPT escriban directamente en tu blog. Del lado de Next.js, la integración tiene la misma forma que Contentful o Sanity: hacer fetch del JSON, renderizar markdown, configurar ISR. La diferencia es lo que te saltas: nada de instalar SDK, nada de rotar tokens para lectura, nada de archivo de esquema. Lees artículos por ID de sitio y slug.

Si todavía estás decidiendo qué CMS usar, lee Qué es un headless CMS y Mejor headless CMS para startups primero. Si ya elegiste Kamaan, el resto de esta guía es para ti.

Aquí tienes la tarjeta de configuración de un vistazo antes de invertir tiempo en el cableado.

Tarjeta de Next.js más headless CMS mostrando configuración de App Router, ISR y rutas multilingües

Benchmark de tiempo de configuración

En el lado de Kamaan, publicar tu primer post toma unos 14 minutos (cuenta, sitio, primer artículo, idiomas en vivo). En el lado de Next.js, cablear los seis archivos siguientes son aproximadamente 40 a 60 minutos para un desarrollador que lo hace por primera vez. El desglose:

Paso Qué corre Dónde corre Tiempo estimado
1. Helper fetch lib/kamaan.ts Servidor 5 min
2. Variables de entorno + configuración de imágenes .env.local, next.config.js Tiempo de build 5 min
3. Vista de lista app/blog/page.tsx Server component 10 min
4. Post individual app/blog/[slug]/page.tsx + generateStaticParams Build + revalidate 15 min
5. Configuración ISR export const revalidate = 60 Edge o Node runtime 5 min
6. Ruta multilingüe app/[lang]/blog/[slug]/page.tsx Server component 20 min

Los quince minutos del paso 4 incluyen el renderizado de markdown y el manejo de la imagen destacada, que es donde se atasca la mayoría de los proyectos. Si te saltas la parte multilingüe, despachas en cuarenta minutos. El trabajo en el dashboard de Kamaan en sí (crear el sitio, redactar el primer artículo, ver llegar las traducciones) es el benchmark de ~14 min citado en kamaan.io.

Paso 1: Helper fetch

Crea lib/kamaan.ts. Este archivo es el único lugar que conoce la forma del endpoint REST de Kamaan. Todos los demás archivos importan desde aquí.

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 = "es"): 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 = "es"): 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;
}

Dos cosas a notar. Primero, el hint next: { revalidate: 60 } le dice a Next.js que cachee la respuesta durante 60 segundos. Esa es tu capa ISR gratis, sin configuración separada. Segundo, no hace falta cabecera de autenticación para contenido publicado. Si quieres traer borradores, agregas una cabecera Authorization: Bearer <token>. El token vive en .env.local, nunca en NEXT_PUBLIC_*.

Paso 2: Variables de entorno y dominios de imagen

Crea .env.local:

NEXT_PUBLIC_KAMAAN_SITE_ID=6a15f18577639d2385209d06

Luego abre next.config.js y agrega el dominio de imágenes de Kamaan a images.remotePatterns. Este es el fallo de build más común. El error dice 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;

Reinicia el servidor de desarrollo después de editar next.config.js. El hot reload no detecta cambios de configuración de imágenes.

Paso 3: La vista de lista

app/blog/page.tsx lee todos los artículos publicados y renderiza tarjetas. Server component, sin estado de cliente.

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("es");
  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 nivel de página es cinturón-y-tirantes junto al revalidate a nivel de fetch. Cualquiera de los dos solos funciona. Los dos juntos también está bien y hace la intención obvia para quien lea el archivo después.

Paso 4: La página de post individual

app/blog/[slug]/page.tsx lee un artículo por slug, devuelve 404 si falta y renderiza markdown. Este es el archivo donde más proyectos se atascan, porque tres cosas tienen que alinearse: el parámetro de ruta dinámica, la generación de static params y el renderizador de 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("es");
  return articles.map((a) => ({ slug: a.slug }));
}

export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const article = await getArticleBySlug(params.slug, "es");
  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 corre en build time. Trae todos los slugs publicados para que Next.js pre-renderice cada post como HTML estático. Los artículos nuevos publicados después del deploy igual funcionan gracias a ISR: la primera petición pega al API, cachea el resultado 60 segundos, y sirve HTML cacheado a todos los demás.

Instala las dependencias de markdown:

npm i remark remark-html

Si prefieres MDX, cambia remark-html por @next/mdx y ajusta la forma del componente. La mayoría de blogs no necesita MDX. Markdown plano más la clase Tailwind prose cubre el 90% de los casos.

Paso 5: ISR, invalidación de caché y el truco

revalidate = 60 significa que un cambio de post aparece dentro de los 60 segundos siguientes a la próxima petición. Eso está bien para la mayoría de los blogs. Si quieres publicación instantánea, dos caminos:

  1. Pon revalidate en un intervalo más largo y dispara revalidación on-demand desde un webhook de Kamaan. Kamaan puede hacer POST a https://tudominio.com/api/revalidate?secret=xxx&path=/blog/[slug] cada vez que se publica un artículo. Tu route handler llama a revalidatePath() y devuelve 200.
  2. Usa revalidate = 0 (SSR en cada petición). Más lento para los usuarios, sin capa de caché, pero siempre fresco.

La mayoría de equipos elige la opción 1 una vez que crece el tráfico. Hasta entonces, revalidate = 60 está bien.

Advertencia honesta: el Data Cache de Vercel y el Full Route Cache de Next.js no siempre se invalidan juntos. Si cambias un artículo y la nueva versión aparece en /blog/[slug] pero el excerpt viejo sigue en /blog, eso es el Full Route Cache de la página índice atrasándose. Dispara revalidate en /blog y en /blog/[slug] desde el mismo webhook.

Paso 6: Rutas multilingües

Aquí es donde Kamaan más tiempo te ahorra. La entrega multilingüe automática de Kamaan significa que un POST crea un artículo en inglés y las traducciones a español, alemán, francés e italiano quedan disponibles en el mismo slug bajo un parámetro de consulta language. Tu lado Next.js maneja el segmento de ruta.

Mueve tus páginas de blog bajo app/[lang]/blog/. Agrega validación de idioma en la ruta:

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();
  // ... renderizar como antes
}

Los slugs quedan en inglés en todos los idiomas. Es una decisión deliberada de SEO: mantiene tu estructura de URL consistente, simplifica la analítica y coincide con lo que hace cada blog SaaS importante (Stripe, Notion, Linear). Si necesitas slugs traducidos, guárdalos en un mapa de slugs y resuélvelos en middleware. La mayoría de equipos no lo hace.

Aquí tienes los seis pasos de configuración mapeados en una sola imagen que puedes tener abierta mientras construyes.

Flujo de integración Next.js más Kamaan en seis pasos: helper fetch, configuración de entorno, vista de lista, post individual, ISR, ruta multilingüe

Escenarios del mundo real

Escenario 1: blog B2B SaaS, 40 posts, inglés más español

Un equipo de dos personas migró de un blog de markdown-en-repo a Kamaan porque cada actualización de contenido requería un deploy. La configuración le tomó al ingeniero senior 50 minutos siguiendo esta guía. La versión en español de los 40 posts se generó automáticamente con Auto-Multilingual Delivery de Kamaan. Tiempo total incluyendo traducciones: 70 minutos. El deploy fue instantáneo porque la app Next.js existente ya estaba en Vercel.

Escenario 2: fundador solo, sin posts todavía, multilingüe desde el día uno

Una desarrolladora solo quería inglés más cuatro idiomas desde el lanzamiento. Se saltó por completo la fase de markdown-en-repo. La configuración fueron 45 minutos del lado Next.js y 15 minutos para el primer artículo (escrito en Kamaan, auto-traducido, publicado). El endpoint MCP Server le permitió escribir artículos siguientes hablando con Claude, que publicaba directo en Kamaan.

Despliegue a Vercel

Tres pasos:

  1. Sube el repo a GitHub.
  2. Importa en Vercel. Vercel detecta Next.js automáticamente.
  3. Agrega NEXT_PUBLIC_KAMAAN_SITE_ID a las variables de entorno de Vercel. Vuelve a desplegar.

Ese es todo el despliegue. ISR funciona en Vercel out of the box. El build corre generateStaticParams, pre-renderiza cada artículo, y el runtime maneja los artículos nuevos vía el hint de revalidate.

Si despliegas en otro lado (Netlify, Cloudflare Pages, tu propio servidor Node), la semántica de ISR varía. Cloudflare Pages con @cloudflare/next-on-pages funciona pero usa KV para la capa de caché. Servidores Node auto-alojados necesitan el modo standalone output y un filesystem persistente para que la caché sobreviva reinicios. Vercel es el camino de menor fricción.

FAQ

¿Cuánto toma realmente una configuración Next.js más Kamaan?

Cuarenta a sesenta minutos para un desarrollador Next.js experimentado siguiendo esta guía. El mayor sumidero de tiempo es el renderizado de markdown, que agrega unos diez minutos si no lo has hecho antes. Multilingüe agrega otros veinte.

¿Necesito TypeScript?

No. Cada ejemplo aquí funciona igual en JavaScript plano. El tipo Article es documentación, no un requisito en tiempo de ejecución. La mayoría de equipos que usan Next.js ya usan TypeScript, por eso los ejemplos lo asumen.

¿Puedo usar Pages Router en vez de App Router?

Sí. El helper fetch queda idéntico. Reemplaza app/blog/page.tsx por pages/blog/index.tsx, usa getStaticProps para el índice, y getStaticParams más getStaticProps con revalidate: 60 para el post individual. App Router es lo recomendado para proyectos nuevos porque la ergonomía de ISR es más limpia.

¿Qué pasa con RSS, sitemap y Open Graph?

Kamaan auto-genera RSS en https://api.kamaan.io/v1/sites/{site_id}/rss. El sitemap está en /sites/{site_id}/sitemap.xml. Ambos se actualizan cada vez que publicas. Para Open Graph, configura título, descripción y featured_image_url en los campos SEO de Kamaan, luego léelos en tu función generateMetadata de Next.js.

¿Cómo hago previsualización de borradores?

Pasa una cabecera Authorization: Bearer <token> en tu llamada fetch y agrega &status=draft a la URL. La mayoría de equipos protege esto detrás de una ruta /blog/preview/[slug] con una cookie secreta. El API de Kamaan soporta tokens de preview directamente, así que no tienes que armar uno propio.

¿Qué pasa si quiero agregar un CMS como Contentful o Sanity después?

El helper fetch es el único archivo que cambia. Cambias la URL del API y el parseo de JSON, dejas igual los archivos de página. Ese es el valor de tratar al CMS como una fuente JSON en vez de una dependencia de SDK.

¿Esto funciona con React Server Components?

Sí. Cada ejemplo de arriba es un server component. No hace falta directiva "use client" salvo que agregues widgets interactivos como una barra de búsqueda.

¿Puedo correr esto sin Vercel?

Sí, con advertencias. Cloudflare Pages, Netlify, AWS Amplify y Node auto-alojado soportan Next.js. ISR funciona out of the box en Vercel y Cloudflare. En Netlify, ISR requiere sus On-Demand Builders. Auto-alojado necesita el modo standalone output.

Relacionado en Kamaan

Empieza a construir con Kamaan

Un blog Next.js más Kamaan son seis archivos y una hora de trabajo. Regístrate en Kamaan, crea un sitio, copia el ID del sitio y sigue esta guía. Si te atascas en dominios de imagen o en caché ISR, los docs cubren ambos con proyectos de ejemplo que puedes forkear.

Frequently asked

FAQ · 8 ITEMS
¿Cuánto toma realmente una configuración Next.js más Kamaan?

Cuarenta a sesenta minutos para un desarrollador Next.js experimentado siguiendo esta guía. El mayor sumidero de tiempo es el renderizado de markdown, que agrega unos diez minutos si no lo has hecho antes. Multilingüe agrega otros veinte.

¿Necesito TypeScript?

No. Cada ejemplo aquí funciona igual en JavaScript plano. El tipo `Article` es documentación, no un requisito en tiempo de ejecución. La mayoría de equipos que usan Next.js ya usan TypeScript, por eso los ejemplos lo asumen.

¿Puedo usar Pages Router en vez de App Router?

Sí. El helper fetch queda idéntico. Reemplaza `app/blog/page.tsx` por `pages/blog/index.tsx`, usa `getStaticProps` para el índice, y `getStaticParams` más `getStaticProps` con `revalidate: 60` para el post individual. App Router es lo recomendado para proyectos nuevos porque la ergonomía de ISR es más limpia.

¿Qué pasa con RSS, sitemap y Open Graph?

Kamaan auto-genera RSS en `https://api.kamaan.io/v1/sites/{site_id}/rss`. El sitemap está en `/sites/{site_id}/sitemap.xml`. Ambos se actualizan cada vez que publicas. Para Open Graph, configura título, descripción y `featured_image_url` en los campos SEO de Kamaan, luego léelos en tu función `generateMetadata` de Next.js.

¿Cómo hago previsualización de borradores?

Pasa una cabecera `Authorization: Bearer <token>` en tu llamada fetch y agrega `&status=draft` a la URL. La mayoría de equipos protege esto detrás de una ruta `/blog/preview/[slug]` con una cookie secreta. El API de Kamaan soporta tokens de preview directamente, así que no tienes que armar uno propio.

¿Qué pasa si quiero agregar un CMS como Contentful o Sanity después?

El helper fetch es el único archivo que cambia. Cambias la URL del API y el parseo de JSON, dejas igual los archivos de página. Ese es el valor de tratar al CMS como una fuente JSON en vez de una dependencia de SDK.

¿Esto funciona con React Server Components?

Sí. Cada ejemplo de arriba es un server component. No hace falta directiva `"use client"` salvo que agregues widgets interactivos como una barra de búsqueda.

¿Puedo correr esto sin Vercel?

Sí, con advertencias. Cloudflare Pages, Netlify, AWS Amplify y Node auto-alojado soportan Next.js. ISR funciona out of the box en Vercel y Cloudflare. En Netlify, ISR requiere sus On-Demand Builders. Auto-alojado necesita el modo 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.