- the Nitro middleware answers redirect (301/302, Location from the answer only, same-site targets as paths as in v0.1.1) and gone (410) before the render and the route cache, sets X-Robots-Tag, and answers 404 for a pagination.invalid answer; other not_found answers stay with Nuxt - resolve answers cached in an LRU (seo.resolveCacheTtl, default 60 s, 1000 entries) with in-flight de-duplication; a clear detaches loads in flight so no answer fetched before a purge is stored after it - the playground route-caches /blog/** (swr 300)
48 lines
2.2 KiB
TypeScript
48 lines
2.2 KiB
TypeScript
// Resolves every page request through the CMS (resolveCached: LRU and the
|
|
// visitor's X-Forwarded-For) and enforces the decision before Nuxt renders
|
|
// the page and before Nitro's route cache, so swr-cached pages honour a new
|
|
// redirect or 410:
|
|
//
|
|
// redirect 301 or 302 with Location from the answer (seoRedirectTarget:
|
|
// a target on the site's own origin becomes a path, another
|
|
// host stays absolute); nothing from the request (Host, path,
|
|
// query) ever goes into the Location
|
|
// gone 410 with the app's error page
|
|
// not_found left to Nuxt's routing, except a pagination.invalid answer
|
|
// (?page=abc), which is a 404
|
|
//
|
|
// X-Robots-Tag follows the answer (staging: noindex, nofollow everywhere;
|
|
// else the page's x_robots_tag), never on a redirect. Skipped: Nuxt's and
|
|
// the layers' internal paths (/_nuxt, /__nuxt_error, /_seo …), /api and
|
|
// asset files (isPagePath). The answer stays in event.context.seo for the
|
|
// render, which then makes no second call (useSeoResolve). With the CMS
|
|
// unreachable the page renders normally (fail open).
|
|
|
|
import { isPagePath } from '../../app/utils/pagePath.ts'
|
|
import { seoRedirectTarget, seoXRobotsTag } from '../../app/utils/seo.ts'
|
|
import { seoServerConfig } from '../lib/config.ts'
|
|
import { resolveCached } from '../lib/resolve.ts'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
if (event.method !== 'GET' && event.method !== 'HEAD') return
|
|
const cfg = seoServerConfig(useRuntimeConfig(event))
|
|
if (!cfg.enabled) return
|
|
const url = event.path
|
|
if (!isPagePath(url, cfg.resolveFiles)) return
|
|
|
|
const answer = await resolveCached(event, url)
|
|
event.context.seo = answer
|
|
event.context.seoResolved = { url, answer }
|
|
if (!answer) return
|
|
|
|
const target = seoRedirectTarget(answer)
|
|
if (target) return sendRedirect(event, target.to, target.code)
|
|
|
|
const robots = seoXRobotsTag(answer)
|
|
if (robots) setResponseHeader(event, 'x-robots-tag', robots)
|
|
if (answer.action === 'gone') throw createError({ statusCode: 410, statusMessage: 'Gone' })
|
|
if (answer.action === 'not_found' && answer.pagination?.invalid === true) {
|
|
throw createError({ statusCode: 404, statusMessage: 'Page Not Found' })
|
|
}
|
|
})
|