{"_id":"@cooperco/nuxt-layer-seo","_rev":"6-9d26f83362b6a46f662ccbbd62dbdee9","name":"@cooperco/nuxt-layer-seo","dist-tags":{"latest":"2.0.0"},"versions":{"0.0.1":{"name":"@cooperco/nuxt-layer-seo","version":"0.0.1","author":{"name":"cooperco"},"license":"MIT","_id":"@cooperco/nuxt-layer-seo@0.0.1","maintainers":[{"name":"krysta.hunt","email":"krysta.hunt@coopersurgical.com"},{"name":"cody.taft","email":"cody.taft@coopersurgical.com"},{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"}],"homepage":"https://github.com/Cryobank/nuxt-layers#readme","bugs":{"url":"https://github.com/Cryobank/nuxt-layers/issues"},"dist":{"shasum":"cdb075b3d4b19b5e0114efab9a6af2ee19eb367a","tarball":"https://registry.npmjs.org/@cooperco/nuxt-layer-seo/-/nuxt-layer-seo-0.0.1.tgz","fileCount":5,"integrity":"sha512-wrA0sw2FJzl3pLAf35JYVAAWkDnDiz4RbIx0bPeG42MrUGcT/zrUP6zt2rZLMhUi2SCmIPw4QYe1j6sgnxuNpA==","signatures":[{"sig":"MEUCIQD0A+rvLXo1F9gHO8C13lhot+kll7wRMAwvwBO8Zc8vdAIgDviHBReRgX3I2S3fFUrIpLWFlE3iXVbcc4DHZqw8XrY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":6266},"main":"./nuxt.config.ts","type":"module","gitHead":"5b21c83562bb0df60d24739df4f5f91bedb10658","scripts":{"dev":"nuxt dev","lint":"eslint .","build":"nuxt build","lint:fix":"eslint . --fix","typecheck":"nuxt typecheck"},"_npmUser":{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"},"repository":{"url":"git+https://github.com/Cryobank/nuxt-layers.git","type":"git","directory":"layers/seo"},"_npmVersion":"10.8.2","description":"SEO Nuxt layer for cooperco projects","directories":{},"_nodeVersion":"20.19.6","dependencies":{"@nuxtjs/robots":"^5.6.7","@nuxtjs/sitemap":"^7.5.2"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vue":"latest","nuxt":"^4.2.2","eslint":"^9.39.2","vue-tsc":"^3.2.2","typescript":"^5.9.3","@types/node":"^24.10.4","@nuxt/eslint":"latest"},"_npmOperationalInternal":{"tmp":"tmp/nuxt-layer-seo_0.0.1_1768026240845_0.11059150472674117","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@cooperco/nuxt-layer-seo","version":"1.0.2","author":{"name":"cooperco"},"license":"MIT","_id":"@cooperco/nuxt-layer-seo@1.0.2","maintainers":[{"name":"krysta.hunt","email":"krysta.hunt@coopersurgical.com"},{"name":"cody.taft","email":"cody.taft@coopersurgical.com"},{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"}],"homepage":"https://github.com/Cryobank/nuxt-layers#readme","bugs":{"url":"https://github.com/Cryobank/nuxt-layers/issues"},"dist":{"shasum":"c48780764a40a097c532fcbf0edd7a21eb53d286","tarball":"https://registry.npmjs.org/@cooperco/nuxt-layer-seo/-/nuxt-layer-seo-1.0.2.tgz","fileCount":5,"integrity":"sha512-zvlNWe35MCx3KCuCRu2Z1YpHyjTF8ilrM9CM8YSEtoLZlrUgGqZ8hQ2JjsdI5PPgV+7EzT27PKMLqTgsBR3HIA==","signatures":[{"sig":"MEYCIQC5vveZq/Wnzu+cUwbk7naahu8vqIh7LXFC7gxwfXOhggIhAJF15toilb7uO3Da6MfzdgQYo5BxAKzPvJq+JPODfuxv","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":6267},"main":"./nuxt.config.ts","type":"module","gitHead":"6408b78fe9bb26b3164369b212493730c1f89437","scripts":{"dev":"nuxt dev","lint":"eslint .","build":"nuxt build","lint:fix":"eslint . --fix","typecheck":"nuxt typecheck"},"_npmUser":{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"},"repository":{"url":"git+https://github.com/Cryobank/nuxt-layers.git","type":"git","directory":"layers/seo"},"_npmVersion":"10.8.2","description":"SEO Nuxt layer for cooperco projects","directories":{},"_nodeVersion":"20.20.0","dependencies":{"@nuxtjs/robots":"^5.7.0","@nuxtjs/sitemap":"^7.6.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.28","nuxt":"^4.3.1","eslint":"^9.39.2","vue-tsc":"^3.2.4","typescript":"^5.9.3","@types/node":"^25.2.2","@nuxt/eslint":"^1.14.0"},"_npmOperationalInternal":{"tmp":"tmp/nuxt-layer-seo_1.0.2_1770678371624_0.412031026885602","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@cooperco/nuxt-layer-seo","version":"1.0.3","author":{"name":"cooperco"},"license":"MIT","_id":"@cooperco/nuxt-layer-seo@1.0.3","maintainers":[{"name":"krysta.hunt","email":"krysta.hunt@coopersurgical.com"},{"name":"cody.taft","email":"cody.taft@coopersurgical.com"},{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"}],"homepage":"https://github.com/Cryobank/nuxt-layers#readme","bugs":{"url":"https://github.com/Cryobank/nuxt-layers/issues"},"dist":{"shasum":"9dada53c2448ac4a89f9d89bc630b7dec6130f75","tarball":"https://registry.npmjs.org/@cooperco/nuxt-layer-seo/-/nuxt-layer-seo-1.0.3.tgz","fileCount":5,"integrity":"sha512-IJQs3gREcDELLEOwj3iQbZFnBdbsQpNeuGiyQ6mroEEqAjgI1LKEduBg8NLgWc0CR71v+WWnSlYfh9svI6+Ieg==","signatures":[{"sig":"MEYCIQDghIpxpRFqstAATvj/Q4nWSKYgb3fMVwwMBbehrwpXNQIhAOZYcxJrB2nbDE6jBwVxwx7EBdPnDPpyy/dO/HK3qyIN","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":6266},"main":"./nuxt.config.ts","type":"module","gitHead":"76ae62633f5dd2bcaf80f0858119590063728615","scripts":{"dev":"nuxt dev","lint":"eslint .","build":"nuxt build","lint:fix":"eslint . --fix","typecheck":"nuxt typecheck"},"_npmUser":{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"},"repository":{"url":"git+https://github.com/Cryobank/nuxt-layers.git","type":"git","directory":"layers/seo"},"_npmVersion":"10.8.2","description":"SEO Nuxt layer for cooperco projects","directories":{},"_nodeVersion":"20.20.0","dependencies":{"@nuxtjs/robots":"^5.7.1","@nuxtjs/sitemap":"^7.6.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.30","nuxt":"^4.3.1","eslint":"^10.0.3","vue-tsc":"^3.2.5","typescript":"^5.9.3","@types/node":"^25.4.0","@nuxt/eslint":"^1.15.2"},"_npmOperationalInternal":{"tmp":"tmp/nuxt-layer-seo_1.0.3_1773094391711_0.33478250749032146","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@cooperco/nuxt-layer-seo","version":"1.0.4","author":{"name":"cooperco"},"license":"MIT","_id":"@cooperco/nuxt-layer-seo@1.0.4","maintainers":[{"name":"krysta.hunt","email":"krysta.hunt@coopersurgical.com"},{"name":"cody.taft","email":"cody.taft@coopersurgical.com"},{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"}],"homepage":"https://github.com/Cryobank/nuxt-layers#readme","bugs":{"url":"https://github.com/Cryobank/nuxt-layers/issues"},"dist":{"shasum":"279cf2b4192bceb897659266fc59fa829e775485","tarball":"https://registry.npmjs.org/@cooperco/nuxt-layer-seo/-/nuxt-layer-seo-1.0.4.tgz","fileCount":5,"integrity":"sha512-8qVeXHKfc62b3Svs0HFosftfwJXFceLODzRFBL02JN9PDnlDEEBApf5zF2yWWj15JWvgA/dRt0K1fndjUYEtkg==","signatures":[{"sig":"MEYCIQCF0qMJLgGcInPD+oNcBw52qxNG2QIV3FvGV13VXfPMPQIhALWm50su2mW1HqFcB5GrSKlOtbZbDCAnMlGA0Fh9432h","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":7117},"main":"./nuxt.config.ts","type":"module","gitHead":"043214f4fd44a5b63c01e31d9eca94dca3f21d5f","scripts":{"dev":"nuxt dev","lint":"eslint .","build":"nuxt build","lint:fix":"eslint . --fix","typecheck":"nuxt typecheck"},"_npmUser":{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"},"repository":{"url":"git+https://github.com/Cryobank/nuxt-layers.git","type":"git","directory":"layers/seo"},"_npmVersion":"10.8.2","description":"SEO Nuxt layer for cooperco projects","directories":{},"_nodeVersion":"20.20.0","dependencies":{"@nuxtjs/robots":"^5.7.1","@nuxtjs/sitemap":"^7.6.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.30","nuxt":"^4.3.1","eslint":"^10.0.3","vue-tsc":"^3.2.5","typescript":"^5.9.3","@types/node":"^25.4.0","@nuxt/eslint":"^1.15.2"},"_npmOperationalInternal":{"tmp":"tmp/nuxt-layer-seo_1.0.4_1773097772481_0.4549790806180265","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@cooperco/nuxt-layer-seo","version":"1.1.0","author":{"name":"cooperco"},"license":"MIT","_id":"@cooperco/nuxt-layer-seo@1.1.0","maintainers":[{"name":"krysta.hunt","email":"krysta.hunt@coopersurgical.com"},{"name":"cody.taft","email":"cody.taft@coopersurgical.com"},{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"}],"homepage":"https://github.com/Cryobank/nuxt-layers#readme","bugs":{"url":"https://github.com/Cryobank/nuxt-layers/issues"},"dist":{"shasum":"50ac91541df61429475458d2e5fdf22838e35919","tarball":"https://registry.npmjs.org/@cooperco/nuxt-layer-seo/-/nuxt-layer-seo-1.1.0.tgz","fileCount":5,"integrity":"sha512-nT87WsWU7cSodUEXK7g+/2P6U2Z0jNHc1xjX1HIya9TP8MfsPLpwSZHY4dw75LMol6k4Rp4jnLaF19YsVVO7WA==","signatures":[{"sig":"MEUCIQDL1x0rqMy+s8PcGOeQTRgPuRZmSOTa8GJ0/FRvTOgWdQIgSO2P8CBsZ89eQxfcP8mzpeeb1H2Iz9S2PL7NFkVe6ng=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":15402},"main":"./nuxt.config.ts","type":"module","gitHead":"7d4ef579dae4ef1552181c618b92d56c673cf0ef","scripts":{"dev":"nuxt dev","lint":"eslint .","build":"nuxt build","lint:fix":"eslint . --fix","typecheck":"nuxt typecheck"},"_npmUser":{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"},"repository":{"url":"git+https://github.com/Cryobank/nuxt-layers.git","type":"git","directory":"layers/seo"},"_npmVersion":"10.8.2","description":"SEO Nuxt layer for cooperco projects","directories":{},"_nodeVersion":"20.20.2","dependencies":{"@nuxtjs/seo":"^5.1.3","@nuxt/eslint":"^1.15.2","nuxt-ai-ready":"^1.1.2","@takumi-rs/core":"^1.0.9","@nuxtjs/mcp-toolkit":"^0.13.4"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.30","nuxt":"^4.4.2","eslint":"^10.0.3","vue-tsc":"^3.2.5","typescript":"^5.9.3","@types/node":"^25.4.0"},"_npmOperationalInternal":{"tmp":"tmp/nuxt-layer-seo_1.1.0_1776235338158_0.909107723345782","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@cooperco/nuxt-layer-seo","type":"module","version":"2.0.0","main":"./nuxt.config.ts","author":{"name":"cooperco"},"description":"SEO Nuxt layer for cooperco projects","license":"MIT","scripts":{"build":"nuxt build","dev":"nuxt dev","lint":"eslint .","lint:fix":"eslint . --fix","typecheck":"nuxt typecheck"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"repository":{"type":"git","url":"git+https://github.com/Cryobank/nuxt-layers.git","directory":"layers/seo"},"dependencies":{"@nuxt/eslint":"^1.16.0","@nuxtjs/mcp-toolkit":"^0.17.2","@nuxtjs/seo":"^5.3.0","@takumi-rs/core":"^1.8.6","nuxt-ai-ready":"^1.4.0"},"devDependencies":{"@types/node":"^25.9.3","eslint":"^10.5.0","nuxt":"^4.4.8","typescript":"^6.0.3","vue":"^3.5.30","vue-tsc":"^3.3.5"},"_id":"@cooperco/nuxt-layer-seo@2.0.0","gitHead":"4d828d56965bce3611879e56380b9ab69fe57ffe","bugs":{"url":"https://github.com/Cryobank/nuxt-layers/issues"},"homepage":"https://github.com/Cryobank/nuxt-layers#readme","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-4a87naBln/c/ETVxvyVb3XsFnUOe+Ep/6WjjvOsa7kDyBUkNsxx2mso7seku3qqnIEo4TUuperARRxnrkviBEQ==","shasum":"ae84a13db01399cb49f4da9e5e206c29191f62c2","tarball":"https://registry.npmjs.org/@cooperco/nuxt-layer-seo/-/nuxt-layer-seo-2.0.0.tgz","fileCount":5,"unpackedSize":17576,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC5iMrAvh+QmT9tJvd74gbzHezxr2fwvk8DBtnKebzMlQIgHE4aLBJbzpjaINeKW2iKuVEyNytrmRgpvLDx9OMehCU="}]},"_npmUser":{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"},"directories":{},"maintainers":[{"name":"krysta.hunt","email":"krysta.hunt@coopersurgical.com"},{"name":"cody.taft","email":"cody.taft@coopersurgical.com"},{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nuxt-layer-seo_2.0.0_1781742186615_0.41778637313244427"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-10T06:24:00.782Z","modified":"2026-06-18T00:23:06.880Z","0.0.1":"2026-01-10T06:24:00.979Z","1.0.2":"2026-02-09T23:06:11.779Z","1.0.3":"2026-03-09T22:13:11.850Z","1.0.4":"2026-03-09T23:09:32.621Z","1.1.0":"2026-04-15T06:42:18.297Z","2.0.0":"2026-06-18T00:23:06.760Z"},"bugs":{"url":"https://github.com/Cryobank/nuxt-layers/issues"},"author":{"name":"cooperco"},"license":"MIT","homepage":"https://github.com/Cryobank/nuxt-layers#readme","repository":{"type":"git","url":"git+https://github.com/Cryobank/nuxt-layers.git","directory":"layers/seo"},"description":"SEO Nuxt layer for cooperco projects","maintainers":[{"name":"krysta.hunt","email":"krysta.hunt@coopersurgical.com"},{"name":"cody.taft","email":"cody.taft@coopersurgical.com"},{"name":"devin-cs","email":"devin.holloway@coopersurgical.com"}],"readme":"# Nuxt SEO Layer\n\nA Nuxt layer that provides the full [Nuxt SEO](https://nuxtseo.com) suite plus [Nuxt AI Ready](https://nuxtseo.com/docs/ai-ready) for Nuxt projects, plus automated linting.\n\n## Features\n\nThe layer registers each of the `@nuxtjs/seo` bundled sub-modules directly in its `modules` array, alongside AI Ready and its MCP toolkit:\n\n- [`@nuxtjs/sitemap`](https://nuxtseo.com/docs/sitemap) — XML sitemap generation\n- [`@nuxtjs/robots`](https://nuxtseo.com/docs/robots) — robots.txt configuration\n- [`nuxt-og-image`](https://nuxtseo.com/docs/og-image) — Open Graph image generation\n- [`nuxt-schema-org`](https://nuxtseo.com/docs/schema-org) — Schema.org structured data\n- [`nuxt-link-checker`](https://nuxtseo.com/docs/link-checker) — broken link detection\n- [`nuxt-seo-utils`](https://nuxtseo.com/docs/seo-utils) — meta tag utilities\n- [`nuxt-site-config`](https://nuxtseo.com/docs/site-config) — shared site config (`site.url`, `site.name`, etc.)\n- [`nuxt-ai-ready`](https://nuxtseo.com/docs/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.\n- [`@nuxtjs/mcp-toolkit`](https://nuxtseo.com/docs/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.\n- ESLint integration with stylistic rules\n\n> **Why register each `@nuxtjs/seo` sub-module explicitly instead of just listing `@nuxtjs/seo`?** The `@nuxtjs/seo` meta-module uses a declarative `moduleDependencies` field 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's `modules` array — no auto-imports are registered and `defineSitemapEventHandler` is not found at build time. Enumerating the sub-modules directly sidesteps the issue. `nuxt-ai-ready` and `@nuxtjs/mcp-toolkit` are not meta-modules, so they're registered normally.\n\n## Configuration\n\nThe SEO layer's own `nuxt.config.ts`:\n\n```typescript\n// nuxt.config.ts in the seo layer\nexport default defineNuxtConfig({\n  modules: [\n    '@nuxtjs/sitemap',\n    '@nuxtjs/robots',\n    'nuxt-og-image',\n    'nuxt-schema-org',\n    'nuxt-link-checker',\n    'nuxt-seo-utils',\n    'nuxt-site-config',\n    'nuxt-ai-ready',\n    '@nuxtjs/mcp-toolkit',\n    '@nuxt/eslint',\n  ],\n  eslint: {\n    config: {\n      stylistic: true\n    }\n  }\n})\n```\n\nConsumer apps should set `site.url` (and optionally `site.name`) in their own `nuxt.config.ts` so the modules can generate absolute URLs:\n\n```typescript\nexport default defineNuxtConfig({\n  extends: ['@cooperco/nuxt-layer-seo'],\n  site: {\n    url: 'https://example.com',\n    name: 'My Site'\n  }\n})\n```\n\n## Consumer app setup: known gotchas\n\nThree things a consuming app needs beyond just `extends`-ing the layer:\n\n### 1. Vite SSR externals (required — production build will 500 without this)\n\nVue'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.\n\n```typescript\n// consumer app nuxt.config.ts\nexport default defineNuxtConfig({\n  extends: ['@cooperco/nuxt-layer-seo'],\n  vite: {\n    ssr: {\n      external: ['@vue/compiler-core', '@vue/compiler-dom', '@vue/compiler-ssr'],\n    },\n  },\n  nitro: {\n    externals: {\n      external: ['entities', 'entities/decode', 'entities/escape'],\n    },\n  },\n})\n```\n\n### 2. `nuxt-og-image` renderer + ejected template (required for OG image generation)\n\n`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.\n\nThe 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:\n\n```bash\ncp node_modules/nuxt-og-image/dist/runtime/app/components/Templates/Community/NuxtSeo.takumi.vue \\\n   app/components/OgImage/NuxtSeo.takumi.vue\n```\n\nThen call `defineOgImage` with the **v6 API** (the old object syntax was removed — see [nuxt-modules/og-image#495](https://github.com/nuxt-modules/og-image/issues/495)):\n\n```typescript\n// any page\ndefineOgImage('NuxtSeo', {\n  title: 'Your Page Title',\n  description: 'Your Page Description',\n})\n```\n\nRuntime OG image URLs should be signed whenever dynamic OG image generation is enabled. Without a signing secret, `nuxt-og-image` warns in dev because anyone who can reach the app can craft arbitrary image-generation requests. Set a stable secret in each consumer app's environment:\n\n```env\nNUXT_OG_IMAGE_SECRET=<secret>\n```\n\nGenerate one with:\n\n```bash\nnpx nuxt-og-image generate-secret\n```\n\nIf the app only needs prerendered/static OG images, disable dynamic runtime generation instead:\n\n```typescript\nexport default defineNuxtConfig({\n  ogImage: {\n    zeroRuntime: true,\n  },\n})\n```\n\n### 3. AI Ready endpoints and the dev/prod page index\n\nOnce the layer is extended, the following endpoints are live:\n\n- `/llms.txt` — canonical LLM discovery manifest for the site (overview, sitemap links, MCP URL).\n- `/llms-full.txt` — full markdown content of every indexed page, for retrieval pipelines.\n- `/<route>.md` — per-page markdown companion for any prerendered route (e.g. `/about.md`, `/index.md`). Written at build time as pages are prerendered.\n- `/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`.\n\nAI Ready uses a database for its page index. In normal local development and normal Node SSR deployments, including typical AWS EC2, ECS, and Elastic Beanstalk apps, `nuxt-ai-ready` defaults to SQLite. As of `nuxt-ai-ready@1.4.0`, that mode requires the consuming app to install the native SQLite driver:\n\n```bash\nnpm install better-sqlite3\n```\n\nUse a regular dependency in deployed Node SSR apps. For this repository's playground, which is only a local layer test app, installing it as a dev dependency is fine:\n\n```bash\ncd playground\nnpm install -D better-sqlite3\n```\n\nDo not install `better-sqlite3` only in this layer and expect consumers to inherit it. AI Ready checks for the driver from the consuming app root.\n\nFor non-Node or serverless/edge deployments, configure AI Ready's database explicitly instead of relying on the SQLite default:\n\n```typescript\nexport default defineNuxtConfig({\n  extends: ['@cooperco/nuxt-layer-seo'],\n  aiReady: {\n    database: {\n      type: 'libsql', // or 'd1', 'neon', 'bun'\n    },\n  },\n})\n```\n\nChoose the provider that matches the host and install/configure its required driver and credentials in the consuming app. For AWS Lambda or other ephemeral-filesystem runtimes, prefer an external database provider rather than SQLite unless persistent storage has been deliberately designed.\n\nAI Ready populates its page index at **prerender time**, which means:\n\n- **Dev mode (`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, but the selected database driver must still be installed.\n- **Production build (`nuxt build` + normal Nitro deploy):** the selected AI Ready database driver must be resolvable from the consuming app. For default Node/AWS SSR, that means `better-sqlite3` belongs in the app's dependencies.\n- **Static generation (`nuxt generate`):** `/llms.txt`, `/llms-full.txt`, and all `*.md` companions are emitted as static files during the prerender pass.\n\n### 4. ESLint link-checker rules (optional — disable if using a custom router)\n\n`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:\n\n```js\n// eslint.config.mjs\nexport default withNuxt({\n  rules: {\n    'link-checker/valid-route': 'off',\n    'link-checker/valid-sitemap-link': 'off',\n  },\n})\n```\n\nThe runtime link-checker scanner (visible in Nuxt Devtools) still works.\n\n## Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Start development server\nnpm run dev\n\n# Run ESLint\nnpm run lint\n\n# Fix linting issues automatically\nnpm run lint:fix\n\n# Run TypeScript type checking\nnpm run typecheck\n```\n\n## Usage\n\nTo use this layer in your Nuxt project:\n\n```typescript\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  extends: [\n    '@cooperco/nuxt-layer-seo'\n  ]\n})\n```\n\nThis will automatically include the SEO layer features.\n\n## Dependencies\n\nAll of the layer's runtime Nuxt modules live in `dependencies` (not `devDependencies`), so downstream consumers inherit the Nuxt modules transitively when they install `@cooperco/nuxt-layer-seo`:\n\n- `@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.\n- `nuxt-ai-ready` — AI/LLM discoverability module (llms.txt, llms-full.txt, per-page `.md` companions, MCP tool/resource definitions).\n- `@nuxtjs/mcp-toolkit` — runtime MCP server that hosts AI Ready's tools and resources at `/mcp`.\n- `@takumi-rs/core` — image renderer backend used by `nuxt-og-image` for OG image generation.\n- `@nuxt/eslint` — ESLint integration for the consumer's `eslint.config.mjs`.\n\nDatabase drivers are the exception. `nuxt-ai-ready` declares `better-sqlite3`, `@libsql/client`, and `@neondatabase/serverless` as optional peer dependencies because the correct driver depends on the consuming app's deployment target. Default Node/AWS SSR apps should install `better-sqlite3`; apps using D1, Neon, LibSQL/Turso, or Bun should configure `aiReady.database.type` and install the matching provider in that app.\n\nThe 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.\n\nTogether these provide a solid SEO + AI discoverability foundation for your Nuxt application.\n\n---\n\n## Claude Code Skills\n\nThis repo includes [Claude Code skills](https://docs.anthropic.com/en/docs/claude-code/skills) for common tasks in apps that use these layers. To use them, copy the relevant skill files from the [`skills/`](../../skills/) directory into your app's `.claude/skills/` directory.\n\n**Skills provided by the SEO layer:**\n\n| Skill | File | Description |\n|-------|------|-------------|\n| `/add-sitemap-source` | `skills/add-sitemap-source.md` | Add dynamic sitemap URL sources with `defineSitemapEventHandler` and configure `sitemap.sources` |\n\n```bash\n# Copy SEO layer skills into your app\nmkdir -p .claude/skills\ncp node_modules/@cooperco/nuxt-layer-seo/../../skills/add-sitemap-source.md .claude/skills/\n```\n\nOr copy them directly from the [nuxt-layers repository](https://github.com/anthropics/nuxt-layers/tree/main/skills).\n\n---\n\n## Publishing to npm (tag-based)\n\nThis layer is published using GitHub Actions when you push a tag that matches the `seo-vX.Y.Z` pattern.\n\nHigh-level flow:\n- Bump the version in `layers/seo/package.json` (SemVer).\n- Commit and push your changes to main (or ensure the commit is on main).\n- Create and push a tag named `seo-vX.Y.Z` (matching the `package.json` version).\n- The Publish SEO Layer workflow installs deps, checks if that exact version already exists on npm, and if not, publishes to npm.\n\nImportant notes:\n- Do NOT rely on `npm version` creating a tag for you (it will create `vX.Y.Z`). We use a custom tag prefix `seo-v`.\n- The workflow will skip if the version already exists.\n\n### Step-by-step\n\n1) Bump the version in `layers/seo/package.json`\n- Option A (recommended): use npm version without creating a tag\n  - Bash:\n    ```bash\n    cd layers/seo\n    npm version patch --no-git-tag-version  # or minor | major\n    ```\n  - PowerShell:\n    ```powershell\n    Set-Location layers/seo\n    npm version patch --no-git-tag-version  # or minor | major\n    ```\n- Option B: manually edit the version field in `layers/seo/package.json` (SemVer: MAJOR.MINOR.PATCH)\n\n2) Commit and push the change (from repo root or `layers/seo`)\n```bash\ngit add layers/seo/package.json\ngit commit -m \"chore(seo): bump version\"\ngit push origin main\n```\n\n3) Create and push the tag using the new version\n- Get the new version value:\n  - Bash:\n    ```bash\n    cd layers/seo\n    VERSION=$(node -p \"require('./package.json').version\")\n    cd ../..\n    git tag \"seo-v$VERSION\"\n    git push origin \"seo-v$VERSION\"\n    ```\n  - PowerShell:\n    ```powershell\n    Set-Location layers/seo\n    $version = node -p \"require('./package.json').version\"\n    Set-Location ../..\n    git tag \"seo-v$version\"\n    git push origin \"seo-v$version\"\n    ```\n\n4) GitHub Actions will publish\n- Workflow: `.github/workflows/publish-seo.yml`\n- Auth: uses `NPM_TOKEN` configured as a GitHub secret\n- Behavior: installs, checks `npm view @cooperco/nuxt-layer-seo@<version>`, publishes if not found\n\n### Troubleshooting\n- Version already exists: bump the version again (patch/minor/major) and push a new tag.\n- Auth errors (401/403): ensure `NPM_TOKEN` is set in repo secrets and org access is correct.\n","readmeFilename":"README.md"}