🎨 Sysprovider Code
Sysprovider LogoWiki
🇪🇸Hosting español para ecommerce

WordPress Headless CMS con Next.js 15

Actualizado el 21 de abril de 2026

Imagina tener la flexibilidad de un CMS moderno como WordPress, pero con el rendimiento y la experiencia de desarrollo de un framework como React. Esa es la promesa de WordPress Headless, y al combinarlo con Next.js 15, obtienes una arquitectura imparable. En este artículo, exploraremos cómo construir un sitio web ultrarrápido y escalable utilizando WordPress como Headless CMS y Next.js 15 como el frontend de presentación.

Veremos desde la configuración inicial hasta la implementación de técnicas avanzadas de renderizado como ISR (Incremental Static Regeneration) y SSR (Server-Side Rendering), todo para que puedas ofrecer contenido dinámico con la velocidad de un sitio estático.

¿Por Qué WordPress Headless con Next.js 15?

Antes de sumergirnos en el código, entendamos el "por qué". Un enfoque Headless separa el backend de gestión de contenido (WordPress) del frontend de presentación (Next.js). Esto ofrece ventajas clave:

  • Rendimiento Superior: El frontend se construye con tecnologías modernas (React) y se puede servir como archivos estáticos o generarse dinámicamente con SSR.
  • Experiencia de Desarrollo Moderna: Next.js 15 ofrece características como Server Components, App Router y optimizaciones automáticas de imágenes.
  • Escalabilidad: Puedes escalar el frontend y el backend de forma independiente. El CMS puede estar en un servidor modesto, mientras que el frontend se despliega en una CDN global.
  • Mejor Seguridad: Al exponer solo la API de WordPress (REST o GraphQL), reduces la superficie de ataque. No hay temas o plugins PHP ejecutándose en el frontend.

Configuración del Entorno: WordPress como API

El primer paso es preparar tu WordPress para que actúe como un Headless CMS. Necesitas una instalación limpia de WordPress (preferiblemente en un subdominio o ruta separada, ej: cms.misitio.com).

Instalación y Plugins Esenciales

Instala estos plugins para optimizar tu WordPress como API:

  1. WPGraphQL (Recomendado): Expone una API GraphQL moderna y eficiente. Es más rápida y flexible que la REST API para consultas complejas.
  2. Advanced Custom Fields (ACF): Para crear campos personalizados que enriquezcan tu contenido (imágenes, selectores, repetidores).
  3. WPGraphQL for ACF: Conecta ACF con WPGraphQL, permitiendo que tus campos personalizados sean consultables por GraphQL.
  4. Yoast SEO o Rank Math: Siguen siendo útiles para gestionar el SEO desde el backend, aunque la presentación final la manejará Next.js.

[TIP]: Si prefieres la REST API, puedes usarla, pero GraphQL te dará un control más granular sobre los datos que obtienes, evitando el "over-fetching" y "under-fetching".

Configuración de Permalinks y CORS

Asegúrate de que los permalinks de WordPress estén en un formato legible (ej: Nombre de la entrada). Luego, configura CORS para permitir peticiones desde tu frontend de Next.js. Puedes hacerlo con un plugin como WP CORS o añadiendo código a tu archivo wp-config.php:

// En wp-config.php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");

Construyendo el Frontend con Next.js 15

Ahora viene la parte divertida: crear la aplicación Next.js que consumirá los datos de WordPress.

1. Inicialización del Proyecto

Asegúrate de tener Node.js 18+ instalado. Crea un nuevo proyecto de Next.js 15:

npx create-next-app@latest mi-blog-headless --typescript --tailwind --app
cd mi-blog-headless

El flag --app habilita el App Router, la nueva forma recomendada de construir aplicaciones en Next.js 15.

2. Configuración del Cliente GraphQL

Instala graphql-request para hacer consultas a tu API de WordPress:

npm install graphql-request graphql

Crea un archivo lib/graphql-client.ts:

import { GraphQLClient } from 'graphql-request';

const endpoint = process.env.WORDPRESS_API_URL || 'https://cms.misitio.com/graphql';

export const client = new GraphQLClient(endpoint);

Y en tu archivo .env.local:

WORDPRESS_API_URL=https://cms.misitio.com/graphql

3. Consultas GraphQL para Contenido

Define tus consultas en lib/queries.ts. Por ejemplo, para obtener posts con sus categorías y featured image:

import { gql } from 'graphql-request';

export const GET_POSTS = gql`
  query GetPosts {
    posts(first: 10) {
      nodes {
        id
        title
        slug
        excerpt
        featuredImage {
          node {
            sourceUrl
            altText
            mediaDetails {
              width
              height
            }
          }
        }
        categories {
          nodes {
            name
            slug
          }
        }
        date
      }
    }
  }
`;

export const GET_POST_BY_SLUG = gql`
  query GetPostBySlug($slug: String!) {
    postBy(slug: $slug) {
      id
      title
      content
      date
      author {
        node {
          name
        }
      }
      featuredImage {
        node {
          sourceUrl
          altText
        }
      }
      categories {
        nodes {
          name
          slug
        }
      }
    }
  }
`;

Renderizado Inteligente: ISR y SSR en Acción

Aquí es donde Next.js 15 brilla. Puedes decidir cómo y cuándo se renderiza cada página.

SSR (Server-Side Rendering) para Páginas Dinámicas

Usa SSR cuando el contenido debe ser fresco en cada solicitud, como una página de inicio de sesión o un dashboard. En Next.js 15 con App Router, simplemente exporta una función fetch que no tenga cache: 'force-cache':

// app/page.tsx
import { client } from '@/lib/graphql-client';
import { GET_POSTS } from '@/lib/queries';

export default async function HomePage() {
  // SSR: No se cachea, se genera en cada petición
  const data = await client.request(GET_POSTS, {}, {
    cache: 'no-store',
  });

  const posts = data.posts.nodes;

  return (
    <main>
      <h1>Últimos Posts (SSR)</h1>
      {posts.map((post: any) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <div dangerouslySetInnerHTML={{ __html: post.excerpt }} />
        </article>
      ))}
    </main>
  );
}

ISR (Incremental Static Regeneration) para Alto Rendimiento

ISR es el punto óptimo. Combina la velocidad del contenido estático con la frescura del contenido dinámico. Con ISR, generas la página estáticamente en el build, pero la revalidas en segundo plano cada cierto tiempo.

En Next.js 15, puedes usar revalidate en la función fetch o en el layout/page:

// app/posts/[slug]/page.tsx
import { client } from '@/lib/graphql-client';
import { GET_POST_BY_SLUG } from '@/lib/queries';

export default async function PostPage({ params }: { params: { slug: string } }) {
  const { slug } = params;

  // ISR: Revalida cada 60 segundos
  const data = await client.request(GET_POST_BY_SLUG, { slug }, {
    next: { revalidate: 60 },
  });

  const post = data.postBy;

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      <p>Autor: {post.author.node.name}</p>
    </article>
  );
}

// Genera rutas estáticas para todos los posts en el build
export async function generateStaticParams() {
  const data = await client.request(GET_POSTS);
  const posts = data.posts.nodes;

  return posts.map((post: any) => ({
    slug: post.slug,
  }));
}

[INFO]: Con generateStaticParams, pre-renderizas todas las páginas de posts en el build. Luego, cuando un visitante accede a una página, se sirve el HTML estático. Si ha pasado más de 60 segundos, Next.js regenera la página en segundo plano para la siguiente solicitud.

Manejo Avanzado de Contenido Enriquecido

WordPress permite contenido enriquecido con bloques (Gutenberg). Para mostrarlo correctamente en Next.js, necesitas procesar el HTML.

Renderizado de Bloques Gutenberg

El contenido de WordPress viene como HTML con clases propias de Gutenberg. Puedes renderizarlo directamente con dangerouslySetInnerHTML, pero es mejor usar una librería como html-react-parser para mayor control:

npm install html-react-parser

Luego, crea un componente RichContent:

import parse from 'html-react-parser';

export default function RichContent({ html }: { html: string }) {
  return <div className="prose prose-lg max-w-none">{parse(html)}</div>;
}

Y úsalo en tu página de post:

<RichContent html={post.content} />

Imágenes Optimizadas con Next/Image

Para las imágenes destacadas, usa el componente Image de Next.js para optimización automática (lazy loading, formatos modernos, redimensionamiento):

import Image from 'next/image';

// En PostPage
{post.featuredImage?.node && (
  <Image
    src={post.featuredImage.node.sourceUrl}
    alt={post.featuredImage.node.altText || post.title}
    width={post.featuredImage.node.mediaDetails?.width || 1200}
    height={post.featuredImage.node.mediaDetails?.height || 630}
    priority
  />
)}

[WARNING]: Para que next/image funcione con dominios externos (tu WordPress), debes configurar remotePatterns en next.config.js:

// next.config.js
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cms.misitio.com',
      },
    ],
  },
};

module.exports = nextConfig;

SEO y Metadatos en Next.js 15

El SEO es crítico. En Next.js 15, puedes generar metadatos dinámicos para cada página usando la API de metadatos.

// app/posts/[slug]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
  const data = await client.request(GET_POST_BY_SLUG, { slug });
  const post = data.postBy;

  return {
    title: post.title,
    description: post.excerpt?.replace(/<[^>]*>/g, '').slice(0, 160),
    openGraph: {
      title: post.title,
      description: post.excerpt?.replace(/<[^>]*>/g, '').slice(0, 160),
      images: [post.featuredImage?.node?.sourceUrl],
    },
  };
}

Despliegue y Consideraciones Finales

Para desplegar tu aplicación, plataformas como Vercel (creada por los creadores de Next.js) son ideales. Aquí tienes un flujo de trabajo típico:

  1. Conecta tu repositorio de Git a Vercel.
  2. Configura las variables de entorno (WORDPRESS_API_URL).
  3. Cada git push a la rama principal desencadena un nuevo build con ISR habilitado.

Webhooks para Revalidación Instantánea

Para que ISR sea aún más potente, configura un webhook en WordPress que se dispare cuando publiques o actualices contenido. Este webhook llamará a una ruta API de Next.js que fuerza la revalidación de páginas específicas.

Crea una ruta API en app/api/revalidate/route.ts:

import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const secret = request.headers.get('x-revalidate-secret');

  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
  }

  const body = await request.json();
  const slug = body.slug;

  if (slug) {
    revalidatePath(`/posts/${slug}`);
  } else {
    revalidatePath('/');
    revalidatePath('/posts');
  }

  return NextResponse.json({ revalidated: true });
}

Luego, desde WordPress, usa un plugin como WP Webhooks para enviar una petición POST a https://tudominio.com/api/revalidate con el slug del post modificado.

Conclusión

Combinar WordPress Headless con Next.js 15 no solo es técnicamente factible, sino que es una estrategia ganadora para proyectos que requieren un CMS potente y un frontend de alto rendimiento. Has aprendido a configurar WordPress como API, construir consultas GraphQL, y aplicar ISR y SSR para optimizar la entrega de contenido.

La flexibilidad de este stack te permite escalar desde un blog personal hasta un portal de noticias con miles de visitas diarias. Ahora es tu turno: implementa estos conceptos, experimenta con las capacidades de Next.js 15, y construye el sitio web que tus usuarios merecen.

¿Necesitas ayuda?Son dos de nuestros técnicos, Agustín y Mikel, y están disponibles para resolver cualquier problema.

Hablar con ellos ahora
Agustín y Mikel