fix: устранить проблемы доступности llms.txt для AI-агентов
- Caddyfile: text/plain применяется только к существующим .txt/.md - Caddyfile: несуществующие .txt/.md отдают 404 вместо HTML-фолбэка - Caddyfile: добавлены редиректы /docs/llms*.txt → /llms*.txt - Caddyfile: добавлен HTTP-заголовок Link с rel="llms" (RFC 8288) - лендинг: относительные ссылки на llms.txt заменены на абсолютные - лендинг: добавлено явное упоминание путей в тексте карточки - config: добавлены <link rel="alternate"> на llms.txt в <head> - generate-llms: добавлена генерация robots.txt и sitemap.xml
This commit is contained in:
@@ -127,6 +127,13 @@ export default defineConfig({
|
|||||||
title: 'NextJS Style Guide',
|
title: 'NextJS Style Guide',
|
||||||
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
|
||||||
|
|
||||||
|
// Дублируем указатель на llms.txt в <head> — для агентов,
|
||||||
|
// которые читают HTML, но не парсят полный DOM/href.
|
||||||
|
head: [
|
||||||
|
['link', { rel: 'alternate', type: 'text/plain', href: '/llms.txt', title: 'llms.txt' }],
|
||||||
|
['link', { rel: 'alternate', type: 'text/plain', href: '/llms-full.txt', title: 'llms-full.txt' }],
|
||||||
|
],
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [utf8TextPlugin],
|
plugins: [utf8TextPlugin],
|
||||||
define: {
|
define: {
|
||||||
|
|||||||
32
Caddyfile
32
Caddyfile
@@ -1,10 +1,34 @@
|
|||||||
:8080 {
|
:8080 {
|
||||||
root * /srv
|
root * /srv
|
||||||
|
|
||||||
# Кириллица в .txt и .md ломается без явного charset
|
# Устаревшие пути llms.txt в подпапках → корень.
|
||||||
@text path *.txt *.md
|
# Без этого опечатка `/docs/llms.txt` уходит в SPA-фолбэк и
|
||||||
header @text Content-Type "text/plain; charset=utf-8"
|
# отдаёт HTML под видом text/plain — агент верит, что получил llms.txt.
|
||||||
|
redir /docs/llms.txt /llms.txt 301
|
||||||
|
redir /docs/llms-full.txt /llms-full.txt 301
|
||||||
|
|
||||||
|
# Подсказка агентам, где лежит карта документации (RFC 8288).
|
||||||
|
# Позволяет найти llms.txt без парсинга DOM — по HTTP-заголовку.
|
||||||
|
header Link "</llms.txt>; rel=\"llms\""
|
||||||
|
|
||||||
|
# Кириллица в .txt/.md ломается без явного charset.
|
||||||
|
# Применяем заголовок только к РЕАЛЬНО существующим файлам,
|
||||||
|
# иначе SPA-фолбэк (HTML) уезжает с Content-Type: text/plain.
|
||||||
|
@existingText {
|
||||||
|
path *.txt *.md
|
||||||
|
file
|
||||||
|
}
|
||||||
|
header @existingText Content-Type "text/plain; charset=utf-8"
|
||||||
|
|
||||||
|
# Несуществующие .txt/.md → 404, не HTML-фолбэк.
|
||||||
|
# Это критично для llms.txt: агент должен получить честный 404,
|
||||||
|
# а не валидный «как бы текст» с лендингом внутри.
|
||||||
|
@missingText {
|
||||||
|
path *.txt *.md
|
||||||
|
not file
|
||||||
|
}
|
||||||
|
respond @missingText 404
|
||||||
|
|
||||||
file_server
|
file_server
|
||||||
try_files {path} /index.html
|
try_files {path} {path}/ /index.html
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,10 +90,13 @@ function toggleTheme(value) {
|
|||||||
</a>
|
</a>
|
||||||
<div class="landing__card landing__card--multi">
|
<div class="landing__card landing__card--multi">
|
||||||
<h3>Ассистенту</h3>
|
<h3>Ассистенту</h3>
|
||||||
<p>Карта документации в формате llms.txt для AI-агентов.</p>
|
<p>
|
||||||
|
Карта документации для AI-агентов:
|
||||||
|
<code>/llms.txt</code>, <code>/llms-full.txt</code>.
|
||||||
|
</p>
|
||||||
<div class="landing__buttons">
|
<div class="landing__buttons">
|
||||||
<a class="landing__button" href="./llms.txt" target="_blank" rel="noopener">llms.txt</a>
|
<a class="landing__button" href="/llms.txt" target="_blank" rel="noopener">llms.txt</a>
|
||||||
<a class="landing__button" href="./llms-full.txt" target="_blank" rel="noopener">llms-full.txt</a>
|
<a class="landing__button" href="/llms-full.txt" target="_blank" rel="noopener">llms-full.txt</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="landing__card" href="./nextjs-style-guide.zip">
|
<a class="landing__card" href="./nextjs-style-guide.zip">
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ const PUBLIC_DIR = 'docs/public';
|
|||||||
/** Префикс URL документации. Соответствует структуре `docs/docs/...`. */
|
/** Префикс URL документации. Соответствует структуре `docs/docs/...`. */
|
||||||
const DOC_PREFIX = '/docs/';
|
const DOC_PREFIX = '/docs/';
|
||||||
|
|
||||||
|
/** Канонический хост сайта (для sitemap/robots). Можно переопределить через ENV. */
|
||||||
|
const SITE_URL = (process.env.SITE_URL || 'https://nextjs-style-guide.gromlab.ru').replace(/\/$/, '');
|
||||||
|
|
||||||
interface SidebarItem {
|
interface SidebarItem {
|
||||||
text: string;
|
text: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
@@ -488,6 +491,63 @@ const writeManifest = (): void => {
|
|||||||
console.log(`${PUBLIC_DIR}/manifest.json создан`);
|
console.log(`${PUBLIC_DIR}/manifest.json создан`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать `robots.txt` с указанием sitemap и явными ссылками
|
||||||
|
* на llms.txt/llms-full.txt — стандартные файлы, которые читают агенты.
|
||||||
|
*/
|
||||||
|
const buildRobots = (): void => {
|
||||||
|
const lines = [
|
||||||
|
'User-agent: *',
|
||||||
|
'Allow: /',
|
||||||
|
'',
|
||||||
|
`Sitemap: ${SITE_URL}/sitemap.xml`,
|
||||||
|
'',
|
||||||
|
'# Карта документации для AI-агентов:',
|
||||||
|
`# ${SITE_URL}/llms.txt`,
|
||||||
|
`# ${SITE_URL}/llms-full.txt`,
|
||||||
|
'',
|
||||||
|
];
|
||||||
|
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(PUBLIC_DIR, 'robots.txt'), lines.join('\n'), 'utf8');
|
||||||
|
console.log(`${PUBLIC_DIR}/robots.txt создан`);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать `sitemap.xml` из sidebar + корневые ресурсы для LLM
|
||||||
|
* (llms.txt, llms-full.txt) — чтобы агенты, читающие sitemap, видели их.
|
||||||
|
*/
|
||||||
|
const buildSitemap = (): void => {
|
||||||
|
const sidebar = cfg.themeConfig.sidebar;
|
||||||
|
const entries = flattenSidebar(sidebar);
|
||||||
|
|
||||||
|
const urls = new Set<string>();
|
||||||
|
urls.add(`${SITE_URL}/`);
|
||||||
|
urls.add(`${SITE_URL}/llms.txt`);
|
||||||
|
urls.add(`${SITE_URL}/llms-full.txt`);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const link = entry.link;
|
||||||
|
// VitePress отдаёт страницы как HTML; для index — каталог со слешем.
|
||||||
|
const url = link.endsWith('/') ? `${SITE_URL}${link}` : `${SITE_URL}${link}.html`;
|
||||||
|
urls.add(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = BUILD_DATE.slice(0, 10);
|
||||||
|
const xml = [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
||||||
|
...[...urls].map(
|
||||||
|
(loc) => ` <url><loc>${loc}</loc><lastmod>${today}</lastmod></url>`,
|
||||||
|
),
|
||||||
|
'</urlset>',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(PUBLIC_DIR, 'sitemap.xml'), xml, 'utf8');
|
||||||
|
console.log(`${PUBLIC_DIR}/sitemap.xml создан`);
|
||||||
|
};
|
||||||
|
|
||||||
/** Скопировать `index.md` документации в корневой README без frontmatter. */
|
/** Скопировать `index.md` документации в корневой README без frontmatter. */
|
||||||
const buildReadme = (): void => {
|
const buildReadme = (): void => {
|
||||||
const indexPath = 'docs/docs/index.md';
|
const indexPath = 'docs/docs/index.md';
|
||||||
@@ -506,4 +566,6 @@ buildLlmsFull();
|
|||||||
copyMdFiles();
|
copyMdFiles();
|
||||||
buildZip();
|
buildZip();
|
||||||
writeManifest();
|
writeManifest();
|
||||||
|
buildRobots();
|
||||||
|
buildSitemap();
|
||||||
buildReadme();
|
buildReadme();
|
||||||
|
|||||||
Reference in New Issue
Block a user