@webnumseoagent/next
v0.12.0
Published
Runtime SEO adapter + installer for Next.js App Router sites (SEO Agent platform)
Maintainers
Readme
@webnumseoagent/next
Рантайм-адаптер SEO для любого сайта на Next.js (App Router). Тянет SEO-конфиг
(метаданные, hreflang, sitemap, robots, JSON-LD) из платформы SEO Agent по
site_id + токен. Правки в платформе применяются на сайте без правок кода (через ISR).
Быстрая установка (одна команда)
В корне репозитория сайта (рабочее дерево git должно быть чистым):
node bin/cli.mjs init --wrap --site <SITE_ID> --token <TOKEN> --api https://seonum.uz
# (после публикации в npm: npx @webnumseoagent/next init --wrap …)С флагом --wrap установщик делает всё автоматически:
- обнаруживает
app/и локали (next-intl), копирует адаптер вseoagent/; - создаёт
app/sitemap.ts,app/robots.ts, роут ревалидацииapp/api/seoagent-revalidate/; - прописывает
.env.local; - сам оборачивает
generateMetadataво всех страницах вseoMeta({ route, locale, fallback })(маршрут вычисляется из пути файла, включая[slug]; гард-возвраты не трогаются).
Останется: смонтировать одну строку <SeoJsonLd/> в layout, задать те же env-переменные в
Vercel и git push. Проверь git diff перед коммитом (для этого и нужно чистое дерево).
Без --wrap установщик не меняет код, а печатает готовые строки для ручной вставки.
После этого всё SEO редактируется в платформе без кода — правки применяются через ISR.
Ручная установка (если нужно вручную)
Скопируй папку
seoagent-next/в проект сайта (напр. вsrc/seoagent/), либо поставь как пакет.Добавь env-переменные (Vercel → Settings → Environment Variables):
SEOAGENT_API_BASE=https://seonum.uz SEOAGENT_SITE_ID=<id сайта из платформы> SEOAGENT_TOKEN=<config_token из настроек сайта>В
app/[locale]/layout.tsxналожи удалённый конфиг поверх своих дефолтов и смонтируй JSON-LD:import { seoMeta } from "@/seoagent/client"; import { SeoJsonLd } from "@/seoagent/jsonld"; export async function generateMetadata({ params }): Promise<Metadata> { const { locale } = await params; const compiled = { /* твои текущие метаданные — как fallback */ }; return seoMeta({ route: "/", locale, fallback: compiled }); } // в JSX layout, внутри <body>: // <SeoJsonLd route="/" locale={locale} />Для страниц с сегментами (
/[slug]) передавай реальныйroute(напр./projects/${slug}).Аналитика (GA4 / Google Tag Manager / Яндекс.Метрика) — с версии 0.5.0 включается автоматически:
<SeoJsonLd/>заодно рендерит счётчики, ID которых заданы на платформе (вкладка «Аналитика»). Отдельно ничего монтировать не нужно — обновил пакет, передеплоил, и счётчики включаются/выключаются прямо с платформы. Скрипты идемпотентны (несколько<SeoJsonLd/>на странице безопасны). Если Schema не используешь и<SeoJsonLd/>не смонтирован — можно смонтировать только аналитику:import { SeoAnalytics } from "@/seoagent/analytics"→<SeoAnalytics />внутри<body>.С версии 0.12 —
npx @webnumseoagent/next analytics(её же вызываетinit --wrap): кладёт загрузчикpublic/seoagent-analytics.js, монтирует<SeoAnalytics loader />в layout отдельно от JSON-LD (у<SeoJsonLd/>там же —analytics={false}) и дописывает домены счётчиков в CSP сайта (next.config,middleware, layout,public/_headers,vercel.json). Загрузчик — обычный скрипт со своего домена, поэтому строгой CSP inline-скрипты не нужны; CSP на nonce поддержана (nonceHeader).--no-csp— без правки CSP. Идемпотентно.Создай карту сайта и robots (если их ещё нет):
// app/sitemap.ts import { seoSitemap } from "@/seoagent/client"; export default async function sitemap() { return seoSitemap(); }// app/robots.ts import { seoRobots } from "@/seoagent/client"; export default async function robots() { return seoRobots(); }Чтобы правки применялись «за секунды» (а не по TTL), маршруты должны быть на ISR. Для полностью статичных страниц добавь ревалидацию, напр.:
export const revalidate = 300; // сек(Опционально) добавь роут ревалидации, который платформа дёрнет после сохранения:
// app/api/seo-revalidate/route.ts import { revalidateTag } from "next/cache"; export async function POST(req: Request) { if (req.headers.get("x-revalidate-secret") !== process.env.SEOAGENT_REVALIDATE_SECRET) { return new Response("no", { status: 401 }); } revalidateTag(`seo-${process.env.SEOAGENT_SITE_ID}`); return Response.json({ ok: true }); }
Блог (git-native)
Блог рендерится из файлов репозитория — папка content/blog (формат blogfs v1).
Статьи туда пишет платформа SEO Agent через git-доставку (коммит/PR); Supabase не нужен.
- Роуты ставит
npx @webnumseoagent/next blog(мультиязычный сайт →/[locale]/blog, под layout сайта) илиinit(по умолчанию,--no-blogчтобы отключить). - Рендер полностью статический (
dynamic = "force-static",dynamicParams = false): файлы читаются только на сборке, в рантайме ничего не запрашивается. Новый/изменённый контент = git-коммит → редеплой пересобирает страницы. - Тема оформления (фирменный цвет) и канонический URL-префикс берутся из
content/blog/.blogfs.json. - Sitemap блога добавляется автоматически (раздел
sitemap/blog.xml). - Роуты карты сайта/
robots/merchant —force-dynamic(неrevalidate): статический путь Vercel пре-рендерит на сборке и раздаёт из edge-кэша, из-за чего при смене данных на платформе (напр. появился блог) индекс/sitemap.xml«застревает» и не обновляется, аrevalidateTagпре-рендер не сбрасывает.force-dynamicубирает пре-рендер; внутренний fetch к платформе остаётся кэшированным (300с + тегseo-<SITE>), поэтому нагрузка почти не растёт.init/blogпроставляют это идемпотентно (и на авто-обновлении), так что старые сайты чинятся сами. init/blogсами прописывают вnext.configoutputFileTracingIncludesдляcontent/blog— без этого на Vercel файлы блога не попадают в serverless-бандл, и рантайм-чтение (ISR-ревалидация страниц + route-handler карты сайта) находит пусто → блог исчезает с сайта и из sitemap. Идемпотентно; запускается и на авто-обновлении адаптера, поэтому сайт чинится сам, без правок разработчика.
Гарантии
- Fail-safe: при недоступности платформы адаптер возвращает твои дефолты и
разрешающий robots — сайт не падает и не уходит в
noindex. - Блог без сети: статьи читаются из файлов репо на сборке — ни Supabase, ни запросов к платформе.
- Защита от деиндекса:
index:falseприменяется только если он явно задан в конфиге. - Generic: ничего не завязано на конкретный сайт — только env + маршрут/локаль.
