WC

Tutorial: Cómo crear un Blog Profesional con Next.js y Wisp CMS

Si te gusta la experiencia de escritura de Medium o Notion pero quieres tener control total sobre el código de tu web (sin pagar hosting de CMS caros), Wisp es la pieza que te falta.

En esta guía conectaremos la API de Wisp con Next.js 14/15 para montar un blog en menos de 15 minutos.

¿Qué necesitas?

  • Node.js instalado.
  • Una cuenta gratuita en wisp.blog.

Paso 1: Configurar Wisp

Ve a wisp.blog y regístrate

  1. Ve a wisp.blog y regístrate.
  2. Crea un nuevo "Blog". Llámalo como quieras (ej: "Mi Blog Dev").
  3. Ve a la pestaña Setup en tu panel de Wisp.
  4. Copia tu Blog ID (será una cadena larga de números y letras). Lo necesitaremos en el código.
  5. (Opcional pero recomendado) Escribe un artículo de prueba y publícalo para tener algo que mostrar.

Paso 2: Crear el proyecto Next.js

Abre tu terminal y crea una app nueva. Usaremos TypeScript y Tailwind CSS (el estándar actual).

npx create-next-app@latest mi-blog-wisp
# Responde:
# TypeScript: Yes
# Tailwind CSS: Yes
# App Router: Yes
Enter fullscreen mode Exit fullscreen mode

Entra en la carpeta e instala el cliente oficial de Wisp. Es una librería pequeñita que facilita mucho las llamadas a la API (no necesitas hacer fetch manual).

cd mi-blog-wisp
npm install @wisp-cms/client
Enter fullscreen mode Exit fullscreen mode

También instalaremos el plugin de tipografía de Tailwind para que el contenido del post (que viene en HTML) se vea bonito automáticamente:

npm install -D @tailwindcss/typography
Enter fullscreen mode Exit fullscreen mode

Configura el plugin: Abre tailwind.config.ts y añádelo:

// tailwind.config.ts
import type { Config } from "tailwindcss";

const config: Config = {
  // ... resto de tu config
  plugins: [
    require('@tailwindcss/typography'),
  ],
};
export default config;
Enter fullscreen mode Exit fullscreen mode

Paso 3: Conectar la API (El cliente)

Vamos a crear un archivo para inicializar Wisp. Crea un archivo lib/wisp.ts (crea la carpeta lib si no existe).

// lib/wisp.ts
import { buildWispClient } from "@wisp-cms/client";

export const wisp = buildWispClient({
  blogId: "PEGA_AQUI_TU_BLOG_ID", // Reemplaza esto con el ID del Paso 1
});
Enter fullscreen mode Exit fullscreen mode

Nota: En un proyecto real, deberías guardar el ID en una variable de entorno .env.


Paso 4: Crear la página principal (Lista de Posts)

Vamos a editar app/page.tsx. Aquí pediremos la lista de artículos a Wisp y los mostraremos.

// app/page.tsx
import { wisp } from "@/lib/wisp";
import Link from "next/link";
import Image from "next/image";

export default async function Home() {
  // 1. Pedimos los posts a Wisp (por defecto trae 20)
  const result = await wisp.getPosts({ limit: 10 });

  return (
    <main className="max-w-4xl mx-auto py-10 px-4">
      <h1 className="text-4xl font-bold mb-8">Mi Blog Personal</h1>

      <div className="grid gap-8">
        {result.posts.map((post) => (
          <article key={post.id} className="border-b pb-8">
            <Link href={`/blog/${post.slug}`} className="group">
              {/* Imagen de portada si existe */}
              {post.image && (
                <div className="relative w-full h-64 mb-4 overflow-hidden rounded-lg">
                  <Image 
                    src={post.image} 
                    alt={post.title} 
                    fill 
                    className="object-cover transition-transform group-hover:scale-105"
                  />
                </div>
              )}

              <h2 className="text-2xl font-bold group-hover:text-blue-600 transition-colors">
                {post.title}
              </h2>

              <p className="text-gray-600 mt-2">
                {post.description}
              </p>

              <span className="text-sm text-gray-400 mt-4 block">
                Leer más →
              </span>
            </Link>
          </article>
        ))}
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

Paso 5: La página del Post Individual

Ahora necesitamos crear la página dinámica que mostrará el contenido.
Crea la estructura de carpetas: app/blog/[slug]/page.tsx.

// app/blog/[slug]/page.tsx
import { wisp } from "@/lib/wisp";
import { notFound } from "next/navigation";
import Image from "next/image";

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

// 1. Esto genera los metadatos SEO automáticamente (Título, Descripción)
export async function generateMetadata({ params }: Params) {
  const result = await wisp.getPost(params.slug);
  if (!result || !result.post) return { title: "Post no encontrado" };

  return {
    title: result.post.title,
    description: result.post.description,
    openGraph: {
      images: [result.post.image || ""],
    },
  };
}

// 2. La página del post
export default async function BlogPostPage({ params }: Params) {
  const result = await wisp.getPost(params.slug);

  if (!result || !result.post) {
    return notFound();
  }

  const { title, content, publishedAt, image, tags } = result.post;

  return (
    <article className="max-w-3xl mx-auto py-10 px-4">
      {/* Cabecera */}
      <header className="mb-8 text-center">
        <h1 className="text-4xl md:text-5xl font-extrabold mb-4">{title}</h1>
        <p className="text-gray-500">
          {new Date(publishedAt).toLocaleDateString()}
        </p>
      </header>

      {/* Imagen Principal */}
      {image && (
        <div className="relative w-full h-80 md:h-96 mb-10 rounded-xl overflow-hidden shadow-lg">
          <Image src={image} alt={title} fill className="object-cover" priority />
        </div>
      )}

      {/* 
         CONTENIDO DEL POST 
         Usamos la clase 'prose' de Tailwind Typography.
         Esto estiliza automáticamente el HTML que nos da Wisp.
      */}
      <div 
        className="prose prose-lg prose-blue mx-auto"
        dangerouslySetInnerHTML={{ __html: content }} 
      />

      {/* Tags */}
      <div className="mt-10 flex gap-2">
        {tags.map((tag) => (
          <span key={tag.id} className="bg-gray-100 px-3 py-1 rounded-full text-sm">
            #{tag.name}
          </span>
        ))}
      </div>
    </article>
  );
}
Enter fullscreen mode Exit fullscreen mode

Paso 6: Probar

  1. Ejecuta el servidor: npm run dev.
  2. Abre http://localhost:3000.
  3. Deberías ver tu lista de posts. Si haces clic en uno, te llevará al artículo completo perfectamente estilizado.

¿Por qué esto es mejor que Hashnode estándar?

  1. Velocidad: Usas Next.js App Router. Puedes implementar Server Components para que la carga sea instantánea.
  2. SEO Canónico: Tú controlas el dominio desde el principio.
  3. Wisp "Magic": Si en Wisp escribes y pegas imágenes, o incrustas tweets, la API te devuelve el HTML listo para renderizar. El dangerouslySetInnerHTML combinado con tailwind-typography hace que se vea profesional sin que escribas ni una línea de CSS para los párrafos, listas o citas.

Siguientes pasos (Despliegue)

Sube tu código a GitHub y conéctalo a Vercel. ¡Tu blog estará online gratis y con CDN global en minutos!

Joaquin Sáez

Escrito por

Joaquin Sáez

Artículos Relacionados

← Volver al Blog