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

Добавляем llms.txt в Astro-блог

Пошаговая инструкция по интеграции llms.txt, llms-full.txt и llms-small.txt в Astro с плагином @4hse/astro-llms-txt.

S

schutzgeist

10 min read
Добавляем llms.txt в Astro-блог

Добавляем llms.txt на блог на Astro, пошаговое руководство

Исходная ситуация

Я разработчик приложений и веду IRC-Coding.de с более чем 600 статьями о разработке программного обеспечения и программировании. AI-поисковые системы вроде ChatGPT, Perplexity и Claude сканируют веб-сайты и пытаются понять их содержимое. При этом они борются с HTML-разметкой, JavaScript-рендерингом и лишним кодом.

Спецификация llms.txt решает эту проблему: простой текстовый файл в корневой директории сайта, который предоставляет содержимое в машиночитаемом формате.

При внедрении я столкнулся со следующими вопросами:

  • Как генерировать llms.txt автоматически при сборке?
  • Какие файлы мне нужны (llms.txt, llms-full.txt, llms-small.txt)?
  • Как защитить мой контент от простого копирования мошенниками?

Я изучил два популярных подхода: custom API Route (как описывает Alex OP) и плагин @4hse/astro-llms-txt (см. GitHub). В этой статье я расскажу, как я оба сравнил и какое решение выбрал для IRC-Coding.de.

Кстати, сайт Alex OP действительно стоит посмотреть. Я нашел его через поиск Google по запросу llms.txt Astro.

Важно: несовместимость с Astro v7

⚠️ Внимание при обновлении на Astro v7.0: Плагин @4hse/astro-llms-txt версии 1.0.5 поддерживает только до Astro v6 (peer astro@“^5.1.6 || ^6.0.0”). При Astro v7 установка через npm install завершается с ошибкой ERESOLVE. Это особенно актуально для развертывания на Railway, Vercel или Netlify, которые используют npm вместо pnpm.

Решение: Удалите плагин перед обновлением на Astro v7 и генерируйте файлы llms.txt собственным скриптом. Pull request для поддержки Astro v7 существует в конкурирующем плагине starlight-llms-txt, но @4hse/astro-llms-txt еще не выпустил совместимый релиз.

Решение

Шаг 1: установка плагина (до Astro v6)

npm install @4hse/astro-llms-txt

Плагин использует хук Astro astro:build:done, читает сгенерированные HTML-файлы из директории dist/ и преобразует их в Markdown с помощью rehype-remark. Это означает: мне не нужно писать собственные парсеры или логику извлечения контента.

Шаг 2: настройка конфигурации Astro

В своем astro.config.mjs я добавил плагин к интеграциям:

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

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

  integrations: [
    // ... другие интеграции
    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',
    }),
  ],
});

Шаг 3: запуск сборки

npm run build

После сборки я нашел три файла в dist/:

  • llms.txt: индекс с названием, описанием и ссылками на наборы документов
  • llms-full.txt: содержимое всех страниц в формате Markdown
  • llms-small.txt: только структура (заголовки и списки)

Шаг 4: ограничение отрывков для защиты контента

По умолчанию плагин генерирует полное содержимое в llms-full.txt. При моих 600 статьях получился файл с более чем 140.000 строк, что является настоящим подарком для контент-воров. Каждый мог открыть https://www.irc-coding.de/llms-full.txt и скопировать весь текст всех статей.

Я расширил плагин. Опция excerptLength ограничивает каждую статью первыми 600 символами. В конце появляется ссылка на оригинальную страницу:

# Algorithmus einfach erklärt

> Algorithmus verständlich erklärt: Eigenschaften, Entwurfsparadigmen...

## Definition

Ein Algorithmus ist eine endliche Folge von wohldefinierten Anweisungen...

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

AI-поисковые системы получают достаточно контекста, чтобы понять, о чем идет речь. Полная статья доступна только на самом сайте. Для этого я расширил интерфейс DocSet в плагине и модифицировал функцию buildEntryFromHtml так, чтобы она обрезала контент на excerptLength символах и добавляла ссылку для посещения.

Почему 600 символов? Я протестировал несколько статей и заметил: 600 символов достаточно, чтобы AI понял контекст, но недостаточно для простого копирования статьи 1:1. Попробуйте сами, может быть для вашего случая подойдут и 400, или 800.

Альтернативные подходы

Custom API Route (Alex OP)

Alex OP описывает простой подход с использованием Astro API Route:

// 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`;
    // ... извлечение контента
  }

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

Преимущества:

  • Без дополнительных зависимостей
  • Полный контроль над форматированием
  • Работает даже на dev-сервере

Недостатки:

  • Извлечение контента нужно реализовать самому (удалять MDX-компоненты, фильтровать директивы Shiki-Twoslash, парсить frontmatter)
  • Нет llms-small.txt и структурированных наборов документов
  • Нет режима onlyStructure

Обычно это хороший подход, но посмотрите на сайт сами, я могу ошибаться или неправильно что-то понять.

Плагин @4hse/astro-llms-txt

Этот плагин от 4HSE работает иначе. Он читает готовые HTML-файлы после сборки и преобразует их обратно в Markdown с помощью rehype-remark.

Плюсы:

  • Полный пайплайн (rehype-parserehype-remarkremark-gfmremark-stringify)
  • MDX-компоненты, Expressive Code и табы обрабатываются автоматически
  • Режим onlyStructure для компактного представления
  • DocSets с паттерн-матчингом include, promote, demote

Минусы:

  • Работает только при сборке (не на dev-сервере)
  • Нет встроенного лимита на размер выписки (я добавил его самостоятельно)

Сравнение

КритерийCustom API Route@4hse/astro-llms-txt
УсилияСреднее (своя логика)Минимальное (конфиг)
Поддержка MDXНужно реализовать вручнуюАвтоматическая
onlyStructureОтсутствуетВстроен
Лимит выпискиРеализовать самомуРасширить самому
Dev-серверДаТолько сборка
DocSetsОтсутствуютС паттерн-матчингом

Я выбрал @4hse/astro-llms-txt, потому что он автоматически обрабатывает MDX-компоненты и блоки Expressive Code. С более чем 600 статей собственная экстракция контента была бы слишком подвержена ошибкам. Подход Alex OP с custom API route интересен для небольших блогов, но при таком количестве статей затраты на поддержку экстракции контента стали бы непомерными.

Решение для Astro v7: собственный скрипт

Поскольку @4hse/astro-llms-txt пока несовместим с Astro v7, я генерирую файлы с помощью собственного Node-скрипта. Он запускается после сборки Astro и читает HTML-файлы из dist/, как это делал плагин.

Интеграция прямо в package.json:

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

Сам скрипт компактен и следует спецификации 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) {
  // Удаляем теги script и style
  let text = html.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, '');
  // Удаляем HTML-теги
  text = text.replace(/<[^>]+>/g, ' ');
  // Очищаем пробелы
  text = text.replace(/\s+/g, ' ').trim();
  return text;
}

function extractTitle(html) {
  const match = html.match(/<title[^>]*>([^<]+)<\/title>/i);
  return match ? match[1].trim() : 'Без названия';
}

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 (индекс)
  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);

Плюсы собственного скрипта:

  • Нет конфликтов peer-зависимостей при обновлении Astro
  • Полный контроль над длиной выписки и форматом
  • Работает на любой платформе деплоя (Railway, Vercel, Netlify)
  • Легко адаптировать под новые требования

Минусы:

  • Нет автоматической обработки MDX-компонентов (только текст)
  • Нет режима onlyStructure с вложенными заголовками
  • Поддержка лежит на тебе

Для IRC-Coding.de с более чем 300 статей этого подхода достаточно. KI-системы получают контекст, а защита контента через excerptLength остаётся на месте.

Частые ошибки

  • Ошибка: Expected pattern to be a non-empty string Решение: Плагин использует picomatch. Пустые строки в массивах promote или include вызывают эту ошибку. Вместо этого я использовал ['index', 'blog/**'].

  • Ошибка: File not found: dist/de/index.html Решение: Паттерн include: ['**'] совпадает со всеми страницами. Если какие-то страницы не генерируются (например, через draft: true), появляется это предупреждение. Оно безобидно, файл просто пропускается.

  • Ошибка: llms-full.txt слишком большой Решение: Я установил excerptLength на 600 символов. Это защищает мой контент и держит файл компактным. Для этого потребовалось небольшое изменение плагина в node_modules/@4hse/astro-llms-txt/src/index.ts, где я расширил интерфейс DocSet свойствами excerptLength и visitLinkText.

  • Ошибка: KI-системы не находят файл Решение: Убедись, что llms.txt находится в корне вашего домена (например, https://www.irc-coding.de/llms.txt). При деплоях на Vercel или Netlify файл из dist/ автоматически доставляется корректно.

Действительно ли ChatGPT или Claude.ai учитывают твой llms.txt и стоит ли тратить на это время?

Вероятно, нет. Это скорее помогает тем, кто копирует контент.

Я был шокирован, насколько быстро можно скопировать WordPress-сайт в Markdown, потому что WordPress уже содержит все необходимые инструменты.

Здесь это скорее движение сообщества, но KI-системы обучаются иначе.

Когда я спросил ChatGPT, вот что он ответил:

Для меня как ChatGPT в этом чате llms.txt не дает прямых преимуществ, потому что я не просто краулю произвольные сайты в реальном времени и не ищу llms.txt при ответе. Если ты не вызываешь поиск в вебе или не предоставляешь мне ссылку, я файл вообще не вижу.

Но если я получаю информацию с твоего сайта через веб-доступ, хорошо структурированный llms.txt действительно может помочь, особенно если он:

содержит важную документацию в понятном виде,
имеет хорошие заголовки и описания,
ссылается на релевантные страницы,
и предоставляет структурированный контент типа llms-full.txt.

Для других KI-систем

Потенциальная польза здесь больше. Системы и инструменты, которые специально поддерживают llms.txt, могут:

быстрее находить документацию,
лучше расставлять приоритеты контента,
меньше искать нерелевантные страницы,
и эффективнее использовать API или документацию проекта.

Для SEO?

В классическом смысле (ранжирование в Google) пока нет подтвержденных преимуществ.

Я бы рассматривал llms.txt скорее так:

robots.txt → для краулеров,
sitemap.xml → для поисковых машин,
llms.txt → для KI-систем.

Они выполняют разные задачи.

Но я не считаю это мерой SEO, а скорее инвестицией в то, чтобы твой контент был проще найти и использовать KI-инструментам. Если llms.txt получит широкое распространение, ты уже будешь готов. Если нет, дополнительные затраты при автоматизированной генерации обычно минимальны.

Почему я всё равно это внедрил?

Сайт служит мне для обучения, и каждый новый посетитель радует меня. И надежда умирает последней.

FAQ llms.txt для Astro

1. Что такое llms.txt?

Текстовый файл в корневой директории сайта, который предоставляет контент в машиночитаемом формате для AI-поисковиков. Спецификация на llmstxt.org.

2. Чем llms.txt отличается от robots.txt?

robots.txt управляет процессом краулинга. llms.txt предоставляет контент в упрощённом виде для AI-моделей.

3. Нужна ли мне llms.txt для SEO?

Не для классического SEO, а для AI-поисковиков вроде ChatGPT, Perplexity и Claude.

4. Какие файлы генерирует плагин?

Три: llms.txt (индекс), llms-full.txt (контент в формате Markdown), llms-small.txt (только структура).

5. Работает ли плагин на dev-сервере?

Нет, плагин использует hook astro:build:done и читает HTML из папки dist/. Работает только после сборки.

6. Могу ли я создать llms.txt без плагина?

Да, через собственный API route. Но с большим количеством MDX-статей расходы на поддержку будут значительными.

7. Как защитить контент от копирования?

Установи excerptLength на 600 символов. Тогда llms-full.txt будет содержать только отрывки со ссылками на исходную страницу.

8. Что означает onlyStructure?

Генерирует только заголовки и списки без текстового контента. Подходит для llms-small.txt.

9. Как настроить promote и demote?

promote передвигает страницы вверх в списке, demote опускает их вниз. Используй glob-паттерны, например blog/**. Не допускай пустых строк.

10. Где должна находиться llms.txt?

В корневой директории домена, например https://www.deine-seite.de/llms.txt.

11. Является ли llms.txt официальным стандартом?

Это стандарт сообщества, но не стандарт W3C или IETF. Спецификация находится на llmstxt.org.

12. Могу ли я исключить некоторые подстраницы?

Да, через include-паттерны в DocSets, например подключив только blog/**.

13. Сколько стоит плагин?

Open Source и бесплатный. Исходный код на github.com/4hse/astro-llms-txt.

14. Сколько символов использовать для excerptLength?

600 символов это хороший выбор. AI поймёт контекст, но не сможет скопировать целую статью.

15. Работает ли llms.txt с SSG и SSR?

С SSG да, файлы генерируются во время сборки. С SSR плагин не работает, потому что он читает готовые HTML-файлы из папки dist/.

16. Что делать с Astro v7?

Плагин @4hse/astro-llms-txt 1.0.5 несовместим с Astro v7 из-за конфликта зависимостей. Удали плагин и генерируй файлы с помощью собственного Node-скрипта после сборки. Скрипт читает HTML-файлы из папки dist/ и создаёт llms.txt, llms-full.txt и llms-small.txt.

Источники статьи

Назад к блогу
Share:

Nächster Artikel in Веб-разработка

Weiterlesen
Веб-разработка: Frontend, Backend и Frameworks

Похожие статьи