npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@webnumseoagent/next

v0.12.0

Published

Runtime SEO adapter + installer for Next.js App Router sites (SEO Agent platform)

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.


Ручная установка (если нужно вручную)

  1. Скопируй папку seoagent-next/ в проект сайта (напр. в src/seoagent/), либо поставь как пакет.

  2. Добавь env-переменные (Vercel → Settings → Environment Variables):

    SEOAGENT_API_BASE=https://seonum.uz
    SEOAGENT_SITE_ID=<id сайта из платформы>
    SEOAGENT_TOKEN=<config_token из настроек сайта>
  3. В 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. Идемпотентно.

  4. Создай карту сайта и 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(); }
  5. Чтобы правки применялись «за секунды» (а не по 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.config outputFileTracingIncludes для content/blog — без этого на Vercel файлы блога не попадают в serverless-бандл, и рантайм-чтение (ISR-ревалидация страниц + route-handler карты сайта) находит пусто → блог исчезает с сайта и из sitemap. Идемпотентно; запускается и на авто-обновлении адаптера, поэтому сайт чинится сам, без правок разработчика.

Гарантии

  • Fail-safe: при недоступности платформы адаптер возвращает твои дефолты и разрешающий robots — сайт не падает и не уходит в noindex.
  • Блог без сети: статьи читаются из файлов репо на сборке — ни Supabase, ни запросов к платформе.
  • Защита от деиндекса: index:false применяется только если он явно задан в конфиге.
  • Generic: ничего не завязано на конкретный сайт — только env + маршрут/локаль.