Files
vue-seo-layer/nuxt.config.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

70 lines
2.9 KiB
TypeScript

// vue-seo-layer: the frontend half of the SummerCMS SEO plugin
// (golem15.seo). For every page it asks GET /_seo/api/v1/resolve?url=…,
// enforces the redirect and 410 decisions, and sets the head (title,
// description, canonical, robots, Open Graph, X/Twitter, verification,
// hreflang, <html lang> and one JSON-LD @graph) from the answer with priority
// over the page's own tags. It also reports 404s to the plugin's log and
// serves the site's root files (verification files and, with a backend that
// answers /_seo/api/v1/site, robots.txt, the sitemaps, llms.txt and the
// IndexNow key). An app consumes it with `extends: ['vue-seo-layer']`.
//
// Every path below is absolute and built from this file's directory: a
// relative or ~ path in a layer config resolves against the consuming
// project, not the layer. The layer's only module is its own local one
// (lib/seo-module.ts); it pulls in no package.
import { fileURLToPath } from 'node:url'
const currentDir = fileURLToPath(new URL('.', import.meta.url))
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
devtools: { enabled: false },
modules: [`${currentDir}lib/seo-module.ts`],
runtimeConfig: {
seo: {
// Server-only base for SSR calls (NUXT_SEO_API_BASE). It never comes
// from request input.
apiBase: 'http://127.0.0.1:8096/_seo/api/v1',
// Server-only origin of the Go binary for the root files
// (NUXT_SEO_BACKEND_BASE). Empty: apiBase without /_seo/api/v1, so
// http://127.0.0.1:8096 by default.
backendBase: '',
// Server-only bearer token of POST /notfound (NUXT_SEO_NOTFOUND_TOKEN),
// the backend's golem15.seo.notfound_token. Empty: 404s are not
// reported. Not under NUXT_SITE_: nuxt-site-config copies every
// NUXT_SITE_* variable into the site config, which is published in
// every page's payload.
notfoundToken: '',
// Server-only bearer token of the route report
// (NUXT_SEO_ROUTE_REPORT_TOKEN). Empty: notfoundToken is used.
routeReportToken: '',
// How long a resolve call may take before the page renders without it
// (NUXT_SEO_TIMEOUT_MS).
timeoutMs: 1500,
// Seconds a resolve answer stays in the layer's cache
// (NUXT_SEO_RESOLVE_CACHE_TTL); the site cache purge clears it. 0
// turns the cache off.
resolveCacheTtl: 60,
},
public: {
seo: {
// Same-origin base for browser navigations (NUXT_PUBLIC_SEO_API_BASE).
// The site's proxy routes GET <base>/resolve to the backend; set it
// to /api/__seo__ to resolve through the Nuxt server instead.
apiBase: '/_seo/api/v1',
// NUXT_PUBLIC_SEO_ENABLED=false turns the layer off: no resolve
// calls, no head, no redirects, no root files, no 404 reports. The
// module option seo.cms (NUXT_PUBLIC_SEO_CMS) is an alias.
enabled: true,
},
},
},
routeRules: {
'/api/__seo__/**': { cache: false },
},
})