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

40 lines
1.9 KiB
TypeScript

// Which requests the Nitro SEO middleware resolves. Pure, so `node --test`
// drives it directly; the name starts with `is` like Nuxt's own helpers and
// is unique enough for the auto-import into the consuming app.
/**
* File extensions that are never pages: build assets, images, fonts, data
* and the root files (robots.txt, sitemap.xml, verification files), which
* server/middleware/seo-root.ts handles.
*/
const ASSET_EXTENSIONS = new Set([
'js', 'mjs', 'cjs', 'css', 'map', 'json', 'xml', 'txt', 'html', 'htm', 'xsl', 'webmanifest',
'ico', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif', 'svg', 'bmp', 'tif', 'tiff',
'woff', 'woff2', 'ttf', 'otf', 'eot',
'mp3', 'mp4', 'webm', 'ogg', 'wav', 'mov',
'pdf', 'zip', 'gz', 'br', 'wasm',
])
/**
* True when `path` (path and query, as h3's event.path) is a page the SEO
* middleware resolves: not Nuxt's or the layers' internal paths (/_nuxt,
* /__nuxt_error, /_journal, /_seo …), not /api, and not a file with one of
* the asset extensions, unless the extension is listed in `resolveFiles`
* (module option seo.resolveFiles, default ['pdf'], so an old PDF address
* can still redirect). Pages a Nuxt route renders under a dotted path (an
* author slug with ".com") are still resolved by the route middleware.
*/
export function isPagePath(path: string, resolveFiles: readonly string[] = []): boolean {
if (typeof path !== 'string' || !path.startsWith('/') || path.startsWith('//')) return false
const pathname = (path.split('?')[0] ?? '').split('#')[0] ?? ''
if (pathname.startsWith('/_')) return false
if (pathname === '/api' || pathname.startsWith('/api/')) return false
const last = pathname.slice(pathname.lastIndexOf('/') + 1)
const dot = last.lastIndexOf('.')
if (dot > 0) {
const ext = last.slice(dot + 1).toLowerCase()
if (ASSET_EXTENSIONS.has(ext) && !resolveFiles.map((e) => e.toLowerCase()).includes(ext)) return false
}
return true
}