Skip to content
IRC-CodingIRC-Coding
Astrollms.txtSEOAI SearchContent ProtectionStatic Site Generation

Agregar llms.txt a tu blog Astro

Guía paso a paso: integra llms.txt, llms-full.txt y llms-small.txt en Astro con @4hse/astro-llms-txt, límites de extracto y protección de contenido.

S

schutzgeist

11 min read
Agregar llms.txt a tu blog Astro

Agregar llms.txt al blog de Astro, guía práctica

Contexto

Administro IRC-Coding.de como desarrollador de aplicaciones, con más de 600 artículos sobre desarrollo de software y programación. Los motores de búsqueda de IA como ChatGPT, Perplexity y Claude rastrean sitios web e intentan entender el contenido. Sin embargo, luchan contra el marcado HTML, la renderización de JavaScript y el código innecesario.

La especificación llms.txt resuelve este problema: Un archivo de texto simple en el directorio raíz de un sitio que proporciona el contenido en un formato legible para máquinas.

Durante la implementación me planteé varias preguntas:

  • ¿Cómo genero llms.txt automáticamente durante la compilación?
  • ¿Qué archivos necesito (llms.txt, llms-full.txt, llms-small.txt)?
  • ¿Cómo protejo mi contenido contra copias simples por parte de estafadores?

Examiné dos enfoques conocidos: una ruta API personalizada (como Alex OP describe) y el plugin @4hse/astro-llms-txt (ver GitHub). En este artículo te muestro cómo comparé ambos y qué solución elegí para IRC-Coding.de.

Por cierto, el sitio de Alex OP es muy recomendable, déjate caer por allí. Lo encontré a través de una búsqueda en Google sobre llms.txt Astro.

Importante: incompatibilidad con Astro v7

⚠️ Atención desde Astro v7.0: El plugin @4hse/astro-llms-txt en versión 1.0.5 solo soporta hasta Astro v6 (peer astro@“^5.1.6 || ^6.0.0”). Con Astro v7, npm install falla con un error ERESOLVE. Esto afecta especialmente a despliegues en Railway, Vercel o Netlify que usan npm en lugar de pnpm.

Solución: Retira el plugin antes de actualizar a Astro v7 y genera los archivos llms.txt con un script propio. Existe un pull request para soporte de Astro v7 en el plugin competidor starlight-llms-txt, pero @4hse/astro-llms-txt aún no ha publicado una versión compatible.

Solución

Paso 1: instalar el plugin (hasta Astro v6)

npm install @4hse/astro-llms-txt

El plugin utiliza el hook astro:build:done de Astro, lee los archivos HTML generados del directorio dist/ y los convierte a Markdown con rehype-remark. Esto significa que no necesito implementar parsers propios ni lógica de extracción de contenido.

Paso 2: adaptar la configuración de Astro

En mi astro.config.mjs agregué el plugin a las integraciones:

import astroLlmsTxt from '@4hse/astro-llms-txt';

export default defineConfig({
  site: 'https://www.deine-seite.de',

  integrations: [
    // ... otras integraciones
    astroLlmsTxt({
      title: 'IRC-Coding',
      description: 'Software Development and Programming, Tutorials, Artikel und Ressourcen.',
      notes: '- Content is auto-generated from the official source at https://www.irc-coding.de',
      docSet: [
        {
          title: 'Complete site',
          description: 'Excerpts of all blog articles with links to full content',
          url: '/llms-full.txt',
          include: ['**'],
          promote: ['index', 'blog/**'],
          excerptLength: 600,
          visitLinkText: 'Visit full article',
        },
        {
          title: 'Compact overview',
          description: 'Structure-only index of all pages',
          url: '/llms-small.txt',
          include: ['**'],
          onlyStructure: true,
          promote: ['index', 'blog/**'],
        },
      ],
      pageSeparator: '\n\n---\n\n',
    }),
  ],
});

Paso 3: ejecutar la compilación

npm run build

Después de la compilación encontré tres archivos en dist/:

  • llms.txt: índice con título, descripción y enlaces a los DocSets
  • llms-full.txt: contenido de todas las páginas como Markdown
  • llms-small.txt: solo estructura (encabezados y listas)

Paso 4: límite de extractos para protección de contenido

Por defecto, el plugin genera el contenido completo en llms-full.txt. Con mis 600 artículos, resultó un archivo de más de 140.000 líneas, un regalo para los ladrones de contenido. Cualquiera podía acceder a https://www.irc-coding.de/llms-full.txt y copiar el texto completo de todos los artículos.

Extendí el plugin para resolver esto. La opción excerptLength limita cada artículo a los primeros 600 caracteres. Al final aparece un enlace a la página original:

# Algoritmo explicado de forma sencilla

> Algoritmo explicado de forma comprensible: propiedades, paradigmas de diseño...

## Definición

Un algoritmo es una secuencia finita de instrucciones bien definidas...

[Visit full article](https://www.irc-coding.de/algorithmus-begriffserklaerung-komplexitaet-korrektheit)

Los motores de búsqueda de IA obtienen suficiente contexto para entender de qué se trata. El artículo completo solo existe en el sitio web. Para la adaptación, extendí la interfaz DocSet en el plugin y modifiqué la función buildEntryFromHtml para que corte el contenido en excerptLength caracteres y agregue el enlace de visita.

¿Por qué 600 caracteres? Probé algunos artículos y me di cuenta de que 600 caracteres son suficientes para que la IA entienda el contexto, pero insuficientes para copiar el artículo tal cual. Experimenta tú mismo, quizás 400 u 800 caracteres se adapten mejor a tu caso de uso.

Enfoques alternativos

Ruta API personalizada (Alex OP)

Alex OP describe un enfoque simple usando una ruta API de Astro:

// src/pages/llms.txt.ts
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";

export const GET: APIRoute = async () => {
  const posts = await getCollection("blog", ({ data }) => !data.draft);
  const sortedPosts = posts.sort(
    (a, b) => new Date(b.data.pubDatetime).valueOf() - new Date(a.data.pubDatetime).valueOf()
  );

  let llmsContent = "";
  for (const post of sortedPosts) {
    llmsContent += `---\ntitle: ${post.data.title}\ndescription: ${post.data.description}\n---\n\n`;
    // ... extracción de contenido
  }

  return new Response(llmsContent, {
    headers: { "Content-Type": "text/plain; charset=utf-8" },
  });
};

Ventajas:

  • Sin dependencias adicionales
  • Control total sobre el formato
  • Funciona también en el servidor de desarrollo

Desventajas:

  • La extracción de contenido debe implementarse manualmente (eliminar componentes MDX, filtrar directivas Shiki-Twoslash, parsear frontmatter)
  • No hay llms-small.txt ni DocSets estructurados
  • Sin modo onlyStructure

Normalmente es una buena solución, pero revisa la página por tu cuenta, podría estar equivocado o malinterpretarlo.

Plugin @4hse/astro-llms-txt

El plugin de 4HSE adopta otro enfoque: lee los archivos HTML generados después del build y los convierte de vuelta a Markdown usando rehype-remark.

Ventajas:

  • Pipeline completa (rehype-parserehype-remarkremark-gfmremark-stringify)
  • Las componentes MDX, Expressive Code y Tabs se procesan automáticamente
  • Modo onlyStructure para una vista compacta
  • DocSets con patrones include, promote, demote y coincidencia de patrones

Desventajas:

  • Solo funciona durante el build, no en el servidor de desarrollo
  • Sin límite de excerpts integrado (yo lo añadí por mi cuenta)

Comparativa

CriterioCustom API Route@4hse/astro-llms-txt
EsfuerzoMedio (lógica propia)Bajo (configuración)
Soporte MDXImplementación manualAutomático
onlyStructureNo disponibleIntegrado
Límite de excerptsImplementación propiaExtensión necesaria
Servidor de desarrolloSolo build
DocSetsNo disponibleCon coincidencia de patrones

Elegí el plugin @4hse/astro-llms-txt porque procesa automáticamente las componentes MDX y los bloques Expressive Code. Con más de 600 artículos, una extracción de contenido propia habría sido propensa a errores. El enfoque de Custom API Route de Alex OP es interesante para blogs pequeños, pero con mi volumen de artículos, el costo de mantenimiento de la extracción de contenido habría sido desproporcionado.

Solución a partir de Astro v7: Script propio

Dado que @4hse/astro-llms-txt aún no es compatible con Astro v7, ahora genero los archivos con un script Node propio. El script se ejecuta después del build de Astro y lee los archivos HTML de dist/, similar a como lo hacía el plugin.

La integración ocurre directamente en el script de build del package.json:

{
  "scripts": {
    "build": "astro build && node scripts/generate-llms-txt.mjs"
  }
}

El script es compacto y sigue la especificación llms.txt:

// scripts/generate-llms-txt.mjs
import { readdir, readFile, writeFile, stat } from 'node:fs/promises';
import { join } from 'node:path';

const SITE = 'https://www.irc-coding.de';
const DIST = './dist';
const EXCERPT_LENGTH = 600;

async function findHtmlFiles(dir, base = '') {
  const entries = await readdir(dir, { withFileTypes: true });
  const files = [];
  for (const entry of entries) {
    const fullPath = join(dir, entry.name);
    const relPath = base ? `${base}/${entry.name}` : entry.name;
    if (entry.isDirectory()) {
      files.push(...await findHtmlFiles(fullPath, relPath));
    } else if (entry.name === 'index.html') {
      files.push({ fullPath, relPath: base || '' });
    }
  }
  return files;
}

function extractText(html) {
  // Elimina etiquetas script/style
  let text = html.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, '');
  // Elimina etiquetas HTML
  text = text.replace(/<[^>]+>/g, ' ');
  // Limpia espacios en blanco
  text = text.replace(/\s+/g, ' ').trim();
  return text;
}

function extractTitle(html) {
  const match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
  return match ? match[1].trim() : 'Unbenannt';
}

async function main() {
  const files = await findHtmlFiles(DIST);
  const pages = [];

  for (const file of files) {
    if (file.relPath.startsWith('404') || file.relPath.startsWith('admin')) continue;
    const html = await readFile(file.fullPath, 'utf-8');
    const title = extractTitle(html);
    const text = extractText(html);
    const excerpt = text.slice(0, EXCERPT_LENGTH);
    const url = file.relPath ? `${SITE}/${file.relPath}/` : `${SITE}/`;
    pages.push({ title, excerpt, url, relPath: file.relPath });
  }

  // llms.txt (Index)
  let llmsTxt = `# ${SITE}\n\n> Software Development and Programming\n\n`;
  llmsTxt += `## Docs\n\n- [Complete site](${SITE}/llms-full.txt): Excerpts of all articles\n- [Compact overview](${SITE}/llms-small.txt): Structure-only index\n`;
  await writeFile(join(DIST, 'llms.txt'), llmsTxt, 'utf-8');

  // llms-full.txt
  let llmsFull = `# ${SITE}\n\n> Software Development and Programming\n\n`;
  for (const page of pages) {
    llmsFull += `\n---\n\n## ${page.title}\n\n${page.excerpt}\n\n[Visit full article](${page.url})\n`;
  }
  await writeFile(join(DIST, 'llms-full.txt'), llmsFull, 'utf-8');

  // llms-small.txt
  let llmsSmall = `# ${SITE}\n\n> Software Development and Programming\n\n`;
  for (const page of pages) {
    llmsSmall += `- [${page.title}](${page.url})\n`;
  }
  await writeFile(join(DIST, 'llms-small.txt'), llmsSmall, 'utf-8');

  console.log(`Generated llms.txt, llms-full.txt, llms-small.txt with ${pages.length} pages`);
}

main().catch(console.error);

Ventajas del script propio:

  • Sin conflictos de peer-dependencies en upgrades de Astro
  • Control total sobre la longitud y formato del excerpt
  • Funciona en cualquier plataforma de deployment (Railway, Vercel, Netlify)
  • Se adapta fácilmente a nuevos requisitos

Desventajas:

  • Sin procesamiento automático de componentes MDX (solo extracción de texto)
  • Sin modo onlyStructure con encabezados anidados
  • El mantenimiento está en tus manos

Para IRC-Coding.de con más de 300 artículos, este enfoque es suficiente. Los sistemas de IA obtienen el contexto, y mantengo la protección de contenido a través de excerptLength.

Errores comunes

  • Error: Expected pattern to be a non-empty string Solución: El plugin usa picomatch internamente. Las cadenas vacías en los arrays promote o include causan este error. Usé ['index', 'blog/**'] en su lugar.

  • Error: File not found: dist/de/index.html Solución: El patrón include: ['**'] coincide con todas las páginas. Si algunas páginas no se generan (por ejemplo, con draft: true), aparece esta advertencia. Es inofensiva, el archivo simplemente se omite.

  • Error: llms-full.txt es demasiado grande Solución: Establecí excerptLength en 600 caracteres. Esto protege mi contenido y mantiene el archivo compacto. Este ajuste requería una pequeña modificación del plugin en node_modules/@4hse/astro-llms-txt/src/index.ts, donde extendí la interfaz DocSet con excerptLength y visitLinkText.

  • Error: Los sistemas de IA no encuentran el archivo Solución: Asegúrate de que llms.txt esté en la raíz de tu dominio (por ejemplo, https://www.irc-coding.de/llms.txt). En deployments en Vercel o Netlify, el archivo se sirve correctamente desde dist/ automáticamente.

¿Detectan ChatGPT o Claude.ai tu llms.txt y vale la pena implementarlo?

Probablemente no, solo ayuda al que copia contenido.

Quedé sorprendido de lo rápido que se podía copiar mi sitio WordPress a Markdown, porque WordPress ya tenía todas las herramientas para hacerlo.

Aquí también es más bien un pensamiento comunitario, pero las IA generalmente se entrenan de otra forma.

Si le preguntas a ChatGPT, obtienes algo como esto:

Para mí como ChatGPT en este chat, un llms.txt no tiene una ventaja directa, porque no simplemente rastro cualquier sitio web en vivo y busco un llms.txt cuando respondo. Si no accedo a tu sitio web mediante búsqueda web o a través de un enlace, no veo el archivo en absoluto.

Pero si accedo a información de tu sitio mediante acceso web, entonces un llms.txt bien estructurado ciertamente puede ser útil, especialmente si:

- lista documentaciones importantes de manera clara,
- contiene buenos títulos y descripciones,
- apunta a páginas relevantes,
- e idealmente proporciona un llms-full.txt o contenido estructurado similar.

Para otros sistemas de IA

El potencial beneficio aquí es actualmente mayor. Sistemas y herramientas que soportan específicamente llms.txt pueden:

- encontrar documentación más rápidamente,
- priorizar contenido mejor,
- buscar menos páginas irrelevantes,
- y acceder más eficientemente a una documentación de API o proyecto.

¿Para SEO?

En el sentido clásico (ranking de Google) no hay beneficios comprobados actualmente.

Vería llms.txt más como:

- robots.txt → para crawlers,
- sitemap.xml → para motores de búsqueda,
- llms.txt → para sistemas impulsados por IA

Cumplen tareas diferentes.

No lo consideraría una medida de SEO, sino como una inversión en la detectabilidad y usabilidad de tu contenido para herramientas impulsadas por IA. Si llms.txt se establece más, ya estarás bien posicionado; si no, el trabajo adicional de una generación automática generalmente es manejable.

¿Por qué lo implementé de todos modos?

Bueno, el sitio me sirvió para aprender el material, y cada visitante adicional me hace feliz. Y la esperanza muere al último.

Preguntas frecuentes sobre llms.txt en Astro

1. ¿Qué es llms.txt?

Un archivo de texto en el directorio raíz de un sitio web que proporciona contenido en formato legible por máquinas para motores de búsqueda con IA. La especificación está disponible en llmstxt.org.

2. ¿En qué se diferencia llms.txt de robots.txt?

robots.txt controla el rastreo del contenido. llms.txt entrega el contenido de forma simplificada a los modelos de IA.

3. ¿Necesito llms.txt para SEO?

No para SEO tradicional, sino para motores de búsqueda con IA como ChatGPT, Perplexity y Claude.

4. ¿Qué archivos genera el plugin?

Tres: llms.txt (índice), llms-full.txt (contenido en Markdown) y llms-small.txt (solo estructura).

5. ¿Funciona el plugin en el servidor de desarrollo?

No, utiliza el hook astro:build:done y lee HTML desde dist/. Solo funciona después de un build.

6. ¿Puedo crear llms.txt sin el plugin?

Sí, usando una ruta API personalizada. Con muchos artículos MDX, el esfuerzo de mantenimiento es considerable.

7. ¿Cómo protejo mi contenido contra copias?

Establece excerptLength en 600 caracteres. llms-full.txt contendrá solo fragmentos con enlaces a la página original.

8. ¿Qué significa onlyStructure?

Genera solo encabezados y listas sin contenido. Ideal para llms-small.txt.

9. ¿Cómo configuro promote y demote?

promote ordena páginas hacia arriba, demote hacia abajo. Usa patrones glob como blog/**. Sin cadenas vacías.

10. ¿Dónde debe estar llms.txt?

En el directorio raíz del dominio, por ejemplo https://www.tu-sitio.es/llms.txt.

11. ¿Es llms.txt un estándar oficial?

Estándar comunitario, no es un estándar W3C ni IETF. La especificación está en llmstxt.org.

12. ¿Puedo excluir subsecciones?

Sí, usando patrones include en DocSets, por ejemplo incluir solo blog/**.

13. ¿Cuánto cuesta el plugin?

Open Source y gratuito. El código fuente está disponible en github.com/4hse/astro-llms-txt.

14. ¿Cuántos caracteres para excerptLength?

600 caracteres es un buen valor. La IA comprende el contexto, pero no puede copiar el artículo completo.

15. ¿Funciona llms.txt con SSG y SSR?

Con SSG sí, los archivos se generan durante el build. Con SSR el plugin no funciona porque lee archivos HTML listos desde dist/.

16. ¿Qué hago con Astro v7?

El plugin @4hse/astro-llms-txt 1.0.5 no es compatible con Astro v7 (conflicto de Peer-Dependency). Elimina el plugin y genera los archivos con un script Node personalizado después del build. El script lee los archivos HTML desde dist/ y crea llms.txt, llms-full.txt y llms-small.txt.

Referencias y fuentes

Volver al blog
Share:

Entradas relacionadas