Communitygithub.com

timerise-ai/blog-markdown

Skill para agente: construye un blog multilingüe basado en archivos Markdown con particiones por locale, slugs localizados unidos por una clave de traducción común, páginas de etiquetas con identidad por slug, artículos relacionados, portada, RSS, sitemap, hreflang y validador de contenido en CI — en Next.js App Router, sin CMS.

¿Qué es blog-markdown?

Este skill proporciona una arquitectura completa para construir un blog multilingüe en Next.js App Router, donde el contenido se almacena como archivos Markdown en el repositorio y se genera estáticamente en tiempo de compilación, sin depender de un CMS externo. Su diseño se basa en un concepto fundamental: la identidad de cada artículo es su `translationKey`, no su slug; los slugs se localizan por idioma para SEO, mientras que la `translationKey` unifica las versiones traducidas de un mismo artículo, permitiendo hreflang, cambio de idioma, artículos relacionados y redirecciones entre locales. El skill incluye reglas estrictas y hechos críticos validados contra una implementación previa: las relaciones se escriben una sola vez en el locale predeterminado usando slugs de ese locale, las etiquetas se agrupan por slug (no por etiqueta textual) para evitar duplicados, se maneja un fallback de parseo de YAML para evitar errores con comillas tipográficas en traducciones, y se memoiza la carga por locale para evitar la amplificación de lectura de archivos (71x confirmada). Incluye referencias a documentos detallados sobre esquema de frontmatter, carga de contenido, i18n, enrutamiento, etiquetas, renderizado, validación en CI, pruebas y adaptación a proyectos existentes. Es ideal para equipos que necesitan un blog multilingüe con etiquetas, feeds RSS, sitemap y contenido relacionado, gestionado completamente como archivos en el repositorio con generación estática.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add timerise-ai/blog-markdown

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Blog Markdown (multilingüe, con etiquetas)

Un blog donde el contenido son archivos, no filas. Todo el diseño gira en torno a una idea: la identidad de un artículo es su translationKey, no su slug. Los slugs se localizan para SEO y difieren por idioma; la clave es lo que hace que tres archivos sean un solo artículo, y es lo que impulsa hreflang, el selector de idioma, los artículos relacionados y las redirecciones entre locales. Si aciertas con ese campo, todo lo demás son búsquedas.

Escrito por el ingeniero que ha enviado este módulo. La implementación anterior contra la que se auditó fue un blog de marketing multilingüe, generado estáticamente, con su sitemap y superficie SEO. Los hechos y reglas a continuación son lo que la suite de la capa de contenido y la compilación verifican; references/provenance.md contiene el registro.

Cuándo usar

  • Contenido redactado como markdown en el repositorio, desplegado con la aplicación
  • Más de un idioma, con diferentes slugs por idioma
  • Páginas de etiquetas, artículos relacionados, feeds o hreflang están dentro del alcance
  • Deseas que todo se genere estáticamente en tiempo de compilación

Cuándo NO usar

En lugar de estoUsa
Un CMS alojado es la fuente de verdadel SDK de ese CMS; guarda solo references/content-model.md
Un sitio de documentación / centro de ayuda (categorías, barra lateral, búsqueda)un skill de centro de ayuda — modelo de navegación diferente
Un blog monolingüe de menos de 10 artículosfs + gray-matter en línea; esto es sobrecarga
Renderizar markdown enviado por usuariosun renderizador sanitizante; consulta la nota XSS en references/rendering.md

Arquitectura

content/blog/<locale>/<localized-slug>.md   # frontmatter + cuerpo
        |
        v
 parseFrontmatter  --YAML, recurre a un analizador línea por línea en comillas mal formadas
        |
        v
   getAllPosts(locale)  <-- memoizado por locale en producción
        |
        +--> translationKey ----> alternates / hreflang / cambio de idioma
        +--> tags -> slug de etiqueta ---> páginas de etiqueta (el slug es la identidad, no la etiqueta)
        +--> related (slugs EN) -> artículos relacionados, con fallback a EN
        |
        v
  /blog   /blog/[slug]   /blog/tag/[slug]   sitemap.xml   feed.xml

Hechos críticos

  1. Las relaciones se escriben una vez, en el locale predeterminado, como slugs del locale predeterminado. Un archivo traducido puede omitir related por completo y aun así obtener los artículos relacionados correctos, resueltos a través de translationKey, de modo que cada traducción renderiza la misma sección de relacionados. La prueba de fallback lo verifica.
  2. La identidad de una etiqueta es su slug, no su etiqueta textual. "AI Agents" y "AI agents" apuntan al mismo slug y URL. Agrupa por slug y empareja artículos por slug, de modo que todas las variantes ortográficas de una etiqueta aparezcan en la misma página de etiqueta. Dos pruebas lo verifican.
  3. gray-matter lanza una excepción en un escalar YAML con comillas dobles que contienen caracteres de comillas literales — las comillas tipográficas en un extracto traducido lo provocan. Sin un analizador de respaldo, la compilación se detiene en ese archivo; el analizador línea por línea de respaldo lo mantiene cargando. Verificado contra la librería y cubierto por una prueba.
  4. gray-matter almacena en caché un parseo fallido como resultado vacío. Escribe su entrada de caché antes de analizar, por lo que la segunda y posteriores lecturas de un archivo que falló devuelven {} — sin error, sin frontmatter. Siempre llama a matter(contents, {}); cualquier objeto de opciones desactiva la caché.
  5. La carga es O(artículos x locales) por página a menos que memoices. Memoiza por locale para que cada archivo de contenido se lea una vez por compilación; resolver alternates en generateMetadata de otra manera resulta en una amplificación de lectura de archivos de 71x, medida en la implementación anterior.
  6. getPostSlugs debe filtrar .md. El filtro evita que un .DS_Store suelto se convierta en una lectura de .DS_Store.md y un ENOENT. Una prueba lo verifica.
  7. Las páginas de etiqueta no tienen identidad entre locales. Las etiquetas son texto libre por locale, por lo que el selector de idioma debe recurrir al índice del blog en una URL de etiqueta en lugar de construir una URL que dé 404.

Reglas estrictas

Nunca uses el slug como clave para búsquedas entre locales. Los slugs están localizados intencionadamente. translationKey es la columna de unión; una coincidencia de slug entre locales es una coincidencia, no una relación.

Nunca permitas que getPostBySlug decida la visibilidad de borradores implícitamente. Filtra los borradores en el cargador; dynamicParams = false los oculta solo como efecto secundario y deja de hacerlo en cuanto la bandera cambia. La prueba de borradores verifica el filtro.

Nunca renderices una constante «5 min de lectura». Calcúlala a partir del cuerpo; cuesta un split, y la prueba de tiempo de lectura lo verifica.

Nunca derives la URL de una página de etiqueta a partir de la etiqueta en tiempo de lectura y la etiqueta a partir de la URL en tiempo de renderizado sin una comprobación de colisión. Ese viaje de ida y vuelta solo es sin pérdidas mientras cada etiqueta se convierta en un slug único.

Inicio rápido

  1. Define el contrato de locale y el esquema de frontmatter — content-model.md
  2. Construye el cargador (memoizado, filtrado por .md, analizador de respaldo) — content-loader.md
  3. Conecta locales, alternates y redirecciones entre locales — i18n-and-routing.md
  4. Añade agrupación de etiquetas y páginas de etiqueta — tags.md
  5. Construye las tres rutas, metadatos y JSON-LD — pages-and-seo.md
  6. Renderiza cuerpos y portadas — rendering.md
  7. Añade el script de validación y lee las brechas operativas — operations.md
  8. Ejecuta los fixtures y las pruebas — testing.md

¿Portando esto a una aplicación existente? Completa primero la tabla de costuras en adaptation.md — lleva diez minutos y evita un renombrado.

Directorio de referencias

EscenarioPalabras clave de activaciónReferencia
Campos de frontmatter, borradores, claves de traducción, relacionesfrontmatter, schema, translationKey, draft, related, excerptcontent-model.md
Lectura de archivos, caché, fallos de YAML, tiempo de lecturagray-matter, fs, cache, memoize, ENOENT, YAMLException, parsecontent-loader.md
Locales, hreflang, alternates, enlaces a locale incorrectoi18n, locale, hreflang, alternates, canonical, language switcher, redirecti18n-and-routing.md
URLs de etiquetas, colisiones, páginas de etiqueta ligerastag, slug, diacritics, collision, tag page, taxonomytags.md
Las rutas, metadatos, JSON-LD, sitemap, RSSpage.tsx, generateStaticParams, generateMetadata, dynamicParams, sitemap, RSS, feed, BlogPostingpages-and-seo.md
Markdown a React, bloques de código, imágenes de portadareact-markdown, remark-gfm, syntax highlighting, cover, motif, accent, XSSrendering.md
Validación de contenido, coste de compilación, flujo de trabajo del editorvalidation, CI check, build time, drafts, authoring, operatoroperations.md
Fixtures y la suite de pruebas que pasatest, fixture, vitest, node:test, regression, asserttesting.md
Integración en una aplicación anfitriona: costuras, renombres, pruebaadapt, port, host, seam, rename, CMS, integrateadaptation.md
El registro de auditoría: qué cambió, qué se mantuvo y qué se añadió vs. la implementación anteriorprovenance, deviations, fidelity, audit, ledgerprovenance.md

Skills relacionados