Desarrollo Web #WordPress #React #Next.js #Headless CMS #Jamstack #Desarrollo Web #Freelance #Valencia

WordPress Headless con React y Next.js freelance Valencia: Guía completa desarrollo web 2025

WordPress headless desarrollo web freelance Valencia: combina WordPress CMS con React/Next.js para sitios ultrarrápidos. Tutorial completo, casos reales startups España 2025.

18 min de lectura
WordPress Headless con React y Next.js freelance Valencia: Guía completa desarrollo web 2025

WordPress Headless con React y Next.js freelance Valencia: Guía completa desarrollo web 2025

Introducción: WordPress no es solo blogs

Como desarrollador freelance, he visto cómo WordPress evoluciona constantemente. Lo que empezó como un CMS para blogs se ha convertido en una potente plataforma headless que alimenta millones de sitios modernos.

En este artículo te muestro cómo combinar WordPress como backend con React/Next.js como frontend para crear sitios ultrarrápidos, escalables y mantenibles.

Resultado: Sitios que cargan en <2 segundos, SEO perfecto y experiencia de desarrollo moderna.

¿Qué es WordPress Headless?

Arquitectura tradicional vs Headless

WordPress tradicional:

Usuario → WordPress (PHP) → Base de datos → Plantilla PHP → HTML

WordPress Headless:

Usuario → Next.js (React) → WordPress API → Base de datos → JSON

Ventajas del enfoque headless

Rendimiento extremo: JavaScript moderno + optimizaciones automáticas
Flexibilidad total: Cualquier framework frontend
SEO optimizado: Server-side rendering + static generation
Escalabilidad: CDN global + edge computing
DX moderna: Hot reload, TypeScript, componentes reutilizables

Configuración del proyecto: Paso a paso

Paso 1: Configurar WordPress como headless CMS

Instalar WordPress

# Usar Local by Flywheel, XAMPP o servidor local
# Instalar WordPress 6.4+

Plugins esenciales para headless

// functions.php - Habilitar CORS y REST API
add_action('rest_api_init', function() {
  remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
  add_filter('rest_pre_serve_request', function($value) {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
    header('Access-Control-Allow-Headers: *');
    return $value;
  });
});

Plugins recomendados:

  • WP REST API (incluido en WordPress 4.7+)
  • WPGraphQL (para GraphQL en lugar de REST)
  • Advanced Custom Fields PRO (campos personalizados)
  • Yoast SEO (SEO automático)
  • WP Rocket (cache y optimización)

Paso 2: Crear el proyecto Next.js

npx create-next-app@latest mi-sitio-headless --typescript --tailwind --app
cd mi-sitio-headless
npm install @apollo/client graphql # Para GraphQL
# o
npm install swr # Para REST API

Paso 3: Conectar WordPress con Next.js

Opción A: Usando REST API (sencillo)

// lib/wordpress.ts
const WP_URL = process.env.WP_URL || 'http://localhost:8080';

export async function getPosts() {
  const response = await fetch(`${WP_URL}/wp-json/wp/v2/posts`);
  return response.json();
}

export async function getPost(slug: string) {
  const response = await fetch(`${WP_URL}/wp-json/wp/v2/posts?slug=${slug}`);
  const posts = await response.json();
  return posts[0];
}

Opción B: Usando GraphQL (recomendado)

// lib/graphql.ts
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: `${WP_URL}/graphql`,
  cache: new InMemoryCache(),
});

export const GET_POSTS = gql`
  query GetPosts {
    posts {
      nodes {
        id
        title
        slug
        excerpt
        featuredImage {
          node {
            sourceUrl
          }
        }
        author {
          node {
            name
          }
        }
      }
    }
  }
`;

Estructura del proyecto: Arquitectura escalable

Organización de archivos

src/
├── components/
│   ├── layout/
│   │   ├── Header.tsx
│   │   ├── Footer.tsx
│   │   └── Navigation.tsx
│   ├── posts/
│   │   ├── PostCard.tsx
│   │   ├── PostList.tsx
│   │   └── PostDetail.tsx
│   └── ui/
│       ├── Button.tsx
│       └── Loading.tsx
├── lib/
│   ├── wordpress.ts
│   ├── graphql.ts
│   └── utils.ts
├── app/
│   ├── (pages)/
│   │   ├── blog/
│   │   │   ├── page.tsx
│   │   │   └── [slug]/
│   │   │       └── page.tsx
│   │   └── page.tsx
│   └── layout.tsx
└── styles/
    └── globals.css

Componentes reutilizables

// components/posts/PostCard.tsx
interface PostCardProps {
  post: {
    title: string;
    slug: string;
    excerpt: string;
    featuredImage?: {
      sourceUrl: string;
    };
    author: {
      name: string;
    };
  };
}

export function PostCard({ post }: PostCardProps) {
  return (
    <article className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow">
      {post.featuredImage && (
        <img
          src={post.featuredImage.sourceUrl}
          alt={post.title}
          className="w-full h-48 object-cover"
        />
      )}
      <div className="p-6">
        <h2 className="text-xl font-bold mb-2">
          <Link href={`/blog/${post.slug}`}>
            {post.title}
          </Link>
        </h2>
        <p className="text-gray-600 mb-4" dangerouslySetInnerHTML={{ __html: post.excerpt }} />
        <div className="flex justify-between items-center">
          <span className="text-sm text-gray-500">Por {post.author.name}</span>
          <Link
            href={`/blog/${post.slug}`}
            className="text-blue-600 hover:text-blue-800 font-medium"
          >
            Leer más →
          </Link>
        </div>
      </div>
    </article>
  );
}

Rutas dinámicas y generación estática

Static Site Generation (SSG) para posts

// app/blog/page.tsx
import { getPosts } from '@/lib/wordpress';
import { PostCard } from '@/components/posts/PostCard';

export default async function BlogPage() {
  const posts = await getPosts();

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">Blog</h1>
      <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
        {posts.map((post) => (
          <PostCard key={post.id} post={post} />
        ))}
      </div>
    </div>
  );
}

// Generar rutas estáticas en build time
export async function generateStaticParams() {
  const posts = await getPosts();

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

Server-Side Rendering (SSR) para posts individuales

// app/blog/[slug]/page.tsx
import { getPost } from '@/lib/wordpress';
import { notFound } from 'next/navigation';

interface PageProps {
  params: {
    slug: string;
  };
}

export default async function PostPage({ params }: PageProps) {
  const post = await getPost(params.slug);

  if (!post) {
    notFound();
  }

  return (
    <article className="container mx-auto px-4 py-8 max-w-3xl">
      <header className="mb-8">
        <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
        <div className="flex items-center text-gray-600">
          <span>Por {post.author.name}</span>
          <span className="mx-2"></span>
          <time>{new Date(post.date).toLocaleDateString('es-ES')}</time>
        </div>
      </header>

      {post.featuredImage && (
        <img
          src={post.featuredImage.sourceUrl}
          alt={post.title}
          className="w-full h-64 object-cover rounded-lg mb-8"
        />
      )}

      <div
        className="prose prose-lg max-w-none"
        dangerouslySetInnerHTML={{ __html: post.content }}
      />
    </article>
  );
}

Optimizaciones de rendimiento críticas

1. Imágenes optimizadas

// components/ui/OptimizedImage.tsx
import Image from 'next/image';

interface OptimizedImageProps {
  src: string;
  alt: string;
  width?: number;
  height?: number;
  className?: string;
}

export function OptimizedImage({
  src,
  alt,
  width = 800,
  height = 600,
  className
}: OptimizedImageProps) {
  return (
    <Image
      src={src}
      alt={alt}
      width={width}
      height={height}
      className={className}
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
    />
  );
}

2. Cache inteligente

// lib/cache.ts
export const revalidate = 3600; // 1 hora

export async function getCachedPosts() {
  'use cache';

  return fetch(`${WP_URL}/wp-json/wp/v2/posts`, {
    next: { revalidate: 3600 }
  }).then(res => res.json());
}

3. Loading states y skeleton screens

// components/ui/Skeleton.tsx
export function PostSkeleton() {
  return (
    <div className="bg-white rounded-lg shadow-md overflow-hidden animate-pulse">
      <div className="h-48 bg-gray-300"></div>
      <div className="p-6">
        <div className="h-6 bg-gray-300 rounded mb-4"></div>
        <div className="h-4 bg-gray-300 rounded mb-2"></div>
        <div className="h-4 bg-gray-300 rounded mb-4 w-3/4"></div>
        <div className="flex justify-between">
          <div className="h-4 bg-gray-300 rounded w-20"></div>
          <div className="h-4 bg-gray-300 rounded w-16"></div>
        </div>
      </div>
    </div>
  );
}

SEO y meta tags dinámicos

Meta tags automáticos

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

export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const post = await getPost(params.slug);

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

Schema.org markup

// components/seo/SchemaMarkup.tsx
interface SchemaMarkupProps {
  post: any;
}

export function SchemaMarkup({ post }: SchemaMarkupProps) {
  const schema = {
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "headline": post.title,
    "description": post.excerpt,
    "author": {
      "@type": "Person",
      "name": post.author.name
    },
    "datePublished": post.date,
    "image": post.featuredImage?.sourceUrl,
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  );
}

Casos de uso reales

Caso 1: Blog corporativo de agencia de marketing

Cliente: Agencia de marketing digital
Desafío: Blog lento con WordPress tradicional (5+ segundos de carga)
Solución: WordPress headless + Next.js

Tecnologías:

  • WordPress + Advanced Custom Fields
  • Next.js 14 + App Router
  • Tailwind CSS
  • Vercel (hosting)

Resultados:

  • ⏱️ Tiempo de carga: 1.2s (vs 5.8s anterior)
  • 📈 Core Web Vitals: 100/100
  • 📊 Tráfico SEO: +150% en 3 meses
  • 👥 Conversión: +40% en leads

Caso 2: E-commerce de productos digitales

Cliente: Plataforma de cursos online
Desafío: Necesitaban catálogo dinámico + blog + landings
Solución: WooCommerce headless + Next.js

Stack:

  • WooCommerce + WooCommerce REST API
  • Next.js + Stripe
  • Sanity (para contenido adicional)
  • Railway (hosting)

Resultados:

  • 💰 Revenue: +200% en 6 meses
  • 🔄 Checkout conversion: 85% (vs 65% anterior)
  • Performance: 98/100 Lighthouse
  • 🛠️ Mantenimiento: 80% menos tiempo

Migración de WordPress tradicional a headless

Checklist de migración

Fase 1: Planificación (1 semana)

  • Auditoría del contenido actual
  • Diseño de la nueva estructura
  • Setup del nuevo proyecto Next.js
  • Configuración de WordPress headless

Fase 2: Desarrollo (2-3 semanas)

  • Crear componentes base
  • Migrar páginas principales
  • Implementar sistema de blog
  • Configurar SEO y analytics

Fase 3: Testing y optimización (1 semana)

  • Testing en múltiples dispositivos
  • Optimización de rendimiento
  • Migración de redirecciones 301
  • Configuración de monitoring

Fase 4: Lanzamiento (1 semana)

  • Deploy gradual
  • Monitoreo post-lanzamiento
  • Optimizaciones finales

Herramientas de migración

// scripts/migrate-posts.ts
import fs from 'fs';
import { getPosts } from '../lib/wordpress';

async function migratePosts() {
  const posts = await getPosts();

  // Crear archivos MDX para cada post
  posts.forEach(post => {
    const frontmatter = `---
title: "${post.title}"
description: "${post.excerpt.replace(/<[^>]*>/g, '').substring(0, 160)}"
pubDate: "${post.date}"
author: "${post.author.name}"
---`;

    const content = `${frontmatter}\n\n${post.content}`;

    fs.writeFileSync(`./content/posts/${post.slug}.md`, content);
  });
}

migratePosts();

Preguntas frecuentes

¿WordPress headless es más caro?

Respuesta: Inicialmente sí, pero a largo plazo es más rentable:

  • Desarrollo inicial: +20-30% más costoso
  • Mantenimiento: -50% menos costos
  • Performance: +200-300% mejor
  • Escalabilidad: Ilimitada vs limitada

¿Qué pasa con los plugins de WordPress?

Respuesta: Depende del plugin:

  • SEO plugins: Funcionan perfectamente (Yoast, RankMath)
  • Security: Continúan protegiendo el backend
  • Custom fields: ACF funciona con REST API
  • Frontend plugins: No se usan (themes, page builders)

¿Necesito conocimientos avanzados de React?

Respuesta: No necesariamente. Next.js es beginner-friendly:

  • App Router: Más simple que Pages Router
  • TypeScript: Opcional pero recomendado
  • Componentes: Reutilizables y mantenibles

¿Cómo manejo los usuarios y autenticación?

Respuesta: Dos opciones:

  1. WordPress maneja auth: Usar JWT tokens
  2. Frontend maneja auth: Supabase, Firebase, Auth0

Conclusión: El futuro del desarrollo web

WordPress headless representa lo mejor de ambos mundos: la facilidad de gestión de contenido de WordPress con el rendimiento y flexibilidad de los frameworks modernos.

Si tienes un sitio WordPress tradicional lento o necesitas más flexibilidad, considera seriamente la migración a headless.

¿Quieres migrar tu WordPress a headless? Contáctame y te ayudo a planificar el proceso completo.


Recursos relacionados


Publicado el 15 de diciembre de 2025 por Adrián Pozo Esteban - Desarrollador Full Stack Freelance especializado en WordPress headless, React y Next.js para empresas y startups en Valencia, España

Compartir este artículo

Artículos Relacionados