feat: the route report sends the noindex static paths (tracer)

- noindexStaticRoutes: requiresAuth, noindex/none seo.robots and static
  pages under seo.noindexRoutes, i18n copies included, never patterns or
  the layer's HTML sitemap pages (G1, gap-2 contract)
- #seo-routes exports noindexPaths; routeReportBody(paths, noindex) answers
  {routes, noindex} with the same limits and keeps the JSON body within the
  older plugin's 4,113,024 bytes, dropping noindex entries first
- smoke grammar noindex= and lacksnoindex=; README carries the contract
This commit is contained in:
Jakub Zych
2026-11-04 22:40:38 +01:00
parent b3a0078fb0
commit 7a26692001
9 changed files with 206 additions and 43 deletions

View File

@@ -1,18 +1,21 @@
// Reports the site's static routes to the CMS's page registry once per
// server process (D-33): POST <apiBase>/registry/routes with
// `Authorization: Bearer <route-report token>` and {routes: [path, ...]}.
// The paths come from the build (#seo-routes): pages without parameters,
// without the ones whose meta keeps them out of the index (requiresAuth, a
// noindex seo.robots) and without seo.noindexRoutes. It never delays the
// start: the report goes out after the plugin returns, a failure (no
// answer, a 5xx) is tried once more after 60 s, and a 404 (a backend without
// the registry) or a refused token is logged once and not retried. Nothing
// is sent with the layer off (seo.cms, public.seo.enabled), with
// seo.routeReport false, without a token (NUXT_SEO_ROUTE_REPORT_TOKEN, else
// NUXT_SEO_NOTFOUND_TOKEN) or while prerendering. The token stays in the
// `Authorization: Bearer <route-report token>` and
// {routes: [path, ...], noindex: [path, ...]}. The paths come from the build
// (#seo-routes): `routes` are the pages without parameters that may be
// indexed; `noindex` the static paths the build serves noindex (requiresAuth,
// a noindex or none seo.robots, a static page under seo.noindexRoutes, their
// i18n copies), so the CMS leaves them out of its sitemaps (G1). `noindex`
// is always sent, also empty; a plugin older than the key ignores it. It
// never delays the start: the report goes out after the plugin returns, a
// failure (no answer, a 5xx) is tried once more after 60 s, and a 404 (a
// backend without the registry) or a refused token is logged once and not
// retried. Nothing is sent with the layer off (seo.cms, public.seo.enabled),
// with seo.routeReport false, without a token (NUXT_SEO_ROUTE_REPORT_TOKEN,
// else NUXT_SEO_NOTFOUND_TOKEN) or while prerendering. The token stays in the
// server-only runtime config and goes to the configured apiBase only.
import { staticRoutes } from '#seo-routes'
import { noindexPaths, staticRoutes } from '#seo-routes'
import { seoServerConfig } from '../lib/config.ts'
import { ROUTE_REPORT_RETRY_MS, routeReportBody, routeReportOutcome } from '../lib/routeReport.ts'
@@ -20,7 +23,7 @@ export default defineNitroPlugin(() => {
if (import.meta.prerender) return
const cfg = seoServerConfig(useRuntimeConfig())
if (!cfg.enabled || !cfg.routeReport || !cfg.routeReportToken) return
const body = routeReportBody(staticRoutes)
const body = routeReportBody(staticRoutes, noindexPaths)
const send = async (attempt: number): Promise<void> => {
let status: number | null = null
@@ -39,7 +42,7 @@ export default defineNitroPlugin(() => {
reason = (err as Error)?.message ?? String(err)
}
const outcome = routeReportOutcome(status)
if (outcome === 'sent') console.info(`[vue-seo-layer] route report: ${body.routes.length} routes sent`)
if (outcome === 'sent') console.info(`[vue-seo-layer] route report: ${body.routes.length} routes and ${body.noindex.length} noindex paths sent`)
else if (outcome === 'absent') console.info('[vue-seo-layer] route report: the CMS has no page registry (404), nothing reported')
else if (outcome === 'refused') console.warn(`[vue-seo-layer] route report: the CMS refused the token (${status}); set NUXT_SEO_ROUTE_REPORT_TOKEN to golem15.seo.route_report_token`)
else if (attempt === 1) {