Files
vue-seo-layer/app/utils/types.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

112 lines
3.4 KiB
TypeScript

// Wire types of GET /_seo/api/v1/resolve. Field names mirror the Go
// serializer (classes.Result in sm-seo-plugin) exactly. Types only, so
// `node --test` can import any util.
/** ok, not_found, redirect or gone. */
export type SeoAction = 'ok' | 'not_found' | 'redirect' | 'gone'
export interface SeoMetaTag {
name: string
content: string
}
/** The head metadata of a resolved page. Empty strings mean "no value". */
export interface SeoHead {
title: string
description: string
canonical: string
robots: string
x_robots_tag: string
og: {
title: string
description: string
image: string
url: string
/** og:locale of the page (pl_PL); absent from backends before SEO v0.2.0. */
locale?: string
/** og:locale:alternate values, one per other translated locale. */
locale_alternates?: string[]
}
twitter: { card: string; site: string }
verification: SeoMetaTag[]
}
export interface SeoResolveAnswer {
action: SeoAction
/** The HTTP status the frontend should answer with (200, 301, 302, 404, 410). */
status: number
/** The absolute target of a redirect answer; absent otherwise. */
location?: string
/** The absolute URL of the page on the site URL. */
url: string
locale: string
/** True outside the backend's production environment: noindex everywhere. */
staging: boolean
/** Set on ok answers only. */
seo: SeoHead | null
// The keys below are additive (SEO plugin v0.2.0); an older backend leaves
// them out and every consumer treats an absent key as empty.
/** The <html lang> value (pl, pl-PL), set on ok answers. */
html_lang?: string
/** hreflang alternates including the page itself and x-default; absolute URLs. */
alternates?: SeoAlternate[]
/** One {"@context", "@graph"} object, or null. */
jsonld?: SeoJsonLd | null
breadcrumbs?: SeoBreadcrumb[]
/** Present only when the request carries a page number. */
pagination?: SeoPaginationAnswer
outbound?: SeoOutbound
autolinks?: Array<{ phrase: string; url: string }>
autolink_max?: number
}
export interface SeoAlternate {
hreflang: string
url: string
}
/** A schema.org node; @id links nodes across the graph. */
export type SeoSchemaNode = Record<string, unknown> & { '@id'?: string; '@type'?: string | string[] }
export interface SeoJsonLd {
'@context'?: string
'@graph'?: SeoSchemaNode[]
}
export interface SeoBreadcrumb {
name: string
url: string
path: string
}
export interface SeoPaginationAnswer {
form?: 'query' | 'path'
param?: string
segment?: string
page?: number
/** A page value that is not a page number (?page=abc): the layer answers 404. */
invalid?: boolean
}
export interface SeoOutbound {
new_tab?: boolean
nofollow_domains?: string[]
sponsored_domains?: string[]
}
/** GET /_seo/api/v1/site. Every key may be absent on an older backend. */
export interface SeoSiteAnswer {
site_url?: string
default_locale?: string
locales?: string[]
unprefixed_locale?: string
links?: { new_tab?: boolean }
html_sitemap?: { enabled?: boolean; paths?: Record<string, string>; page_size?: number }
routes?: Array<{ type: string; pattern: string }>
/** Root file names the backend serves (verification files, the IndexNow key file). */
root_files?: string[]
llms?: boolean
pagination?: { form?: 'query' | 'path'; param?: string; segments?: Record<string, string> }
home_crumb?: { name?: string; url?: string } | null
}