- 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
40 lines
1.9 KiB
TypeScript
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
|
|
}
|