Как сделать Next.js сайт читаемым для AI агентов: llms.txt, Markdown-мираж и Accept: text/markdown

#next.js#AI coding#middleware#markdown#agentic development

Всё больше пользователей вообще не открывают ваш сайт в браузере. Они спрашивают ChatGPT, Claude или Gemini, а эти AI агенты ходят по URL вашего сайта, чтобы достать ответ. Но у агента ограничен токен-бюджет — и всякие cookie-баннеры, мегаменю и 40 КБ скриптов гидрации им ни к чему. Они хотят чистый, понятный текст.

Я запускал AskedTheAI — сервис, который задаёт один и тот же вопрос четырём AI-моделям и проверяет ответы вручную на Amazon. Логично, что AI-агенты должны без проблем читать сайт. Поэтому перед релизом я сделал четыре вещи:

Технически это 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:

// 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
}

Несколько важных деталей:

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. Это нормально — проверка ключа идёт асинхронно. Через пару минут всё работает.

Практические нюансы и подводные камни

Итоги: кому это полезно и что дальше

Если ваш сайт — контентный, с большим числом статей или знаний, и вы хотите, чтобы AI-ассистенты могли читать его напрямую, а не через посредников, — этот подход стоит попробовать.

Плюсы:

Минусы и риски:

Что попробовать дальше:


Если вы тоже хотите, чтобы ваш Next.js сайт был дружелюбен к AI, но не хотите городить отдельные CMS или API — этот рецепт реально работает и почти не требует поддержки. Пишите, если нужна помощь с генератором или middleware — поделюсь кодом и опытом.


Попробуй сам: DigitalOcean — $200 кредитов для новых пользователей.


Источник: https://dev.to/olivia_342fsfsdgrere/making-a-nextjs-site-readable-by-ai-agents-llmstxt-a-markdown-mirror-and-accept-textmarkdown-38e3