// Reports every page answered 404 to the SEO plugin's 404 log // (POST /notfound with the bearer token). A page that throws a 404 (an // unknown route, a missing record) goes through Nitro's error path, which // skips afterResponse, so both the `error` hook and `afterResponse` report, // once per request. Only page views count: GET requests that accept // text/html, never Nuxt's own paths (/_nuxt, /__nuxt_error, …) or the API // routes, and nothing without the token configured. A failed report is // logged once and never touches the response. import type { H3Event } from 'h3' import { seoResolvableUrl } from '../../app/utils/seo.ts' let warned = false function report(event: H3Event) { if (event.context.seoNotFoundReported) return if (event.method !== 'GET') return if (!(getRequestHeader(event, 'accept') ?? '').includes('text/html')) return const config = useRuntimeConfig(event) if ((config.public.seo as { enabled?: boolean } | undefined)?.enabled === false) return const seo = config.seo as { apiBase: string; notfoundToken?: string; timeoutMs?: number } const token = seo.notfoundToken?.trim() if (!token) return const url = seoResolvableUrl(event.path) if (!url) return event.context.seoNotFoundReported = true const referer = getRequestHeader(event, 'referer') ?? '' const forwarded = event.context.seoForwardedFor as string | undefined const sent = $fetch('/notfound', { baseURL: seo.apiBase, method: 'POST', body: { url: url.slice(0, 2048), referer: referer.slice(0, 1024) }, headers: { authorization: `Bearer ${token}`, ...(forwarded ? { 'x-forwarded-for': forwarded } : {}) }, timeout: Number(seo.timeoutMs) || 1500, }).catch((err: unknown) => { if (!warned) { warned = true console.warn('[vue-seo-layer] 404 report failed:', (err as Error)?.message ?? err) } }) event.waitUntil?.(sent) } export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('error', (error, { event }) => { const status = (error as { statusCode?: number } | null)?.statusCode if (event && status === 404) report(event) }) nitroApp.hooks.hook('afterResponse', (event) => { if (event.node.res.statusCode === 404) report(event) }) })