Всё больше пользователей вообще не открывают ваш сайт в браузере. Они спрашивают ChatGPT, Claude или Gemini, а эти AI агенты ходят по URL вашего сайта, чтобы достать ответ. Но у агента ограничен токен-бюджет — и всякие cookie-баннеры, мегаменю и 40 КБ скриптов гидрации им ни к чему. Они хотят чистый, понятный текст.
Я запускал AskedTheAI — сервис, который задаёт один и тот же вопрос четырём AI-моделям и проверяет ответы вручную на Amazon. Логично, что AI-агенты должны без проблем читать сайт. Поэтому перед релизом я сделал четыре вещи:
- Генерирую Markdown-версию каждой страницы из результата билда;
- Настраиваю Content Negotiation — тот же URL отдаёт Markdown, если агент запросит его через Accept;
- Создаю llms.txt и llms-full.txt — карты сайта для AI;
- Настраиваю IndexNow пинги, чтобы Bing и Яндекс моментально узнавали про изменения.
Технически это Next.js 16 с App Router, полностью статический, деплоится на Vercel. Ниже делюсь реальным кодом и своими соображениями.
Markdown — не просто рендерить из React
Поначалу хотелось рендерить Markdown прямо из React-компонентов. Но это ловушка. Каждая страница — это своя композиция, со своим состоянием и данными. Поддерживать два рендерера — React и Markdown — быстро станет головной болью и источником багов.
Вместо этого я беру HTML, который Next.js уже сгенерировал в .next/server/app после команды next build. Там лежат готовые статические страницы. Скрипт на Node.js проходит по этим файлам, вытаскивает содержимое <main>, превращает его в Markdown с помощью node-html-markdown и кладёт в public/md/<route>.md.
// scripts/md/generate.mjs (фрагмент)
const APP = '.next/server/app'
const OUT = 'public/md'
const SKIP = /^\/(api|_not-found|_global-error|404|500|examples|search|templates|md)(\/|$)/
const nhm = new NodeHtmlMarkdown({
ignore: ['script', 'style', 'noscript', 'svg', 'button', 'form', 'iframe', 'video', 'template'],
maxConsecutiveNewlines: 2,
useLinkReferenceDefinitions: false,
})
// ...walk по APP, извлечение <main>, конвертация nhm.translate() и запись в OUT
Каждый Markdown-файл начинается с фронтматтер-блока, который объясняет AI, что за страница, откуда она и когда обновлялась:
---
title: "How We Ask the AI: Our Research Method | AskedTheAI"
url: https://www.askedtheai.com/ai-research
description: "How AskedTheAI puts the same buying question to Claude, ChatGPT, Gemini and Grok..."
last_updated: 2026-10-01
source: AskedTheAI (https://www.askedtheai.com)
affiliate_disclosure: As an Amazon Associate we earn from qualifying purchases.
---
Так AI сразу понимает, что это за контент и как его интерпретировать.
Content Negotiation через middleware
Дальше нужно, чтобы по одному и тому же URL отдавалась либо HTML, либо Markdown, в зависимости от заголовка Accept в запросе. Для этого я написал middleware:
- Проверяю, есть ли для текущего route Markdown-версия (из
md-routes.json); - Если в заголовке
Acceptестьtext/markdownи он стоит раньшеtext/html, отдаю Markdown (переписываю запрос на/md/...); - Иначе — отдаю обычный HTML, но с заголовком
Link: rel="alternate"указываю на Markdown-альтернативу.
// src/middleware.ts (ключевые места)
function prefersMarkdown(accept: string): boolean {
if (!/text\/markdown/i.test(accept)) return false
const first = accept.split(',')[0]?.trim().toLowerCase() ?? ''
return first.startsWith('text/markdown') || !/text\/html/i.test(accept)
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const route = pathname.length > 1 ? pathname.replace(/\/$/, '') : '/'
if (!MD_ROUTES.has(route)) return NextResponse.next()
const mdPath = mdPathFor(route)
if (prefersMarkdown(request.headers.get('accept') || '')) {
const url = request.nextUrl.clone()
url.pathname = mdPath
const res = NextResponse.rewrite(url)
res.headers.set('Vary', 'Accept')
res.headers.set('Content-Type', 'text/markdown; charset=utf-8')
res.headers.set('X-Robots-Tag', 'noindex, follow')
return res
}
const res = NextResponse.next()
res.headers.set('Link', `<${SITE}${mdPath}>; rel="alternate"; type="text/markdown"`)
res.headers.set('Vary', 'Accept')
return res
}
Несколько важных деталей:
Vary: Acceptнужен, чтобы CDN не путал и не отдавал Markdown браузерам;X-Robots-Tag: noindex, follow— зеркальная Markdown-версия не индексируется поисковиками, чтобы не было дублей;Link: rel="alternate"помогает AI агентам узнать, что есть облегчённая версия.
llms.txt и llms-full.txt — карты сайта для AI
AI-модели ищут, что читать, и как ориентироваться по сайту. Для этого есть llms.txt и llms-full.txt — обычные Markdown-файлы в корне сайта.
В llms.txt — краткий список разделов и главных страниц с короткими описаниями. В llms-full.txt — развёрнутый индекс со всеми страницами и их Markdown-URL.
Важно прямо с начала сказать, что сайт НЕ делает, чтобы AI не строил неверных ожиданий. Например, мой файл указывает, что мы не тестируем продукты в лаборатории, а только проводим обзоры.
Процесс генерации этих файлов я встроил в тот же скрипт, что и создание Markdown-миража, — чтобы всё всегда было в синхронизации и не требовало поддержки.
IndexNow — мгновенное оповещение поисковиков
Google не поддерживает IndexNow, но Bing, Яндекс, Seznam и Naver — да. Это API, куда можно отправить список URL, чтобы поисковики быстрее узнали об изменениях.
У меня в проекте скрипт, который читает sitemap.xml, проверяет ключ, размещённый публично, и отправляет до 10 000 URL за запрос.
// scripts/indexnow-ping.mjs (упрощённо)
const keyRes = await fetch(`${ORIGIN}/${KEY}.txt`)
if (!keyRes.ok || (await keyRes.text()).trim() !== KEY) {
console.error(`[indexnow] key file not live (HTTP ${keyRes.status}); deploy first`)
process.exit(1)
}
const xml = await (await fetch(`${ORIGIN}/sitemap.xml`)).text()
const urls = [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map(m => m[1].trim())
const res = await fetch('https://api.indexnow.org/indexnow', {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify({ host: HOST, key: KEY, keyLocation: `${ORIGIN}/${KEY}.txt`, urlList: urls }),
})
Первый запрос IndexNow может вернуть ошибку SiteVerificationNotCompleted. Это нормально — проверка ключа идёт асинхронно. Через пару минут всё работает.
Практические нюансы и подводные камни
-
Markdown-мираж и диффы. Если на сайте есть динамические элементы с датами или случайным выбором, генерируемые Markdown-файлы будут менять дату обновления или контент, хотя по сути текст не изменился. В итоге
git diffшумит. Я просто коммичу реальные изменения и откатываю остальное. -
Настройка ESLint. В Next.js 16 билд не запускает ESLint. Поэтому у меня деплой — это
npm run lint && npm run build && npm run ai:files. -
Middleware имя. В Next.js 16 middleware переименовали в proxy.ts, но старое имя ещё работает. Я переключусь после релиза, чтобы не ломать роутинг в последний момент.
-
Кэширование. Vercel кэширует HTML и Markdown по разным ключам, потому что Markdown отдаётся через rewrite на
/md/.... Это важно, чтобы не было путаницы. -
robots.txt. Я явно разрешаю AI-ботам (ChatGPT-User, ClaudeBot и др.) сканировать сайт, а также указываю, что можно использовать контент для обучения. Это бизнес-решение, но для моего проекта логичное.
Итоги: кому это полезно и что дальше
Если ваш сайт — контентный, с большим числом статей или знаний, и вы хотите, чтобы AI-ассистенты могли читать его напрямую, а не через посредников, — этот подход стоит попробовать.
Плюсы:
- Никакого второго источника контента — Markdown зеркалит HTML-билд;
- AI получает чистый, «лёгкий» текст без мусора;
- Можно гибко управлять доступом через Accept и robots.txt;
- Быстрая индексация через IndexNow.
Минусы и риски:
- Если контент сильно динамичный или зависит от состояния, зеркалирование может быть сложным;
- Нужно контролировать кеширование и заголовки — иначе браузеры и CDNs могут спутать Markdown и HTML;
- Дополнительный скрипт и pipeline — хоть и простой, но надо поддерживать.
Что попробовать дальше:
- Добавить RAG (retrieval-augmented generation) поверх Markdown, чтобы AI мог локально вытаскивать релевантные куски;
- Экспериментировать с llms.txt, делая его более «agentic» — с инструкциями для агентов по навигации и логике;
- Подключить мониторинг запросов Accept, чтобы понять, кто и как использует Markdown.
Если вы тоже хотите, чтобы ваш Next.js сайт был дружелюбен к AI, но не хотите городить отдельные CMS или API — этот рецепт реально работает и почти не требует поддержки. Пишите, если нужна помощь с генератором или middleware — поделюсь кодом и опытом.
Попробуй сам: DigitalOcean — $200 кредитов для новых пользователей.