
Security News
Socket Releases Free Certified Patches for Critical vm2 Sandbox Escape
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.
@cooperco/nuxt-layer-seo
Advanced tools
A Nuxt layer that provides the full Nuxt SEO suite plus Nuxt AI Ready for Nuxt projects, plus automated linting.
The layer registers each of the @nuxtjs/seo bundled sub-modules directly in its modules array, alongside AI Ready and its MCP toolkit:
@nuxtjs/sitemap — XML sitemap generation@nuxtjs/robots — robots.txt configurationnuxt-og-image — Open Graph image generationnuxt-schema-org — Schema.org structured datanuxt-link-checker — broken link detectionnuxt-seo-utils — meta tag utilitiesnuxt-site-config — shared site config (site.url, site.name, etc.)nuxt-ai-ready — generates /llms.txt and /llms-full.txt for LLM crawlers and exposes an MCP surface (list_pages, search_pages) that AI agents can query. Per-page markdown companions (e.g. /about.md) are emitted automatically when pages are prerendered.@nuxtjs/mcp-toolkit — runtime MCP server that hosts the AI Ready tools/resources at /mcp over Streamable HTTP. Shipped alongside AI Ready so the MCP surface activates out of the box.Why register each
@nuxtjs/seosub-module explicitly instead of just listing@nuxtjs/seo? The@nuxtjs/seometa-module uses a declarativemoduleDependenciesfield to tell Nuxt which sub-modules to load. In practice, that auto-loading does not kick in when the meta-module is registered from within a Nuxt layer'smodulesarray — no auto-imports are registered anddefineSitemapEventHandleris not found at build time. Enumerating the sub-modules directly sidesteps the issue.nuxt-ai-readyand@nuxtjs/mcp-toolkitare not meta-modules, so they're registered normally.
The SEO layer's own nuxt.config.ts:
// nuxt.config.ts in the seo layer
export default defineNuxtConfig({
modules: [
'@nuxtjs/sitemap',
'@nuxtjs/robots',
'nuxt-og-image',
'nuxt-schema-org',
'nuxt-link-checker',
'nuxt-seo-utils',
'nuxt-site-config',
'nuxt-ai-ready',
'@nuxtjs/mcp-toolkit',
'@nuxt/eslint',
],
eslint: {
config: {
stylistic: true
}
}
})
Consumer apps should set site.url (and optionally site.name) in their own nuxt.config.ts so the modules can generate absolute URLs:
export default defineNuxtConfig({
extends: ['@cooperco/nuxt-layer-seo'],
site: {
url: 'https://example.com',
name: 'My Site'
}
})
Three things a consuming app needs beyond just extends-ing the layer:
Vue's @vue/compiler-core CJS build does require('entities/decode'), which Rollup rewrites to an ESM default import. entities@^7.0.1 dropped default exports from its subpath ESM builds, so the bundled production server crashes at import time with "does not provide an export named 'default'". Fix: externalize the Vue compiler packages so Node handles the CJS/ESM interop at runtime.
// consumer app nuxt.config.ts
export default defineNuxtConfig({
extends: ['@cooperco/nuxt-layer-seo'],
vite: {
ssr: {
external: ['@vue/compiler-core', '@vue/compiler-dom', '@vue/compiler-ssr'],
},
},
nitro: {
externals: {
external: ['entities', 'entities/decode', 'entities/escape'],
},
},
})
nuxt-og-image renderer + ejected template (required for OG image generation)nuxt-og-image needs at least one image renderer. The layer ships @takumi-rs/core as a runtime dependency, so the takumi renderer resolves automatically for every consumer — no manual install needed on your end.
The bundled "community" templates (e.g., NuxtSeo, BlogPost) are dev-only, however. For production builds you must eject the template you want to use — copy it from the module into your app's components/OgImage/ directory so the build compiles it as a first-party component:
cp node_modules/nuxt-og-image/dist/runtime/app/components/Templates/Community/NuxtSeo.takumi.vue \
app/components/OgImage/NuxtSeo.takumi.vue
Then call defineOgImage with the v6 API (the old object syntax was removed — see nuxt-modules/og-image#495):
// any page
defineOgImage('NuxtSeo', {
title: 'Your Page Title',
description: 'Your Page Description',
})
Once the layer is extended, the following endpoints are live:
/llms.txt — canonical LLM discovery manifest for the site (overview, sitemap links, MCP URL)./llms-full.txt — full markdown content of every indexed page, for retrieval pipelines./<route>.md — per-page markdown companion for any prerendered route (e.g. /about.md, /index.md). Written at build time as pages are prerendered./mcp — Model Context Protocol endpoint exposing list_pages and search_pages tools and a pages resource. Reachable via POST /mcp with Accept: application/json, text/event-stream.AI Ready populates its page index at prerender time, which means:
nuxt dev): endpoints are reachable but the page index is empty — /llms.txt shows the structure and a "dev mode - run nuxi generate for page titles" note. This is expected.nuxt build + normal Nitro deploy): .output/server/package.json declares the SQLite driver that AI Ready needs at runtime. Running npm install --production inside .output/server/ on the deploy host (the standard Nitro deployment pattern) installs the driver and the endpoints work fully. You never need to install better-sqlite3 in your own app — Nitro's deploy flow handles it.nuxt generate): /llms.txt, /llms-full.txt, and all *.md companions are emitted as static files during the prerender pass.nuxt-link-checker ships two ESLint rules (link-checker/valid-route, link-checker/valid-sitemap-link) that statically validate <NuxtLink to="…"> and <a href="…"> values against the app's route manifest. If your app uses a custom router.options.ts instead of file-based routing, the rules can't discover your routes and false-positive on valid links. Disable them:
// eslint.config.mjs
export default withNuxt({
rules: {
'link-checker/valid-route': 'off',
'link-checker/valid-sitemap-link': 'off',
},
})
The runtime link-checker scanner (visible in Nuxt Devtools) still works.
# Install dependencies
npm install
# Start development server
npm run dev
# Run ESLint
npm run lint
# Fix linting issues automatically
npm run lint:fix
# Run TypeScript type checking
npm run typecheck
To use this layer in your Nuxt project:
// nuxt.config.ts
export default defineNuxtConfig({
extends: [
'@cooperco/nuxt-layer-seo'
]
})
This will automatically include the SEO layer features.
All of the layer's runtime Nuxt modules live in dependencies (not devDependencies), so downstream consumers inherit them transitively when they install @cooperco/nuxt-layer-seo — no manual installs needed on the consumer side:
@nuxtjs/seo — the Nuxt SEO umbrella, which brings all seven sub-modules (sitemap, robots, og-image, schema-org, link-checker, seo-utils, site-config) as transitive deps.nuxt-ai-ready — AI/LLM discoverability module (llms.txt, llms-full.txt, per-page .md companions, MCP tool/resource definitions).@nuxtjs/mcp-toolkit — runtime MCP server that hosts AI Ready's tools and resources at /mcp.@takumi-rs/core — image renderer backend used by nuxt-og-image for OG image generation.@nuxt/eslint — ESLint integration for the consumer's eslint.config.mjs.The layer's own build-time deps (nuxt, vue, vue-tsc, typescript, eslint, @types/node) remain in devDependencies — they're only used to build and lint this layer's own source code in isolation, never by downstream consumers.
Together these provide a solid SEO + AI discoverability foundation for your Nuxt application.
This repo includes Claude Code skills for common tasks in apps that use these layers. To use them, copy the relevant skill files from the skills/ directory into your app's .claude/skills/ directory.
Skills provided by the SEO layer:
| Skill | File | Description |
|---|---|---|
/add-sitemap-source | skills/add-sitemap-source.md | Add dynamic sitemap URL sources with defineSitemapEventHandler and configure sitemap.sources |
# Copy SEO layer skills into your app
mkdir -p .claude/skills
cp node_modules/@cooperco/nuxt-layer-seo/../../skills/add-sitemap-source.md .claude/skills/
Or copy them directly from the nuxt-layers repository.
This layer is published using GitHub Actions when you push a tag that matches the seo-vX.Y.Z pattern.
High-level flow:
layers/seo/package.json (SemVer).seo-vX.Y.Z (matching the package.json version).Important notes:
npm version creating a tag for you (it will create vX.Y.Z). We use a custom tag prefix seo-v.layers/seo/package.jsoncd layers/seo
npm version patch --no-git-tag-version # or minor | major
Set-Location layers/seo
npm version patch --no-git-tag-version # or minor | major
layers/seo/package.json (SemVer: MAJOR.MINOR.PATCH)layers/seo)git add layers/seo/package.json
git commit -m "chore(seo): bump version"
git push origin main
cd layers/seo
VERSION=$(node -p "require('./package.json').version")
cd ../..
git tag "seo-v$VERSION"
git push origin "seo-v$VERSION"
Set-Location layers/seo
$version = node -p "require('./package.json').version"
Set-Location ../..
git tag "seo-v$version"
git push origin "seo-v$version"
.github/workflows/publish-seo.ymlNPM_TOKEN configured as a GitHub secretnpm view @cooperco/nuxt-layer-seo@<version>, publishes if not foundNPM_TOKEN is set in repo secrets and org access is correct.FAQs
SEO Nuxt layer for cooperco projects
We found that @cooperco/nuxt-layer-seo demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A critical vm2 sandbox escape can allow untrusted JavaScript to break isolation and execute commands on the host Node.js process.

Research
Five malicious NuGet packages impersonate Chinese .NET libraries to deploy a stealer targeting browser credentials, crypto wallets, SSH keys, and local files.

Security News
pnpm 11 turns on a 1-day Minimum Release Age and blocks exotic subdeps by default, adding safeguards against fast-moving supply chain attacks.