- 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
40 lines
1.5 KiB
TypeScript
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 }))
|
|
}
|
|
})
|