Files
vue-seo-layer/app/middleware/seo.global.ts
Jakub Zych 160b53b2f5 feat: CMS head and one JSON-LD graph on every server-rendered page (tracer)
- Nitro middleware resolves page requests once and hands the answer to the
  render (event.context.seo); the route middleware reuses it
- useCmsSeo: html lang, hreflang links, og:locale and exactly one JSON-LD
  script with one @graph (CMS nodes plus useSchemaNode nodes, merged by @id)
- local module (configKey seo): cms (alias of public.seo.enabled),
  breadcrumbs, links.newTabHint, rootFiles, resolveFiles
- GET /api/__seo__/resolve for client navigation through the Nuxt server
- smoke harness: count=, exact header=, noheader=, reqheader=, repeated
  options, resolves/apicalls/noreport lines, --no-site; v0.1.1 suite runs
  unchanged against a backend without /site
2026-11-04 09:15:26 +01:00

40 lines
1.5 KiB
TypeScript

// Resolves every navigation before its page runs, so a redirect or a 410
// wins over the page (also for an old address no page matches). The
// answer is kept in shared state for the head plugin; on hydration the
// server's answer comes with the payload and nothing is fetched again.
import { seoEnabled, seoRedirectTarget, seoResolvableUrl, seoXRobotsTag } from '../utils/seo.ts'
export default defineNuxtRouteMiddleware(async (to) => {
const nuxtApp = useNuxtApp()
const config = useRuntimeConfig()
if (!seoEnabled(config.public.seo)) return
if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) return
const state = useSeoAnswer()
const url = seoResolvableUrl(to.fullPath)
if (!url) {
state.value = null
return
}
const answer = await resolveSeo(url)
state.value = answer
if (!answer) return
if (import.meta.server) {
const header = seoXRobotsTag(answer)
if (header) useResponseHeader('X-Robots-Tag').value = header
}
// On the server a redirect only sends Location (as external), so the
// target page is not rendered and its headers stay off the redirect.
const target = seoRedirectTarget(answer)
if (target) {
if (import.meta.server) useResponseHeader('X-Robots-Tag').value = undefined
return navigateTo(target.to, { redirectCode: target.code, external: target.external || import.meta.server })
}
if (answer.action === 'gone') {
return abortNavigation(createError({ statusCode: 410, statusMessage: 'Gone', fatal: true }))
}
})