@semsei/next-blog
v0.4.0
Published
Integra blogs generados por Semsei en tu sitio Next.js
Readme
@semsei/next-blog
Renderiza blogs generados por Semsei directamente en tu sitio Next.js, en tu propio dominio.
Instalación
npm install @semsei/next-blog
# o
bun add @semsei/next-blogConfiguración
- Obtén tu API key en el dashboard de Semsei: Integraciones → CODE
- Agrega al
.env.local:
SEMSEI_API_KEY=sem_abc123...SEMSEI_API_KEY es exclusivamente de servidor. No la expongas con un
prefijo NEXT_PUBLIC_, no llames la API de Semsei desde un Client Component
y no la envíes como prop al navegador.
Para usar otro origen de API (por ejemplo, en desarrollo):
SEMSEI_API_URL=http://localhost:3000Para habilitar la revalidación automática cuando Semsei publique contenido, configura el secreto generado para el webhook:
SEMSEI_WEBHOOK_SECRET=<generado por Semsei>Uso
1. Ruta localizada para los blogs
Crea exactamente app/[locale]/blogs/[...slug]/page.tsx:
// app/[locale]/blogs/[...slug]/page.tsx
export { generateMetadata, default } from "@semsei/next-blog/page";Esto maneja URLs como /es/blogs/mi-articulo,
/en/blogs/my-article y slugs anidados como
/es/blogs/guias/mi-articulo. Next.js entrega el catch-all como un array y el
paquete lo normaliza al slug esperado por la API.
La integración sigue aceptando consumidores existentes montados en
app/blog/[[...slug]]/page.tsx: los slugs catch-all se unen y, cuando no hay
locale, se usa es. Una ruta sin slug o con un locale distinto de es/en
produce un 404.
Middleware público: excluye
/es/blogs/*y/en/blogs/*de cualquier matcher de Clerk, autenticación o redirección a login. Estas páginas deben permanecer públicas. El paquete no inicializa Clerk ni hace fetch de contenido en el cliente.
2. (Opcional) Revalidación on-demand
Crea app/api/semsei/revalidate/route.ts:
export { POST } from "@semsei/next-blog/revalidate";Semsei llamará automáticamente este endpoint cuando se publique o actualice contenido.
La ruta exige Authorization: Bearer <SEMSEI_WEBHOOK_SECRET> y no acepta
SEMSEI_API_KEY como sustituto. El secreto se compara de forma timing-safe y
nunca se incluye en respuestas. Cualquier fallo de autenticación, incluido un
secreto sin configurar, devuelve la misma respuesta genérica 401.
Cada evento tiene exactamente este contrato:
type SemseiRevalidatePayload = {
action: "publish" | "update" | "unpublish";
pageId: string;
locale: "es" | "en";
slug: string;
previousSlug: string;
updatedAt: string;
};La ruta invalida el tag de datos de Semsei y tanto
/{locale}/blogs/{previousSlug} como /{locale}/blogs/{slug}. Todos los
eventos válidos se procesan aunque se repitan o lleguen fuera de orden;
updatedAt es informativo.
3. (Opcional) Sitemap
Crea app/sitemap.xml/route.ts:
// app/sitemap.xml/route.ts
export { GET } from "@semsei/next-blog/sitemap";El helper devuelve las URLs canónicas y alternates que Semsei tiene
configuradas para la integración CODE. Como defensa adicional, omite cualquier
entrada que la API marque con noindex: true, también de los alternates.
Layout
Los blogs se renderizan dentro del layout.tsx del grupo de rutas donde los montes. Si quieres un layout personalizado:
// app/blog/layout.tsx
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return (
<div className="max-w-4xl mx-auto py-8">
{children}
</div>
);
}Variables de entorno
| Variable | Obligatorio | Descripción |
|---|---|---|
| SEMSEI_API_KEY | ✅ | API key generada en Semsei |
| SEMSEI_API_URL | ❌ | URL base de la API (default: https://app.semsei.io) |
| SEMSEI_WEBHOOK_SECRET | Solo revalidación | Secreto exclusivo del webhook; no usa fallback a la API key |
El origen público y el prefijo canónico (por ejemplo, /blogs) se configuran
en la integración CODE de Semsei. El paquete usa esos valores devueltos por la
API para metadata canonical y alternates; no construye URLs canónicas a partir
de headers del navegador.
Respuestas de la API
401:SEMSEI_API_KEYestá ausente o no es válida. El error se propaga para corregir la configuración del servidor.404: no existe una página publicada para ese locale/slug. La página de Next.js llamanotFound()y metadata devuelve un objeto vacío.409 configuration_mismatch: el host del request no coincide con el origen público almacenado, o la integración CODE no tiene una configuración válida. El error se propaga; corrige el dominio/origen en Semsei o el proxy.
Cómo funciona
- La ruta pública recibe requests a
/{locale}/blogs/{slug} - El Server Component llama a la API de Semsei con host, locale y slug
- Semsei devuelve el HTML renderizado con el branding del cliente
- Next.js cachea los datos del fetch de servidor durante 1 hora
- La ruta sigue siendo dinámica porque usa
headers(); no es ISR de página
El contenido no se almacena en tu repositorio. Se obtiene desde el servidor y la respuesta del fetch se reutiliza mediante la caché de datos de Next.js.
