Files
vue-seo-layer/app/composables/useSeoResolve.ts

34 lines
1.6 KiB
TypeScript

// The resolve call and the shared answer of the current page. On the server
// it calls the internal base (runtimeConfig.seo.apiBase) and forwards the
// visitor's x-forwarded-for plus the Nuxt peer, so the backend's per-IP
// throttle (seo-resolve, 600 a minute) keeps one bucket per visitor, also on
// route-cached renders. In the browser it calls the same-origin public base.
// Any failure (timeout, 4xx, 5xx, an unexpected body) answers null: the page
// then renders with its own head.
import { seoAnswer } from '../utils/seo.ts'
import type { SeoResolveAnswer } from '../utils/types.ts'
/** The resolve answer of the page being rendered, null when there is none. */
export function useSeoAnswer() {
return useState<SeoResolveAnswer | null>('seo-layer:answer', () => null)
}
export async function resolveSeo(url: string): Promise<SeoResolveAnswer | null> {
const config = useRuntimeConfig()
const timeout = Number((config.seo as { timeoutMs?: number } | undefined)?.timeoutMs) || 1500
try {
if (import.meta.server) {
const base = (config.seo as { apiBase: string }).apiBase
const event = useRequestEvent()
const recorded = event?.context.seoForwardedFor as string | undefined
const headers: Record<string, string> = recorded ? { 'x-forwarded-for': recorded } : {}
return seoAnswer(await $fetch('/resolve', { baseURL: base, query: { url }, headers, timeout }))
}
const base = (config.public.seo as { apiBase: string }).apiBase
return seoAnswer(await $fetch('/resolve', { baseURL: base, query: { url }, timeout }))
} catch {
return null
}
}