# Nuxt SEO > Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. Canonical Origin: https://nuxtseo.com ## LLM Resources - [Full Content](https://nuxtseo.com/llms-full.txt) Complete page content in markdown format. - [MCP](https://nuxtseo.com/mcp) Model Context Protocol server endpoint for AI agent integration. ## Pages ### Nuxt SEO · All-in-one Technical SEO for Nuxt Source: https://nuxtseo.com/ Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. Fully equipped** **Technical SEO** for busy **Nuxters**. ** Nuxt SEO is a collection of [**modules **](https://nuxt.com/modules) that handle all of the technical aspects in growing your sites organic traffic. [**Get Started **](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction) [** Install Nuxt SEO **](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation) [Robots](https://nuxtseo.com/docs/robots/getting-started/introduction) [Sitemap](https://nuxtseo.com/docs/sitemap/getting-started/introduction) [OG Image](https://nuxtseo.com/docs/og-image/getting-started/introduction) [Schema.org](https://nuxtseo.com/docs/schema-org/getting-started/introduction) [Link Checker](https://nuxtseo.com/docs/link-checker/getting-started/introduction) [SEO Utils](https://nuxtseo.com/docs/seo-utils/getting-started/introduction) 1. h2. **Put web crawlers to work.** Providing a robots.txt and sitemap.xml gives web crawlers data on how to index your site. [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/robots/getting-started/introduction) [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/docs/sitemap/getting-started/introduction) 2. h2. **Feed semantic data to hungry bots.** Robots love data, give them what they want with Schema.org and Open Graph tags. [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/docs/schema-org/getting-started/introduction) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/docs/seo-utils/getting-started/introduction) 3. h2. **Humanize it.** Technical SEO doesn't just involve robots, make sure your site is human friendly too. [OG Image **v5.1.13** 2.9M 495 Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/docs/og-image/getting-started/introduction) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/docs/seo-utils/getting-started/introduction) 4. h2. **Nurture and watch it flourish.** Providing a robots.txt and sitemap.xml gives web crawlers data on how to index your site. [Link Checker **v4.3.9** 2.3M 95 Find and magically fix links that may be negatively effecting your SEO.](https://nuxtseo.com/docs/link-checker/getting-started/introduction) h2. **Nuxt SEO Principals ** h3. **Delightful Developer Experience** Full featured modules that do everything you expect and more. h3. **Zero Config Defaults** Provide a site URL and all modules are good to go. Fully extensible with config and hooks. ![Nuxt I18n Icon](https://ipx.nuxt.com/s_80,f_auto/gh/nuxt/modules/main/icons/i18n.png) h3. **Integrate with Ecosystem** Modules integrate with themselves as well as Nuxt Content, Nuxt I18n and Nuxt DevTools. h3. **For Apps All Shapes and Sizes ** Single Page Server-Side Generated Server-Side Rendered Multi-tenancy Base URL Trailing Slashes h2. **Learn SEO ** Comprehensive guides for technical SEO with code examples and best practices. [

**Nuxt SEO Guide **

Meta tags, sitemaps, robots.txt, Schema.org, and more. Everything you need for Nuxt applications. - Mastering Meta Tags - Controlling Web Crawlers - Routes & Rendering Modes](https://nuxtseo.com/learn-seo/nuxt) [

**Vue SEO Guide **

SPA SEO challenges, SSR frameworks comparison, Unhead integration, and Core Web Vitals optimization. - SPA SEO Solutions - SSR Framework Comparison - Vue SEO Checklist](https://nuxtseo.com/learn-seo/vue) h2. **Free SEO Tools ** Developer-focused tools to debug, validate, and optimize your site's SEO. [

**Social Share Debugger**

Preview how links appear on Twitter, Facebook, LinkedIn, Slack & Discord.](https://nuxtseo.com/tools/social-share-debugger) [

**Meta Tag Checker**

Validate title & description length. Preview Google search appearance.](https://nuxtseo.com/tools/meta-tag-checker) [

**Robots.txt Generator**

Generate robots.txt with AI crawler presets. Block GPTBot, ClaudeBot & more.](https://nuxtseo.com/tools/robots-txt-generator) [

**Schema.org Validator**

Validate JSON-LD & Microdata markup for rich snippets.](https://nuxtseo.com/tools/schema-validator) [

**XML Sitemap Validator**

Test sitemap structure & validate against Google requirements.](https://nuxtseo.com/tools/xml-sitemap-validator) [

**HTML to Markdown**

Convert webpages to clean markdown for Nuxt Content or LLMs.](https://nuxtseo.com/tools/html-to-markdown) [

**Keyword Research**

**Pro ** Find long-tail opportunities with volume & difficulty data.](https://nuxtseo.com/tools/keyword-research) [

**SERP Analyzer**

**Pro ** See who ranks for any keyword. Analyze AI Overview & SERP features.](https://nuxtseo.com/tools/serp-analyzer) [

**Domain Rankings**

**Pro ** Check keyword positions, search volume & estimated traffic.](https://nuxtseo.com/tools/domain-rankings) [**View All Tools **](https://nuxtseo.com/tools) h2. **Technical SEO Audits ** Nuxt SEO provides you with with all the tools needed to help you pass technical SEO audits on Google Lighthouse. SEO h2. **Up To Date. Always. ** Nuxt SEO was started at the end of 2022 and has received continuous bug fixes and feature improvements from the community. ![GitHub User 5326365](https://avatars.githubusercontent.com/u/5326365?s=80&v=4)![GitHub User 38922203](https://avatars.githubusercontent.com/u/38922203?s=80&v=4)![GitHub User 28706372](https://avatars.githubusercontent.com/u/28706372?s=80&v=4)![GitHub User 44604921](https://avatars.githubusercontent.com/u/44604921?s=80&v=4)![GitHub User 12596485](https://avatars.githubusercontent.com/u/12596485?s=80&v=4)![GitHub User 6196533](https://avatars.githubusercontent.com/u/6196533?s=80&v=4)![GitHub User 4778485](https://avatars.githubusercontent.com/u/4778485?s=80&v=4)![GitHub User 70809675](https://avatars.githubusercontent.com/u/70809675?s=80&v=4)![GitHub User 1840026](https://avatars.githubusercontent.com/u/1840026?s=80&v=4)![GitHub User 7005614](https://avatars.githubusercontent.com/u/7005614?s=80&v=4)![GitHub User 1233149](https://avatars.githubusercontent.com/u/1233149?s=80&v=4)![GitHub User 37402126](https://avatars.githubusercontent.com/u/37402126?s=80&v=4)![GitHub User 59267857](https://avatars.githubusercontent.com/u/59267857?s=80&v=4)![GitHub User 127685984](https://avatars.githubusercontent.com/u/127685984?s=80&v=4)![GitHub User 6649305](https://avatars.githubusercontent.com/u/6649305?s=80&v=4)![GitHub User 1319995](https://avatars.githubusercontent.com/u/1319995?s=80&v=4)![GitHub User 43475742](https://avatars.githubusercontent.com/u/43475742?s=80&v=4)![GitHub User 83548](https://avatars.githubusercontent.com/u/83548?s=80&v=4)![GitHub User 45267552](https://avatars.githubusercontent.com/u/45267552?s=80&v=4)![GitHub User 10612835](https://avatars.githubusercontent.com/u/10612835?s=80&v=4)![GitHub User 60323306](https://avatars.githubusercontent.com/u/60323306?s=80&v=4)![GitHub User 96652894](https://avatars.githubusercontent.com/u/96652894?s=80&v=4)![GitHub User 4960853](https://avatars.githubusercontent.com/u/4960853?s=80&v=4)![GitHub User 13146097](https://avatars.githubusercontent.com/u/13146097?s=80&v=4)![GitHub User 10812694](https://avatars.githubusercontent.com/u/10812694?s=80&v=4)![GitHub User 88148092](https://avatars.githubusercontent.com/u/88148092?s=80&v=4)![GitHub User 1658644](https://avatars.githubusercontent.com/u/1658644?s=80&v=4)![GitHub User 58269084](https://avatars.githubusercontent.com/u/58269084?s=80&v=4)![GitHub User 52504463](https://avatars.githubusercontent.com/u/52504463?s=80&v=4)![GitHub User 48283236](https://avatars.githubusercontent.com/u/48283236?s=80&v=4)![GitHub User 13064722](https://avatars.githubusercontent.com/u/13064722?s=80&v=4)![GitHub User 2766008](https://avatars.githubusercontent.com/u/2766008?s=80&v=4)![GitHub User 469009](https://avatars.githubusercontent.com/u/469009?s=80&v=4)![GitHub User 34515355](https://avatars.githubusercontent.com/u/34515355?s=80&v=4)![GitHub User 1107521](https://avatars.githubusercontent.com/u/1107521?s=80&v=4)![GitHub User 7123667](https://avatars.githubusercontent.com/u/7123667?s=80&v=4)![GitHub User 78361788](https://avatars.githubusercontent.com/u/78361788?s=80&v=4)![GitHub User 77567](https://avatars.githubusercontent.com/u/77567?s=80&v=4)![GitHub User 13056429](https://avatars.githubusercontent.com/u/13056429?s=80&v=4)![GitHub User 49156174](https://avatars.githubusercontent.com/u/49156174?s=80&v=4)![GitHub User 17025257](https://avatars.githubusercontent.com/u/17025257?s=80&v=4)![GitHub User 12688139](https://avatars.githubusercontent.com/u/12688139?s=80&v=4)![GitHub User 2822227](https://avatars.githubusercontent.com/u/2822227?s=80&v=4)![GitHub User 20121604](https://avatars.githubusercontent.com/u/20121604?s=80&v=4)![GitHub User 144150970](https://avatars.githubusercontent.com/u/144150970?s=80&v=4)![GitHub User 30938967](https://avatars.githubusercontent.com/u/30938967?s=80&v=4)![GitHub User 731073](https://avatars.githubusercontent.com/u/731073?s=80&v=4)![GitHub User 6578052](https://avatars.githubusercontent.com/u/6578052?s=80&v=4)![GitHub User 1836701](https://avatars.githubusercontent.com/u/1836701?s=80&v=4)![GitHub User 16214725](https://avatars.githubusercontent.com/u/16214725?s=80&v=4)![GitHub User 26404060](https://avatars.githubusercontent.com/u/26404060?s=80&v=4)![GitHub User 20051792](https://avatars.githubusercontent.com/u/20051792?s=80&v=4)![GitHub User 866499](https://avatars.githubusercontent.com/u/866499?s=80&v=4)![GitHub User 4572799](https://avatars.githubusercontent.com/u/4572799?s=80&v=4)![GitHub User 25174262](https://avatars.githubusercontent.com/u/25174262?s=80&v=4)![GitHub User 67317883](https://avatars.githubusercontent.com/u/67317883?s=80&v=4)![GitHub User 2013388](https://avatars.githubusercontent.com/u/2013388?s=80&v=4)![GitHub User 16446824](https://avatars.githubusercontent.com/u/16446824?s=80&v=4)![GitHub User 32813692](https://avatars.githubusercontent.com/u/32813692?s=80&v=4)![GitHub User 2047945](https://avatars.githubusercontent.com/u/2047945?s=80&v=4)![GitHub User 50038825](https://avatars.githubusercontent.com/u/50038825?s=80&v=4)![GitHub User 90514161](https://avatars.githubusercontent.com/u/90514161?s=80&v=4)![GitHub User 48188258](https://avatars.githubusercontent.com/u/48188258?s=80&v=4)![GitHub User 7257092](https://avatars.githubusercontent.com/u/7257092?s=80&v=4)![GitHub User 14162739](https://avatars.githubusercontent.com/u/14162739?s=80&v=4)![GitHub User 112722215](https://avatars.githubusercontent.com/u/112722215?s=80&v=4)![GitHub User 33375791](https://avatars.githubusercontent.com/u/33375791?s=80&v=4)![GitHub User 328718](https://avatars.githubusercontent.com/u/328718?s=80&v=4)![GitHub User 8945203](https://avatars.githubusercontent.com/u/8945203?s=80&v=4)![GitHub User 13484795](https://avatars.githubusercontent.com/u/13484795?s=80&v=4)![GitHub User 59170152](https://avatars.githubusercontent.com/u/59170152?s=80&v=4)![GitHub User 78470999](https://avatars.githubusercontent.com/u/78470999?s=80&v=4)![GitHub User 2629739](https://avatars.githubusercontent.com/u/2629739?s=80&v=4)![GitHub User 2229946](https://avatars.githubusercontent.com/u/2229946?s=80&v=4)![GitHub User 11247099](https://avatars.githubusercontent.com/u/11247099?s=80&v=4)![GitHub User 5359825](https://avatars.githubusercontent.com/u/5359825?s=80&v=4)![GitHub User 7423087](https://avatars.githubusercontent.com/u/7423087?s=80&v=4)![GitHub User 72013831](https://avatars.githubusercontent.com/u/72013831?s=80&v=4)![GitHub User 106754824](https://avatars.githubusercontent.com/u/106754824?s=80&v=4)![GitHub User 15009722](https://avatars.githubusercontent.com/u/15009722?s=80&v=4)![GitHub User 18027877](https://avatars.githubusercontent.com/u/18027877?s=80&v=4)![GitHub User 8042044](https://avatars.githubusercontent.com/u/8042044?s=80&v=4)![GitHub User 51825926](https://avatars.githubusercontent.com/u/51825926?s=80&v=4)![GitHub User 904724](https://avatars.githubusercontent.com/u/904724?s=80&v=4)![GitHub User 5037600](https://avatars.githubusercontent.com/u/5037600?s=80&v=4)![GitHub User 13403295](https://avatars.githubusercontent.com/u/13403295?s=80&v=4)![GitHub User 94787322](https://avatars.githubusercontent.com/u/94787322?s=80&v=4)![GitHub User 20974624](https://avatars.githubusercontent.com/u/20974624?s=80&v=4)![GitHub User 7954293](https://avatars.githubusercontent.com/u/7954293?s=80&v=4)![GitHub User 5989846](https://avatars.githubusercontent.com/u/5989846?s=80&v=4)![GitHub User 110889225](https://avatars.githubusercontent.com/u/110889225?s=80&v=4)![GitHub User 47750560](https://avatars.githubusercontent.com/u/47750560?s=80&v=4)![GitHub User 20380298](https://avatars.githubusercontent.com/u/20380298?s=80&v=4)![GitHub User 25445516](https://avatars.githubusercontent.com/u/25445516?s=80&v=4)![GitHub User 6450536](https://avatars.githubusercontent.com/u/6450536?s=80&v=4)![GitHub User 1983246](https://avatars.githubusercontent.com/u/1983246?s=80&v=4)![GitHub User 40447](https://avatars.githubusercontent.com/u/40447?s=80&v=4)![GitHub User 1246641](https://avatars.githubusercontent.com/u/1246641?s=80&v=4)![GitHub User 7460168](https://avatars.githubusercontent.com/u/7460168?s=80&v=4)![GitHub User 2862673](https://avatars.githubusercontent.com/u/2862673?s=80&v=4)![GitHub User 640208](https://avatars.githubusercontent.com/u/640208?s=80&v=4)![GitHub User 73585109](https://avatars.githubusercontent.com/u/73585109?s=80&v=4)![GitHub User 122052334](https://avatars.githubusercontent.com/u/122052334?s=80&v=4) 6.3K Commits 1.1K Issues Closed 103 Contributors h2. **Loved by Nuxt Developers ** Nuxt SEO was built for the community. Here's what some of them have to say. **78K downloads**per day, on average Nuxt SEO is used and trusted by thousands of developers and companies around the world. 2.4M Downloads/ month 3.2K Total Stars h2. **Funded by the community ** Nuxt SEO is completely free and open-source due to the generous support of the community. [**Become a sponsor **](https://github.com/sponsors/harlan-zw) **Top Sponsors ** [![Daniel Roe](https://avatars.githubusercontent.com/u/28706372?u=c4a5aa5232c09c3248533366d5c93a138d7e8987&v=4) **Daniel Roe** roe.dev](https://roe.dev) **Gold Sponsors ** [![Johann Schopplich](https://avatars.githubusercontent.com/u/27850750?u=19ad2478f28905f0659f88c3f81f96b0910b8047&v=4) **Johann Schopplich** johannschopplich.com](https://johannschopplich.com) [![Massive Monster](https://avatars.githubusercontent.com/u/11852534?v=4) **Massive Monster** massivemonster.co](https://massivemonster.co) [![Localazy](https://avatars.githubusercontent.com/u/59409404?v=4) **Localazy** localazy.com](https://localazy.com) [![SMTP Labs](https://avatars.githubusercontent.com/u/89388475?v=4) **SMTP Labs** smtp.dev](https://smtp.dev) **Backers ** [![Jan-Henrik Damaschke](https://avatars.githubusercontent.com/u/15030951?u=724eb8ee4cae7792ddf13e25c31589dd6c5cb1d1&v=4)](https://itinsights.org) [![Mike Gifford](https://avatars.githubusercontent.com/u/116832?u=22ad1e2212885bcd11e7754d1cefac5aff6e45c1&v=4)](https://github.com/mgifford) [![timhanlon](https://avatars.githubusercontent.com/u/4340187?u=86cabd9543038313f2057b790f8e6d901488cd48&v=4)](https://github.com/timhanlon) [![Björn Büttner](https://avatars.githubusercontent.com/u/7874631?u=dbcb7fa18f2d10b54426fe10451472a0f5e5fca3&v=4)](https://github.com/Idrinth) [![Milos Dimitrijevic](https://avatars.githubusercontent.com/u/46306967?u=771a9a638e8d2d13b28a2e0f2d357cc45e0d298a&v=4)](https://github.com/milos018) [![Marko](https://avatars.githubusercontent.com/u/2497323?u=189c706b73e0495b56688589447d3e46b8045f73&v=4)](https://github.com/aussieboi) [![Christian Ducrot](https://avatars.githubusercontent.com/u/3525119?u=6b662d7c909bb2c725194f0f9290d2b8e78b7188&v=4)](https://ducrot.de) [![Maik](https://avatars.githubusercontent.com/u/16877165?u=51e3a642e812d53c7d51037f4cf421e9faf9a3f9&v=4)](https://github.com/just-maik) [![João Carmona](https://avatars.githubusercontent.com/u/411763?u=e1539021b583abe14d08fb5c8a30f18309b19b00&v=4)](https://github.com/jpsc) [![Froggly](https://avatars.githubusercontent.com/u/79277835?v=4)](https://froggly.pl) [![reshepe](https://avatars.githubusercontent.com/u/175365848?v=4)](https://reshepe.dev) [![Generate Ads AI](https://avatars.githubusercontent.com/u/210313732?v=4)](https://generateads.ai) [![AdKit](https://avatars.githubusercontent.com/u/242531394?v=4)](https://adkit.so) [![axelparisflashfiscal](https://avatars.githubusercontent.com/u/248088490?v=4)](https://github.com/axelparisflashfiscal) --- ### Social Share Debugger - Preview & Clear OG Cache · Nuxt SEO Source: https://nuxtseo.com/tools/social-share-debugger Description: Preview how your links appear on Twitter, Facebook, LinkedIn, Slack and Discord. Debug OG tags and clear platform caches instantly. h1. **Social Share Debugger** Preview how your links appear on Twitter, Facebook, LinkedIn, Slack and Discord. Debug OG tags and clear platform caches instantly. h2. **Clear Platform Cache ** h3. **Facebook Sharing Debugger** Clear Facebook cache and force re-scrape [**Open Tool **](https://developers.facebook.com/tools/debug/) h3. **Twitter Card Validator** Validate and preview Twitter cards [**Open Tool **](https://cards-dev.twitter.com/validator) h3. **LinkedIn Post Inspector** Inspect and refresh LinkedIn previews [**Open Tool **](https://www.linkedin.com/post-inspector/) h2. **Why Isn't My OG Image Updating? ** Social platforms cache link previews aggressively to reduce server load. After updating your OG tags, you'll need to manually clear each platform's cache. h3. **Facebook ** Caches for approximately **30 days**. Force refresh via the [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/). Click "Scrape Again" to fetch fresh data. h3. **Twitter/X ** Caches until you request a re-scrape via the [Card Validator](https://cards-dev.twitter.com/validator). Note: The validator UI was removed but the API still works. h3. **LinkedIn ** Has a particularly sticky cache. Clear via [Post Inspector](https://www.linkedin.com/post-inspector/). May take up to 7 days for cache to fully refresh. h3. **Pro Tips for OG Images ** - Use **1200x630px** for optimal display across platforms - Keep important content in the center 800x400px safe zone - Use absolute URLs for og:image (relative URLs won't work) - Add cache-busting query params when updating: `?v=2` h2. **Official References** [**The Open Graph Protocol**](https://ogp.me/) Official specification for og:title, og:description, og:image and more. [**Twitter Cards Documentation**](https://developer.x.com/en/docs/twitter-for-websites/cards/overview/abouts-cards) Official X/Twitter Cards specification and card types. [**Facebook Sharing Best Practices**](https://developers.facebook.com/docs/sharing/webmasters/) Meta's guide to optimizing link shares on Facebook and Instagram. h2. **Related Resources** [**OG Image Module**](https://nuxtseo.com/tools/social-share-debugger/docs/og-image/getting-started/introduction) [**Open Graph Guide**](https://nuxtseo.com/tools/social-share-debugger/learn-seo/nuxt/mastering-meta/open-graph) [**Twitter Cards**](https://nuxtseo.com/tools/social-share-debugger/learn-seo/nuxt/mastering-meta/twitter-cards) [**Vue Social Sharing**](https://nuxtseo.com/tools/social-share-debugger/learn-seo/vue/mastering-meta/social-sharing) [**Meta Tag Checker**](https://nuxtseo.com/tools/social-share-debugger/tools/meta-tag-checker) --- ### Meta Tag Checker - Test Title & Description Length · Nuxt SEO Source: https://nuxtseo.com/tools/meta-tag-checker Description: Check meta tags from any URL. Validate title and description length. Preview how your page appears in Google search results. h1. **Meta Tag Checker** Check meta tags from any URL. Preview how your page appears in Google search results. h2. **Meta Tag Length Limits ** h3. **Title Tag ** - Keep under 60 characters to avoid truncation - 51-60 chars have lowest rewrite rates (39-42%) - Front-load keywords—users may only see first 50 [Learn title best practices ](https://nuxtseo.com/tools/meta-tag-checker/learn-seo/vue/mastering-meta/titles) h3. **Meta Description ** - Desktop: ~155-160 chars, mobile: ~120 - Google rewrites 62-70% of descriptions - Front-load value for mobile truncation [Learn description best practices ](https://nuxtseo.com/tools/meta-tag-checker/learn-seo/vue/mastering-meta/descriptions) h2. **Why Pixel Width Matters ** Characters have different widths. "iiiiii" is narrower than "WWWWWW". This tool estimates actual pixel width, not just character count, giving you a more accurate picture of how your titles will display. h2. **Are Your Meta Tags Server-Rendered? ** Search engines need meta tags in the initial HTML response. Client-side rendered meta tags (added by JavaScript after page load) may not be indexed. **Good news:** This tool fetches your page exactly like a search engine would—no JavaScript execution. If your meta tags appear above, they're server-rendered correctly. h3. **Manual Check ** 1. View Page Source (Ctrl+U / Cmd+U) 2. Search for your tag 3. If it's missing or shows a placeholder, you have an SSR problem h2. **Solutions by Framework ** Nuxt Use `useSeoMeta()` or `useHead()` - SSR by default. [Nuxt SEO Docs →](https://nuxtseo.com/tools/meta-tag-checker/docs/nuxt-seo/getting-started/introduction) Vue Use [Unhead](https://unhead.unjs.io) with SSR or prerendering. [Vue SEO Guide →](https://nuxtseo.com/tools/meta-tag-checker/learn-seo/vue/mastering-meta) Next.js Use `generateMetadata` (App Router) or `<Head>` (Pages Router). [Next.js Docs →](https://nextjs.org/docs/app/building-your-application/optimizing/metadata) React Use [react-helmet](https://github.com/nfl/react-helmet) or [Unhead](https://unhead.unjs.io). Requires SSR framework (Next.js, Remix) for SEO. Astro Set tags in `<head>` directly or use [@astrojs/sitemap](https://docs.astro.build/en/guides/integrations-guide/sitemap/). SSR by default. SvelteKit Use `<svelte:head>` in pages. [Svelte Docs →](https://svelte.dev/docs/svelte/svelte-head) Remix Export `meta` function from routes. [Remix Docs →](https://remix.run/docs/en/main/route/meta) SolidStart Use `<Title>` and `<Meta>` from [@solidjs/meta](https://docs.solidjs.com/solid-meta). h2. **Official References** [**Google: Title Links in Search Results**](https://developers.google.com/search/docs/appearance/title-link) Official documentation on how Google generates title links and best practices. [**Google: Control Snippets in Search Results**](https://developers.google.com/search/docs/appearance/snippet) How Google uses meta descriptions and generates snippets. [**MDN: <meta> Element Reference**](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) Complete HTML meta tag specification and attributes. [**The Open Graph Protocol**](https://ogp.me/) Official specification for Open Graph meta tags. h2. **Related Resources** [**Title Best Practices**](https://nuxtseo.com/tools/meta-tag-checker/learn-seo/vue/mastering-meta/titles) [**Description Best Practices**](https://nuxtseo.com/tools/meta-tag-checker/learn-seo/vue/mastering-meta/descriptions) [**Social Sharing Guide**](https://nuxtseo.com/tools/meta-tag-checker/learn-seo/vue/mastering-meta/social-sharing) [**useSeoMeta Docs**](https://nuxtseo.com/tools/meta-tag-checker/docs/seo-utils/api/use-seo-meta) [**Social Share Debugger**](https://nuxtseo.com/tools/meta-tag-checker/tools/social-share-debugger) --- ### XML Sitemap Validator - Test & Validate Sitemaps · Nuxt SEO Source: https://nuxtseo.com/tools/xml-sitemap-validator Description: Validate XML sitemaps for errors, check URL format, and ensure search engine compliance. Test sitemap structure, validate against Google requirements, and optimize crawl efficiency. h1. **XML Sitemap Validator** Validate XML sitemaps for errors and ensure search engine compliance. Test sitemap structure and optimize for better crawl efficiency. h2. **What Makes a Valid Sitemap? ** XML sitemaps help search engines discover and crawl your pages efficiently. A valid sitemap follows specific rules: - **Proper namespace:** Must include xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" - **Size limits:** Maximum 50,000 URLs and 50MB uncompressed - **Valid URLs:** Absolute URLs with proper encoding - **Date format:** W3C datetime format (YYYY-MM-DD) h2. **Common Sitemap Errors ** Invalid XML Missing closing tags, incorrect nesting, or unescaped characters (&, <, >). Always escape special characters. Wrong date Using formats like "01/15/2024" instead of "2024-01-15". Use W3C datetime format. Relative URLs Using "/page" instead of "https://example.com/page". Always use absolute URLs. Too large Over 50,000 URLs or 50MB file size. Use sitemap index for large sites. h2. **Sitemap Best Practices ** h3. **What to Include ** - Important content pages - Recently updated pages - Pages hard to discover through navigation - Canonical versions of URLs only h3. **What to Exclude ** - Duplicate or near-duplicate pages - Session ID URLs - API endpoints - Admin or private pages - 404 or error pages h2. **Submitting Your Sitemap ** Once validated, submit your sitemap to search engines: 1. **Google:** Submit through [Search Console](https://search.google.com/search-console) under Indexing → Sitemaps 2. **Bing:** Submit through [Bing Webmaster Tools](https://www.bing.com/webmasters) 3. **Robots.txt:** Add `Sitemap: https://example.com/sitemap.xml` to your robots.txt h2. **Dynamic Sitemaps with Nuxt ** Generate sitemaps automatically with the Nuxt Sitemap module: ``` h1. Install the module npm install @nuxtjs/sitemap h1. Configure in nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/sitemap'], sitemap: { // Auto-discovery enabled by default exclude: ['/admin/**', '/api/**'] } }) ``` [Learn more about Nuxt Sitemap →](https://nuxtseo.com/tools/xml-sitemap-validator/docs/sitemap/getting-started/introduction) h2. **Official References** [**Sitemaps Protocol**](https://www.sitemaps.org/protocol.html) Official specification for XML sitemaps including format and requirements. [**Google: Build and Submit a Sitemap**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) Google's guide to creating and submitting sitemaps. [**Google Search Console**](https://search.google.com/search-console) Submit and monitor your sitemap for indexing. [**Bing Webmaster Tools**](https://www.bing.com/webmasters) Submit your sitemap to Bing for indexing. h2. **Related Resources** [**Sitemap Module**](https://nuxtseo.com/tools/xml-sitemap-validator/docs/sitemap/getting-started/introduction) [**Robots.txt Module**](https://nuxtseo.com/tools/xml-sitemap-validator/docs/robots/getting-started/introduction) [**Sitemap Guide**](https://nuxtseo.com/tools/xml-sitemap-validator/learn-seo/nuxt/controlling-crawlers/sitemaps) [**Meta Tag Checker**](https://nuxtseo.com/tools/xml-sitemap-validator/tools/meta-tag-checker) [**Nuxt SEO Docs**](https://nuxtseo.com/tools/xml-sitemap-validator/docs/nuxt-seo/getting-started/introduction) --- ### Schema.org Validator - Test Structured Data · Nuxt SEO Source: https://nuxtseo.com/tools/schema-validator Description: Validate Schema.org structured data on any website. Test JSON-LD and Microdata markup, find errors, and ensure proper implementation for rich snippets. h1. **Schema.org Validator** Validate structured data on any website. Test Schema.org markup, identify errors, and ensure proper implementation for rich snippets. h2. **What is Schema.org Structured Data? ** Schema.org provides a shared vocabulary that search engines understand. When you add structured data to your pages, you're explicitly telling search engines what your content means, not just what it says. - **Rich snippets:** Enhanced search results with ratings, prices, availability - **Better understanding:** Help search engines comprehend your content's context - **Higher CTR:** Rich results can increase click-through rates by 30%+ h2. **Popular Schema Types ** h3. **Content Types ** - **Article:** News, blog posts, sports articles - **FAQPage:** Frequently asked questions - **HowTo:** Step-by-step guides - **Recipe:** Cooking instructions h3. **Business Types ** - **Product:** Physical or digital products - **Organization:** Companies, NGOs, clubs - **LocalBusiness:** Stores, restaurants - **SoftwareApplication:** Apps, tools h2. **JSON-LD Implementation ** JSON-LD is Google's recommended format for structured data. Add it to your page's <head> or <body>: ``` <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Article", "headline": "Your Article Title", "author": { "@type": "Person", "name": "Author Name" }, "datePublished": "2024-01-15", "image": "https://example.com/image.jpg" } </script> ``` h2. **Common Validation Errors ** Missing @type Every schema must specify its type (Article, Product, etc). Always include "@type" property. Invalid dates Dates must be in ISO 8601 format (YYYY-MM-DD). Use proper date formatting. Missing required Each type has required properties that must be included. Check schema.org documentation. Invalid URLs URLs must be absolute and properly formatted. Include full https:// URLs. h2. **Structured Data with Nuxt ** Use the Nuxt Schema.org module for type-safe structured data: ``` h1. Install the module npm install @nuxtjs/schema-org h1. Use in your Vue components <script setup> useSchemaOrg([ defineArticle({ headline: 'My Article Title', author: 'John Doe', datePublished: new Date('2024-01-15'), image: '/og-image.jpg' }) ]) </script> ``` [Learn more about Nuxt Schema.org →](https://nuxtseo.com/tools/schema-validator/docs/schema-org/getting-started/introduction) h2. **Official References** [**Schema.org Documentation**](https://schema.org/docs/gs.html) Official getting started guide for implementing structured data. [**Google Structured Data**](https://developers.google.com/search/docs/appearance/structured-data) Google's guide to structured data for rich results. [**Rich Results Test**](https://search.google.com/test/rich-results) Google's tool for testing structured data and preview rich results. [**Schema.org Types**](https://schema.org/docs/full.html) Complete reference of all available Schema.org types and properties. h2. **Related Resources** [**Schema.org Module**](https://nuxtseo.com/tools/schema-validator/docs/schema-org/getting-started/introduction) [**Structured Data Guide**](https://nuxtseo.com/tools/schema-validator/learn-seo/nuxt/controlling-crawlers/structured-data) [**Meta Tag Checker**](https://nuxtseo.com/tools/schema-validator/tools/meta-tag-checker) [**Nuxt SEO Docs**](https://nuxtseo.com/tools/schema-validator/docs/nuxt-seo/getting-started/introduction) [**Article Schema Recipe**](https://nuxtseo.com/tools/schema-validator/recipes/schema-org/article) --- ### HTML to Markdown Converter - Convert URLs & HTML · Nuxt SEO Source: https://nuxtseo.com/tools/html-to-markdown Description: Convert any webpage or HTML to clean markdown. Extract article content for Nuxt Content, LLMs, or documentation migration. h1. **HTML to Markdown Converter** Convert any webpage or HTML to clean markdown. Perfect for Nuxt Content, LLMs, or documentation migration. **Extract article content only** Remove navigation, footer, ads h2. **Convert Any Webpage to Markdown ** Enter a URL to extract and convert the page content to clean markdown. h3. **How It Works ** Powered by [mdream](https://github.com/harlan-zw/mdream), a fast HTML to Markdown converter. 1. Fetches the webpage HTML 2. Extracts main content (removes nav, footer, ads) 3. Converts HTML elements to markdown syntax 4. Outputs clean, portable markdown h3. **Use Cases ** - **Documentation migration:** Move docs from legacy CMS to Nuxt Content - **LLM context:** Feed clean markdown to ChatGPT, Claude, or RAG pipelines - **Content archival:** Save articles in portable format - **Blog migration:** Convert WordPress posts to markdown files h2. **Convert HTML to Markdown for LLMs ** Large language models work better with markdown than HTML: - **Smaller token count:** Markdown is more compact - **Cleaner structure:** No style attributes or wrapper divs - **Better parsing:** LLMs understand markdown headings, lists, links h4. For RAG Pipelines 1. Scrape source pages 2. Convert to markdown (this tool) 3. Chunk by headings 4. Embed and index h2. **Official References** [**CommonMark Specification**](https://commonmark.org/help/) The standard Markdown syntax reference used by most tools. [**GitHub Flavored Markdown**](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) Extended Markdown syntax with tables, task lists, and strikethrough. [**Nuxt Content Documentation**](https://content.nuxt.com/) Use converted Markdown with Nuxt Content for content-driven sites. h2. **Related Resources** [**Meta Tag Checker**](https://nuxtseo.com/tools/html-to-markdown/tools/meta-tag-checker) [**Nuxt SEO Docs**](https://nuxtseo.com/tools/html-to-markdown/docs/nuxt-seo/getting-started/introduction) [**llms.txt Guide**](https://nuxtseo.com/tools/html-to-markdown/learn-seo/nuxt/controlling-crawlers/llms-txt) [**AI Ready Module**](https://nuxtseo.com/tools/html-to-markdown/docs/ai-ready/getting-started/introduction) [**Vue SEO Guide**](https://nuxtseo.com/tools/html-to-markdown/learn-seo/vue) --- ### Robots.txt Generator & Tester - Block AI Crawlers · Nuxt SEO Source: https://nuxtseo.com/tools/robots-txt-generator Description: Generate robots.txt with AI crawler presets. Test rules against Googlebot, GPTBot, ClaudeBot and 50+ user agents. Validate syntax instantly. h1. **Robots.txt Generator & Tester** Generate robots.txt with AI crawler presets. Test rules instantly. h2. **Rules ** 1. Disallow *(none) * Allow Sitemaps Content-Usage [IETF](https://datatracker.ietf.org/doc/draft-ietf-aipref-vocab/) search train-ai Content-Signal [Cloudflare](https://blog.cloudflare.com/content-signals-policy/) search ai-input ai-train "No preference" excludes from output robots.txt ``` h1. Generated by Nuxt SEO h1. https://nuxtseo.com/tools/robots-txt-generator User-agent: * Allow: / ``` h2. **Test Your Rules ** URL Path to test User-agent Access granted! Matched: User-agent: * Allow: / h2. **Common User Agents ** h3. **Search Engines** [Googlebot ](https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers) Google Search (28% of bot traffic) [Bingbot ](https://www.bing.com/webmasters/help/which-crawlers-does-bing-use-8c184ec0) Microsoft Bing Search [DuckDuckBot ](https://duckduckgo.com/duckduckbot) DuckDuckGo Search [YandexBot ](https://yandex.com/support/webmaster/robot-workings/check-yandex-robots.html) Yandex Search (Russia) [Baiduspider ](https://www.baidu.com/search/robots_english.html) Baidu Search (China) h3. **AI Crawlers** [GPTBot ](https://platform.openai.com/docs/bots) OpenAI model training (7.5% of bot traffic, most blocked bot) [ChatGPT-User ](https://platform.openai.com/docs/bots) ChatGPT live browsing [OAI-SearchBot ](https://platform.openai.com/docs/bots) ChatGPT Search feature [ClaudeBot ](https://www.anthropic.com/crawlers-info) Anthropic model training [Claude-Web ](https://www.anthropic.com/crawlers-info) Claude live browsing [anthropic-ai ](https://www.anthropic.com/crawlers-info) Anthropic data collection [CCBot ](https://commoncrawl.org/ccbot) Common Crawl dataset (Frequently blocked, used by many AI labs) [Google-Extended ](https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers#google-extended) Gemini/Bard training (separate from Search) [PerplexityBot ](https://docs.perplexity.ai/guides/bots) Perplexity AI search Bytespider TikTok/ByteDance AI [Amazonbot ](https://developer.amazon.com/amazonbot) Amazon Alexa training [cohere-ai ](https://cohere.com/robots) Cohere model training [Meta-ExternalAgent ](https://www.facebook.com/externalhit_uatext.php) Meta AI training [meta-externalfetcher ](https://www.facebook.com/externalhit_uatext.php) Meta data fetching [Applebot-Extended ](https://support.apple.com/en-us/119829) Apple AI training (not Search) h3. **Social Platforms** [facebookexternalhit ](https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/) Facebook/Meta link previews [Twitterbot ](https://developer.x.com/en/docs/x-for-websites/cards/guides/getting-started) Twitter/X link previews [LinkedInBot ](https://www.linkedin.com/help/linkedin/answer/a521928) LinkedIn link previews [Slackbot ](https://api.slack.com/robots) Slack link previews Discordbot Discord link previews WhatsApp WhatsApp link previews TelegramBot Telegram link previews Source: [Cloudflare 2025 Bot Traffic Report](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/) h2. **Directives ** h3. **Core Directives ** `User-agent: *` applies to all crawlers `Disallow: /` block entire site `Allow: /` explicitly allow (for exceptions) `Crawl-delay: 10` wait 10s between requests (Bing/Yandex) `Sitemap: URL` specify sitemap location h3. **Content-Usage ** [IETF](https://datatracker.ietf.org/doc/draft-ietf-aipref-vocab/) Uses `y`/`n` values `Content-Usage: search=y` allow search indexing `Content-Usage: train-ai=n` disallow AI model training Combine: `search=y, train-ai=n` h3. **Content-Signal ** [Cloudflare](https://blog.cloudflare.com/content-signals-policy/) Uses `yes`/`no` values `search=yes` allow search indexing `ai-input=no` disallow live AI answers `ai-train=no` disallow model training h3. **Pattern Matching ** `*` matches any sequence `$` matches end of URL `/*.pdf` all .pdf files `/*.php$` URLs ending in .php h2. **Common Patterns ** `Disallow: /admin/`Block /admin/ directory `Disallow: /*?`Block URLs with query strings `Disallow: /*.json$`Block all .json files `Disallow: /private/*`Block everything under /private/ `Allow: /api/public`Allow specific path (exception) h2. **Block AI Training ** [GPTBot: most blocked bot 2024 →](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/) $ top AI crawlers to block `GPTBot``ClaudeBot``CCBot``Google-Extended``Bytespider``PerplexityBot` Google-Extended = Gemini training (not Search) $ content preference headers [IETF](https://datatracker.ietf.org/doc/draft-ietf-aipref-vocab/)`Content-Usage: search=y, train-ai=n` [Cloudflare](https://blog.cloudflare.com/content-signals-policy/)`Content-Signal: search=yes, ai-train=no` ai-input = live answers ai-train = model training h2. **Learn More ** [<h2>**Robots.txt Guide**</h2>Complete guide to robots.txt syntax, common patterns, and security considerations.](https://nuxtseo.com/tools/robots-txt-generator/learn-seo/vue/controlling-crawlers/robots-txt) [<h2>**XML Sitemaps**</h2>How sitemaps work with robots.txt to guide crawler discovery.](https://nuxtseo.com/tools/robots-txt-generator/learn-seo/vue/controlling-crawlers/sitemaps) [<h2>**Meta Robots Tags**</h2>Page-level control when robots.txt isn't enough.](https://nuxtseo.com/tools/robots-txt-generator/learn-seo/vue/controlling-crawlers/meta-tags) h2. **Need Dynamic Robots.txt? ** Generate robots.txt rules dynamically based on routes, environments, or user conditions with the Nuxt Robots module. [**Explore Nuxt Robots **](https://nuxtseo.com/tools/robots-txt-generator/docs/robots/getting-started/introduction) **Was this tool helpful? ** Your feedback helps us improve --- ### SEO Tools for Developers · Nuxt SEO Source: https://nuxtseo.com/tools Description: Developer tools for SEO. Debug social share cards, generate robots.txt, check meta tags, keyword research, SERP analysis, and more. h1. **SEO Tools ** Developer utilities for SEO. Pro tools offer 2 free requests/day. [<h2>**Keyword Research ****Pro **</h2>Find keyword opportunities with volume, difficulty, and intent data.](https://nuxtseo.com/tools/tools/keyword-research) [<h2>**SERP Analyzer ****Pro **</h2>See who ranks for any keyword and what SERP features are present.](https://nuxtseo.com/tools/tools/serp-analyzer) [<h2>**Domain Rankings ****Pro **</h2>Check what keywords any domain ranks for in Google.](https://nuxtseo.com/tools/tools/domain-rankings) [<h2>**Social Share Debugger **</h2>Preview and debug social share cards across Twitter, Facebook, LinkedIn, and more.](https://nuxtseo.com/tools/tools/social-share-debugger) [<h2>**Robots.txt Generator **</h2>Generate and test robots.txt rules. Block AI crawlers with one click.](https://nuxtseo.com/tools/tools/robots-txt-generator) [<h2>**Meta Tag Checker **</h2>Check meta tags from any URL. Preview SERP appearance and validate lengths.](https://nuxtseo.com/tools/tools/meta-tag-checker) [<h2>**HTML to Markdown **</h2>Convert webpages to clean markdown for docs, LLMs, or content migration.](https://nuxtseo.com/tools/tools/html-to-markdown) [<h2>**XML Sitemap Validator **</h2>Validate XML sitemaps for errors, check URL format, and ensure search engine compliance.](https://nuxtseo.com/tools/tools/xml-sitemap-validator) [<h2>**Schema.org Validator **</h2>Validate structured data on any website. Test Schema.org markup and ensure proper implementation.](https://nuxtseo.com/tools/tools/schema-validator) --- ### SERP Keyword Research Tool · Nuxt SEO Pro Source: https://nuxtseo.com/tools/keyword-research Description: Research keywords with volume, difficulty, and intent data. Find long-tail opportunities and track your research history. h1. **Keyword Research** Find keyword opportunities with volume, difficulty, and intent data. Try these h2. **Understanding Keyword Metrics ** h3. **Search Volume ** Monthly search volume estimates how many times a keyword is searched. Higher isn't always better—10,000 searches with 0.1% CTR means fewer clicks than 500 searches with 10% CTR. - 10-100: Very long-tail, specific intent - 100-1K: Long-tail, good for targeting - 1K-10K: Medium competition - 10K+: High competition, broad intent h3. **Keyword Difficulty ** A 0-100 score estimating how hard it is to rank. Based on backlink profiles of current top-ranking pages. New sites should target 0-30; established sites can compete at 50+. - 0-30: Easy - achievable with quality content - 30-50: Medium - needs strong content + some links - 50-70: Hard - requires authority + link building - 70+: Very hard - major authority sites only h2. **Search Intent Types ** Matching search intent is more important than keyword volume. Google ranks pages that best satisfy what searchers want. h3. **Informational ** "how to", "what is", "guide to" — User wants to learn. Create comprehensive guides, tutorials, explainers. h3. **Navigational ** Brand names, specific sites — User wants a specific page. Optimize your brand pages and ensure you rank for your own name. h3. **Commercial ** "best", "vs", "review", "top 10" — User is researching before buying. Create comparison content, reviews, listicles. h3. **Transactional ** "buy", "price", "discount", "download" — User ready to act. Optimize product/service pages with clear CTAs. h2. **Finding Quick Wins ** The best keyword opportunities combine decent volume with low competition. Use the "Quick Wins" preset to filter for these automatically. h3. **Ideal Quick Win Criteria ** - **Volume 100-5,000:** Enough traffic to matter, not so competitive - **Difficulty under 30:** Achievable with quality content alone - **Clear intent match:** You can satisfy what searchers want - **Topic authority:** Related to your site's existing content h2. **From Keywords to Content ** 1. 1.**Cluster related keywords** — Group keywords by topic. One page can rank for dozens of related terms if it covers the topic comprehensively. 2. 2.**Analyze the SERP** — Use the SERP Analyzer to see what's currently ranking. Match the content format that Google prefers. 3. 3.**Create better content** — Don't just match competitors—exceed them. More depth, better examples, clearer structure. 4. 4.**Optimize on-page SEO** — Use keywords naturally in title, H1, first paragraph, and throughout. Add structured data where appropriate. h2. **Official References** [**Google Keyword Planner**](https://ads.google.com/home/tools/keyword-planner/) Google's official tool for keyword research and search volume data. [**Search Intent Guide**](https://developers.google.com/search/docs/fundamentals/creating-helpful-content) Google's guidance on understanding and matching search intent. [**Long-tail Keywords Strategy**](https://ahrefs.com/blog/long-tail-keywords/) Comprehensive guide to finding and targeting long-tail keywords. h2. **Related Resources** [**SERP Analyzer**](https://nuxtseo.com/tools/keyword-research/tools/serp-analyzer) [**Domain Rankings**](https://nuxtseo.com/tools/keyword-research/tools/domain-rankings) [**Structured Data Guide**](https://nuxtseo.com/tools/keyword-research/learn-seo/nuxt/controlling-crawlers/structured-data) [**Nuxt SEO Docs**](https://nuxtseo.com/tools/keyword-research/docs/nuxt-seo/getting-started/introduction) --- ### Vue SEO Guide · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue Description: Complete Vue.js SEO resource. Meta tags, crawl control, SPA solutions, SSR frameworks, and performance monitoring. with Unhead code examples. h1. **Vue SEO Guide** Complete Vue.js SEO resource. Meta tags, crawl control, SPA solutions, SSR frameworks, and performance monitoring. with Unhead code examples. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - Vue SPAs render empty `<head>` tags. use SSR or prerendering for SEO - All examples use Unhead, the Vue 3 head manager (vue-meta has no Vue 3 support) - Google can render JavaScript but other crawlers can't. always verify with View Page Source Vue SPAs don't work with search engines by default. The `<head>` renders empty, crawlers see a blank page, and your content never gets indexed. This guide covers everything you need for Vue SEO. from basic meta tags to SSR framework selection to production monitoring. All examples use [**Unhead**](https://unhead.unjs.io/), the Vue 3 head manager that replaced vue-meta. h2. [Start Here](#start-here) **New to Vue SEO?** Start with [**Mastering Meta**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta). It covers the fundamentals: titles, descriptions, and social sharing tags with working code examples. **Need a quick audit?** Use the [**Vue SEO Checklist**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/checklist). a pre-launch, post-launch, and ongoing monitoring checklist with links to detailed guides for every item. **Already know the basics?** Jump to the section that matches your current problem. h2. [Content Sections](#content-sections) h3. [[**Mastering Meta**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta)](#mastering-meta) Meta tags control how your pages appear in search results and social shares. Learn to set titles, descriptions, Open Graph tags, Twitter Cards, and structured data with Unhead. | **Guide** | **What You'll Learn** | | --- | --- | | [**Titles**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta/titles) | Page titles, title templates, character limits | | [**Descriptions**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta/descriptions) | Meta descriptions, CTR optimization | | [**Social Sharing**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta/social-sharing) | Open Graph, Twitter Cards, preview images | | [**Schema.org**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta/schema-org) | Structured data, JSON-LD, eligibility | | [**Rich Results**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta/rich-results) | Testing tools, what still works after 2023 changes | | [**Migrating from vue-meta**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta/migrating-vue-meta) | Side-by-side syntax, breaking changes | h3. [[**Controlling Crawlers**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/controlling-crawlers)](#controlling-crawlers) Manage how search engines discover and index your pages. Configure robots.txt, sitemaps, canonical URLs, and handle redirects properly. | **Guide** | **What You'll Learn** | | --- | --- | | [**robots.txt**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/controlling-crawlers/robots-txt) | Allow/block crawlers, AI bot blocking | | [**Sitemaps**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/controlling-crawlers/sitemaps) | XML sitemap generation, submission | | [**Meta Tags**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/controlling-crawlers/meta-tags) | noindex, nofollow, per-page control | | [**Canonical URLs**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/controlling-crawlers/canonical-urls) | Duplicate content, parameter handling | | [**Redirects**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/controlling-crawlers/redirects) | 301 vs 302, redirect chains | | [**Duplicate Content**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/controlling-crawlers/duplicate-content) | Detection, causes, fixes | h3. [[**SPA SEO**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/spa)](#spa-seo) Single page applications have unique SEO challenges. Google can render JavaScript, but other crawlers can't. Learn when SSR is required and what alternatives exist. | **Guide** | **What You'll Learn** | | --- | --- | | [**Prerendering**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/spa/prerendering) | Build-time HTML generation, vite-ssg | | [**Dynamic Rendering**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/spa/dynamic-rendering) | Serve different content to bots (deprecated but functional) | | [**Hydration**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/spa/hydration) | Mismatch debugging, SEO implications | h3. [[**SSR Frameworks**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/ssr-frameworks)](#ssr-frameworks) Compare Vue server-side rendering options. Each framework has different strengths for SEO, performance, and developer experience. | **Guide** | **What You'll Learn** | | --- | --- | | [**Nuxt vs Quasar**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar) | Feature comparison, when to use each | | [**Vite SSR**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/ssr-frameworks/vite-ssr) | Custom SSR setup, manual control | | [**VitePress**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/ssr-frameworks/vitepress) | Static sites, documentation, blogs | h3. [[**Routes & Rendering**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering)](#routes-rendering) URL structure affects rankings. Rendering mode affects indexing. This section covers both. | **Guide** | **What You'll Learn** | | --- | --- | | [**URL Structure**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/url-structure) | Slugs, hyphens, length, keywords | | [**Pagination**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/pagination) | Self-referencing canonicals, infinite scroll | | [**Trailing Slashes**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/trailing-slashes) | Consistency, redirect configuration | | [**Query Parameters**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/query-parameters) | Filter params, tracking params, canonicals | | [**i18n**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/i18n) | Hreflang, x-default, bidirectional links | | [**404 Pages**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/404-pages) | Soft 404s, proper status codes | | [**Dynamic Routes**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/dynamic-routes) | Route params, per-route meta tags | | [**Rendering Modes**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/routes-and-rendering/rendering) | SSR vs SSG vs SPA comparison | h3. [[**Launch & Listen**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen)](#launch-listen) Deploy your site and monitor its search performance. Set up tracking, debug indexing issues, and handle site migrations. | **Guide** | **What You'll Learn** | | --- | --- | | [**Going Live**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/going-live) | First deployment, SSR verification | | [**Search Console**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/search-console) | Verification, reports, URL inspection | | [**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/core-web-vitals) | LCP, INP, CLS optimization for Vue | | [**Indexing Issues**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/indexing-issues) | "Crawled not indexed" fixes | | [**SEO Monitoring**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/seo-monitoring) | Analytics, rank tracking, alerts | | [**Site Migration**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/site-migration) | Domain moves, redirects, recovery | | [**IndexNow**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/indexnow) | Instant indexing for Bing/Yandex | | [**Debugging**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/debugging) | Common Vue SEO issues, hydration errors | h2. [Quick Reference: SEO Checklist](#quick-reference-seo-checklist) The [**Vue SEO Checklist**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/launch-and-listen/checklist) provides a structured audit path: - **Pre-launch** . SSR configured, meta tags set, URLs structured, crawlers controlled - **Post-launch** . Search Console verified, indexing confirmed, performance passing - **Ongoing** . Weekly GSC reviews, monthly monitoring, quarterly audits h2. [Vue vs Nuxt](#vue-vs-nuxt) This guide covers **plain Vue** applications using Vue Router and Unhead. If you're using **Nuxt**, much of this is handled automatically. Nuxt provides: - SSR by default - File-based routing with automatic meta tag handling - [**Nuxt SEO module**](https://nuxtseo.com/learn-seo/vue/docs/nuxt-seo/getting-started/introduction) for sitemaps, robots.txt, OG images - Built-in canonical URLs and sitemap generation [**See the Nuxt SEO guide →**](https://nuxtseo.com/learn-seo/vue/learn-seo/nuxt) h2. [Why Vue SPAs Fail SEO](#why-vue-spas-fail-seo) Vue SPAs render content client-side. The initial HTML looks like this: ``` <!DOCTYPE html> <html> <head> <!-- Empty - no meta tags --> </head> <body> <div id="app"></div> <script src="/app.js"></script> </body> </html> ``` Search engines need meta tags and content in the initial HTML. Google can execute JavaScript to render your page, but: - Rendering is [**queued separately**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) from crawling - Other crawlers (Bing, social platforms) don't render JavaScript - Social preview cards (Twitter, Facebook, Slack) read initial HTML only - Performance suffers. users wait for JavaScript before seeing content **Solutions:** - [**SSR frameworks**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/ssr-frameworks) render HTML on the server - [**Prerendering**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/spa/prerendering) generates HTML at build time - [**Dynamic rendering**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/spa/dynamic-rendering) serves pre-rendered HTML to bots (deprecated but functional) h2. [Common Mistakes](#common-mistakes) **Using vue-meta in Vue 3** . vue-meta never shipped Vue 3 support. Use [**Unhead**](https://unhead.unjs.io/) instead. [**Migration guide**](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta/migrating-vue-meta). **Skipping SSR verification** . View Page Source on your deployed site. If meta tags are missing, crawlers see nothing. **Relative OG image URLs** . Social platforms need absolute URLs (`https://site.com/og.png`), not relative paths (`/og.png`). **Template meta descriptions** . "Welcome to {page}" descriptions hurt click-through rates. Write unique descriptions per page. **Ignoring Search Console** . GSC shows exactly what Google sees. Check the Page Indexing report for excluded pages. h2. [External Resources](#external-resources) - [**Google JavaScript SEO Basics**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) - [**Unhead Documentation**](https://unhead.unjs.io/) - [**Vue Router History Mode**](https://router.vuejs.org/guide/essentials/history-mode.html) - [**Vue.js SSR Guide**](https://vuejs.org/guide/scaling-up/ssr.html) --- [**Mastering Meta** Set up meta tags in Vue 3 with Unhead. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org with SSR patterns.](https://nuxtseo.com/learn-seo/vue/learn-seo/vue/mastering-meta) **On this page** - [Start Here](#start-here) - [Content Sections](#content-sections) - [Quick Reference: SEO Checklist](#quick-reference-seo-checklist) - [Vue vs Nuxt](#vue-vs-nuxt) - [Why Vue SPAs Fail SEO](#why-vue-spas-fail-seo) - [Common Mistakes](#common-mistakes) - [External Resources](#external-resources) --- ### Nuxt SEO Guide · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt Description: Complete Nuxt SEO resource. Meta tags, crawl control, rendering modes, and performance monitoring. Powered by the Nuxt SEO ecosystem. h1. **Nuxt SEO Guide** Complete Nuxt SEO resource. Meta tags, crawl control, rendering modes, and performance monitoring. Powered by the Nuxt SEO ecosystem. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Jan 4, 2026** Nuxt handles SEO by default. Meta tags work out of the box, pages render on the server, and the [**Nuxt SEO module**](https://nuxtseo.com/learn-seo/nuxt/docs/nuxt-seo/getting-started/introduction) automates sitemaps, robots.txt, Open Graph images, and structured data with zero configuration. This guide covers everything you need for production-grade Nuxt SEO, from basic meta tags to hybrid rendering to search performance monitoring. h2. [All-in-One Solution](#all-in-one-solution) The Nuxt SEO ecosystem provides modules for every SEO requirement. Install the full package or pick individual modules: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/docs/nuxt-seo/getting-started/introduction) The meta-module includes sitemaps, robots.txt, OG images, schema.org, and 20+ utilities. Or install modules individually: [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/learn-seo/nuxt/docs/sitemap/getting-started/introduction) [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/learn-seo/nuxt/docs/robots/getting-started/introduction) [OG Image **v5.1.13** 2.9M 495 Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/docs/og-image/getting-started/introduction) [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/learn-seo/nuxt/docs/schema-org/getting-started/introduction) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/learn-seo/nuxt/docs/seo-utils/getting-started/introduction) [Site Config **v3.2.14** 8.9M 75 Powerful build and runtime shared site configuration for Nuxt modules.](https://nuxtseo.com/learn-seo/nuxt/docs/site-config/getting-started/introduction) h2. [Start Here](#start-here) **New to Nuxt SEO?** Start with [**Mastering Meta**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta). It covers the fundamentals: titles, descriptions, and social sharing tags with working code examples. **Need a quick audit?** Use the [**Nuxt SEO Checklist**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/checklist): a pre-launch, post-launch, and ongoing monitoring checklist with links to detailed guides for every item. **Already know the basics?** Jump to the section that matches your current need. h2. [Content Sections](#content-sections) h3. [[**Mastering Meta**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta)](#mastering-meta) Meta tags control how your pages appear in search results and social shares. Nuxt provides `useHead()`, `useSeoMeta()`, and `defineOgImage()` composables, all auto-imported and SSR-ready. | **Guide** | **What You'll Learn** | | --- | --- | | [**Titles**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta/titles) | Page titles, title templates, character limits | | [**Descriptions**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta/descriptions) | Meta descriptions, CTR optimization | | [**Open Graph**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta/open-graph) | Social preview images, OG tags | | [**Twitter Cards**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta/twitter-cards) | Twitter-specific meta tags | | [**Schema.org**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta/schema-org) | Structured data with `useSchemaOrg()` | | [**Slack**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta/slack) | Unfurl configuration for Slack | h3. [[**Controlling Crawlers**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/controlling-crawlers)](#controlling-crawlers) Manage how search engines discover and index your pages. Nuxt modules automate robots.txt, sitemaps, and canonical URLs, or configure manually for full control. | **Guide** | **What You'll Learn** | | --- | --- | | [**robots.txt**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/controlling-crawlers/robots-txt) | Module config, AI bot blocking | | [**Sitemaps**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/controlling-crawlers/sitemaps) | Automatic generation, dynamic routes | | [**Meta Tags**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/controlling-crawlers/meta-tags) | noindex, nofollow, per-page control | | [**Canonical URLs**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/controlling-crawlers/canonical-urls) | Duplicate content prevention | | [**Redirects**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/controlling-crawlers/redirects) | 301 vs 302, `routeRules` config | h3. [[**Routes & Rendering**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/routes-and-rendering)](#routes-rendering) File-based routing makes URLs clean by default. Hybrid rendering with `routeRules` lets you SSR, SSG, or SPA on a per-route basis. | **Guide** | **What You'll Learn** | | --- | --- | | [**Rendering Modes**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/routes-and-rendering/rendering) | SSR vs SSG vs SPA, `routeRules` | | [**Trailing Slashes**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/routes-and-rendering/trailing-slashes) | Configuration, redirect handling | h3. [[**Launch & Listen**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen)](#launch-listen) Deploy your site and monitor its search performance. Set up tracking, debug indexing issues, and handle site migrations. | **Guide** | **What You'll Learn** | | --- | --- | | [**Going Live**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/going-live) | First deployment, SSR verification | | [**Search Console**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/search-console) | Verification, reports, URL inspection | | [**Core Web Vitals**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/core-web-vitals) | LCP, INP, CLS optimization for Nuxt | | [**Indexing Issues**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/indexing-issues) | "Crawled not indexed" fixes | | [**SEO Monitoring**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/seo-monitoring) | Analytics, rank tracking, alerts | | [**Site Migration**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/site-migration) | Domain moves, redirects, recovery | | [**IndexNow**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/indexnow) | Instant indexing for Bing/Yandex | | [**Debugging**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/launch-and-listen/debugging) | Common Nuxt SEO issues, hydration errors | h2. [Quick Reference: SEO Checklist](#quick-reference-seo-checklist) The [**Nuxt SEO Checklist**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/checklist) provides a structured audit path: - **Pre-launch**: Meta tags set, URLs structured, crawlers configured, modules installed - **Post-launch**: Search Console verified, indexing confirmed, performance passing - **Ongoing**: Weekly GSC reviews, monthly monitoring, quarterly audits h2. [Why Nuxt Works for SEO](#why-nuxt-works-for-seo) Nuxt renders pages on the server by default. This means meta tags and content appear in the initial HTML that search engines and social platforms read: ``` <!DOCTYPE html> <html> <head> <title>Your Page Title
``` **Key advantages:** - **SSR by default**: No setup required, pages render server-side - **Auto-imported composables**: `useHead()`, `useSeoMeta()`, `useRoute()` work everywhere - **File-based routing**: Clean URLs without manual configuration - **Module ecosystem**: Install pre-built solutions for sitemaps, OG images, schema.org - **Hybrid rendering**: SSR, SSG, or SPA per route via `routeRules` h2. [Nuxt vs Plain Vue](#nuxt-vs-plain-vue) If you're building a Vue application without Nuxt, you'll need to manually configure SSR, install Unhead, set up Vue Router, and build your own sitemap/robots.txt solutions. Nuxt includes all of this out of the box. [**See the Vue SEO guide →**](https://nuxtseo.com/learn-seo/nuxt/learn-seo/vue) for plain Vue applications. h2. [Common Mistakes](#common-mistakes) **Skipping the Nuxt SEO module**: Installing individual tools when [**Nuxt SEO**](https://nuxtseo.com/learn-seo/nuxt/docs/nuxt-seo/getting-started/introduction) provides everything in one package. **Not verifying SSR**: View Page Source on your deployed site. Meta tags should appear in initial HTML, not injected by JavaScript. **Relative OG image URLs**: Social platforms need absolute URLs (`https://site.com/og.png`). Use `defineOgImage()` which handles this automatically. **Template meta descriptions**: "Welcome to {page}" descriptions hurt click-through rates. Write unique descriptions per page. **Ignoring Search Console**: GSC shows exactly what Google sees. Check the Page Indexing report for excluded pages. h2. [External Resources](#external-resources) - [**Nuxt SEO Module Documentation**](https://nuxtseo.com/learn-seo/nuxt/docs/nuxt-seo/getting-started/introduction) - [**Google JavaScript SEO Basics**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) - [**Nuxt Official Documentation**](https://nuxt.com/docs) - [**Unhead Documentation**](https://unhead.unjs.io/) --- [**Mastering Meta** Set up meta tags in Nuxt with useSeoMeta. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org. with SSR patterns that actually get indexed.](https://nuxtseo.com/learn-seo/nuxt/learn-seo/nuxt/mastering-meta) **On this page** - [All-in-One Solution](#all-in-one-solution) - [Start Here](#start-here) - [Content Sections](#content-sections) - [Quick Reference: SEO Checklist](#quick-reference-seo-checklist) - [Why Nuxt Works for SEO](#why-nuxt-works-for-seo) - [Nuxt vs Plain Vue](#nuxt-vs-plain-vue) - [Common Mistakes](#common-mistakes) - [External Resources](#external-resources) --- ### SERP Analyzer - See Who Ranks for Any Keyword · Nuxt SEO Pro Source: https://nuxtseo.com/tools/serp-analyzer Description: Analyze Google search results for any keyword. See top 10 rankings, domain authority, and SERP features like AI Overview and Featured Snippets. h1. **SERP Analyzer** See who ranks for any keyword and what SERP features are present. Try these h2. **Understanding SERP Features ** Modern search results are more than just 10 blue links. Google shows various SERP features that can either help or hurt your organic traffic. h3. **AI Overview** Google's AI-generated summary at the top of search results. Can reduce organic clicks by 40-60%. Focus on content AI can't easily summarize. h3. **Featured Snippet** A highlighted answer box extracted from a top-ranking page. Position zero visibility. Structure content with clear Q&A format to win these. h3. **People Also Ask** Expandable related questions from other searchers. Opportunity to rank for multiple related queries. Answer these in your content. h3. **Local Pack** Map with local business listings. Dominates local searches. Optimize Google Business Profile if relevant. h3. **Knowledge Panel** Information box about entities (people, places, things). Establishes authority. Use Schema.org markup to influence. h3. **Image Pack** Row of images in search results. Visual content opportunity. Optimize images with alt text and captions. h3. **Video Results** Video carousels or inline video results. YouTube dominates. Consider video content for these queries. h3. **Top Stories** News carousel for current events. Requires news site status or very timely content. h3. **Related Searches** Suggested queries at the bottom of results. Keyword research gold. Target these for content clusters. h3. **Shopping Results** Product ads and comparisons. Commercial intent. Organic ranking harder—consider Google Merchant Center. h2. **The AI Overview Challenge ** Google's AI Overview (SGE) can significantly reduce clicks to organic results. When present, consider these strategies: - **Create unique insights:** Original research, data, or perspectives AI can't summarize from existing content. - **Target long-tail queries:** Complex questions often have less AI coverage and more click-through. - **Be cited by AI:** Structure content clearly so AI references your site as a source. - **Focus on action keywords:** "How to [specific task]" often leads to clicks for detailed instructions. h2. **Using SERP Data Strategically ** 1. 1.**Assess the landscape** — Before creating content, check what's currently ranking. Match or exceed the content format that dominates. 2. 2.**Find the gaps** — Look for keywords where competition is low-to-medium authority. These are your opportunities. 3. 3.**Target SERP features** — If FAQs appear, add FAQ Schema. If videos rank, consider video content. Match the format Google prefers. 4. 4.**Monitor changes** — SERPs evolve. Re-analyze periodically to catch new opportunities or threats. h2. **Official References** [**Google Search Central**](https://developers.google.com/search) Official documentation on how Google Search works and ranking factors. [**SERP Features Guide**](https://moz.com/learn/seo/serp-features) Comprehensive guide to all SERP feature types and how to optimize for them. h2. **Related Resources** [**Keyword Research**](https://nuxtseo.com/tools/serp-analyzer/tools/keyword-research) [**Domain Rankings**](https://nuxtseo.com/tools/serp-analyzer/tools/domain-rankings) [**Schema.org Module**](https://nuxtseo.com/tools/serp-analyzer/docs/schema-org/getting-started/introduction) [**Structured Data Guide**](https://nuxtseo.com/tools/serp-analyzer/learn-seo/nuxt/controlling-crawlers/structured-data) --- ### Domain Rankings Checker - See Your Keyword Positions · Nuxt SEO Pro Source: https://nuxtseo.com/tools/domain-rankings Description: Check what keywords any domain ranks for. See positions, search volume, and estimated traffic. Find opportunities to improve your rankings. h1. **Domain Rankings** See what keywords any domain ranks for in Google. Try these h2. **Understanding Your Rankings ** Domain rankings show which keywords your site appears for in Google search results. This data helps you understand your SEO performance and find optimization opportunities. h3. **Position Ranges ** - 1-3Premium positions. High visibility and click-through rates. Protect these rankings. - 4-10Page 1. Good visibility. Optimize to push into top 3. - 11-20Striking distance. So close to page 1. Priority optimization targets. - 21+Lower visibility. May need content refresh or link building. h3. **Traffic Estimation ** Estimated monthly traffic is calculated from search volume and expected click-through rate for each position. Use this to prioritize which keywords to optimize first. - Position 1: ~30% CTR - Position 2: ~15% CTR - Position 3: ~10% CTR - Position 4-10: ~2-5% CTR - Position 11+: ~0.5% CTR h2. **What is Striking Distance? ** Keywords ranking 11-20 are in "striking distance"—just one page away from the coveted first page of Google. These represent your best optimization opportunities. h3. **Why Striking Distance Matters ** - **You're already ranking:** Google sees your content as relevant. You just need to prove you deserve page 1. - **Incremental effort:** Small improvements can yield big results. A few backlinks or content updates may be enough. - **10x traffic potential:** Moving from position 15 to position 5 can increase traffic by 10x or more. h2. **Improving Your Rankings ** 1. 1.**Analyze top performers** — Look at the pages driving the most traffic. What makes them successful? Apply those patterns elsewhere. 2. 2.**Target striking distance** — Focus on keywords ranking 11-20 first. Small optimizations here have the biggest ROI. 3. 3.**Update stale content** — Pages that have dropped in rankings may need fresh content, updated data, or better internal linking. 4. 4.**Build topical authority** — Create supporting content around your core topics. Internal links between related pages boost rankings. h2. **Using Rankings for Competitive Analysis ** You can also check competitor domains to understand their SEO strategy: h3. **Find Content Gaps ** Keywords competitors rank for that you don't. These are opportunities to create new content. h3. **Benchmark Performance ** Compare your keyword count and traffic estimates to competitors. Set realistic improvement goals. h3. **Identify Top Pages ** See which competitor pages drive the most traffic. Analyze their structure and content approach. h3. **Monitor Trends ** Track rankings over time to spot emerging competitors or declining content that needs attention. h2. **Official References** [**Google Search Console**](https://search.google.com/search-console) Official tool for monitoring your site's presence in Google Search results. [**Understanding Search Traffic**](https://developers.google.com/search/docs/monitor-debug/analytics-traffic) Google's guide to analyzing and understanding your search traffic. [**Ranking Factors Study**](https://backlinko.com/google-ranking-factors) Comprehensive analysis of factors that influence Google rankings. h2. **Related Resources** [**Keyword Research**](https://nuxtseo.com/tools/domain-rankings/tools/keyword-research) [**SERP Analyzer**](https://nuxtseo.com/tools/domain-rankings/tools/serp-analyzer) [**Sitemap Module**](https://nuxtseo.com/tools/domain-rankings/docs/sitemap/getting-started/introduction) [**Robots Module**](https://nuxtseo.com/tools/domain-rankings/docs/robots/getting-started/introduction) --- ### Pro · Nuxt SEO Pro Source: https://nuxtseo.com/pro Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. The futures SEO Modules** for todays **Nuxters**. ** People ask ChatGPT instead of Google now. If AI can't parse your content, you're invisible. These modules handle llms.txt, MCP, and version skew for your Nuxt site. [**Get Early Access - $119 **](https://nuxtseo.com/pro/pro#pricing) [** Try Free in Dev **](https://nuxtseo.com/pro/docs/nuxt-seo-pro/getting-started/introduction) [Skew Protection](https://nuxtseo.com/pro/docs/skew-protection/getting-started/introduction) [AI Ready](https://nuxtseo.com/pro/docs/ai-ready/getting-started/introduction) [AI Kit](https://nuxtseo.com/pro/docs/ai-kit/getting-started/introduction) h2. **Future-ready Nuxt modules ** Built for what's coming, works today. h2. **Drive organic traffic from AI ** ChatGPT, Claude, and Perplexity pull answers from sources they can parse. If your content isn't structured for AI, you're invisible to a growing audience. - **Auto-generated llms.txt ** Your entire site converted to AI-readable markdown, kept in sync automatically - **MCP for live queries ** AI agents fetch your latest content directly instead of relying on stale training data - **RAG-ready exports ** Token-optimized chunks ready for embedding and semantic search pipelines [**Try free in dev **](https://nuxtseo.com/pro/docs/ai-ready/getting-started/introduction) License required for production. h1. Nuxt SEO > Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h2. LLM Resources - [Pages Minimal](/llms.toon) - [Page Chunks](/llms-full.toon) - [MCP](/mcp) h2. Pages - [Nuxt SEO · All-in-one Technical SEO](/) - [Nuxt SEO Pro](/pro) pages[242]{route,title,description,headings,chunkIds}: /,Nuxt SEO · All-in-one Technical SEO,Nuxt SEO is a collection...,"h1:Fully equipped Technical SEO...|h2:Put web crawlers to work.",8a5edab2-0... /pro,Pro · Nuxt SEO,Nuxt SEO is a collection...,"h1:The futures SEO Modules...|h2:Three modules.",f6001279-0... /chat,Chat · Nuxt SEO,Nuxt SEO is a collection...,"h1:AI Chat|h3:NuxtSEO",6ea4b827-0 /releases,Releases · Nuxt SEO,Nuxt SEO is a collection...,"h1:Nuxt SEO Releases",95799bc5-0 ``` { "8a5edab2-0": "Fully equipped Technical SEO for busy Nuxters. Nuxt SEO is a collection of modules that handle all of the technical aspects in growing your sites organic traffic.", "8a5edab2-1": "1.Put web crawlers to work. Providing a robots.txt and sitemap.xml gives web crawlers data on how to index your site.", "8a5edab2-2": "Robots v5.6.1 7.5M 499 Tame the robots crawling and indexing your site with ease. Zero config dynamic /robots.txt. Page level robots control. Avoid non-production sites getting indexed" } ``` h2. **AI chat for your site, without the lock-in ** Users expect to ask questions, not dig through docs. Add conversational search that runs on your infrastructure, with the LLM of your choice. - **Own your data ** Embeddings stay on your vector DB. Switch providers without re-indexing. - **Any LLM, any vector store ** OpenAI, Anthropic, Ollama. libSQL, pgvector, Upstash, Cloudflare Vectorize. - **Nuxt UI components included ** Chat interface that matches your design system, ready to customize. [**Try free in dev **](https://nuxtseo.com/pro/docs/ai-kit/getting-started/introduction)**Early Access ** AI RAG How do I add schema.org to my Nuxt blog? Use the `useSchemaOrg()` composable in your blog page: ``` useSchemaOrg([ defineArticle({ headline: post.title, datePublished: post.date, author: { name: 'Your Name' } }) ]) ``` This automatically adds structured data for Google rich results. h2. **Never break a session ** Every deployment breaks 2-5% of active sessions. Users hit ChunkLoadError, see blank screens, and leave. This catches it before they do. - **Catches stale chunks before they break ** Detects client/server mismatch and prompts users to refresh gracefully - **Persistent assets across deploys ** Old chunks stay accessible so crawlers and slow sessions don't 404 - **Zero config ** Works out of the box on Vercel, Netlify, Cloudflare [**Try free in dev **](https://nuxtseo.com/pro/docs/skew-protection/getting-started/introduction) License required for production. Update Notification h2. **Deploys where you deploy ** Zero platform-specific config. Works with your existing Nuxt setup. **Vercel** **Netlify** **Cloudflare** **Self-hosted** Claude Code $claude "find low competition keywords for my nuxt tutorial" ●nuxt-seo-pro ⚡research_keywords→ 47 suggestions ⚡analyze_serp→ top 10 competitors ✓Found 12 keywords under difficulty 40 nuxt ssr tutorialvol: 320 · kd: 24 nuxt server componentsvol: 180 · kd: 31 nuxt data fetchingvol: 210 · kd: 28 h2. **SEO research in your IDE ** Connect Claude Code, Cursor, or Windsurf to research keywords, analyze competitors, and generate content briefs without leaving your editor. **Keyword research ** Find long-tail keywords with volume, difficulty, and search intent data **SERP analysis ** See who ranks, their domain authority, and what SERP features appear **Content generation ** Generate briefs and articles with built-in writing rules [**Get Started **](https://nuxtseo.com/pro/docs/nuxt-seo-pro/mcp/installation)**Early Access ** h2. **Trusted by Nuxt developers ** The free modules have 3M+ downloads. Here's what developers say. Coming Q2 2025 h2. **Skip the SEO setup entirely ** Production-ready templates with structured data, sitemaps, and meta tags already configured. Included with your license. **Coming soon** **Documentation ** Sidebar, search, versioning **Coming soon** **Blog ** Articles, tags, RSS **Coming soon** **SaaS Landing ** Hero, pricing, features **Coming soon** **E-commerce ** Products, cart, checkout h2. **One payment. Everything included. ** No subscriptions, no per-seat pricing. Lifetime access and updates. **Early Access ** h3. **Nuxt SEO Pro ** **$119**~~$249~~+ tax - All three modules, one license - Lifetime updates, no recurring fees - Private GitHub repo access - 30-day refund, no questions asked [**Get Nuxt SEO Pro **](https://buy.stripe.com/aFaaEXfcU6Os4gjfsJcs801) h3. **Not sure yet? ** Sign in with GitHub to get notified before the price goes up. [**Join Waitlist with GitHub **](https://nuxtseo.com/pro/auth/github) **+251**![](https://pbs.twimg.com/profile_images/1832800489580224512/uqwwtRlK_400x400.jpg)![](https://pbs.twimg.com/profile_images/1374040164180299791/ACw4G3nZ_400x400.jpg)![](https://cdn.discordapp.com/avatars/212548363529355264/a23dd75d7ffadac117115cb745edb25a.webp?size=240) 254+ developers on the waitlist ![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4) > "By purchasing Nuxt SEO Pro you're supporting all of the open-source work I do on Nuxt and within the Nuxt ecosystem. Thank you so much!" **Harlan Wilton ** Nuxt Core Team h2. **Everything you need, one license ** Try free in dev. Purchase when you're ready for production. Early access pricing won't last. [**Get Early Access — $119 **](https://buy.stripe.com/aFaaEXfcU6Os4gjfsJcs801) [**Read the docs **](https://nuxtseo.com/pro/docs/nuxt-seo-pro/getting-started/introduction) --- ### Nuxt SEO · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction Description: Everything you need for technical SEO in Nuxt. One install, zero config, all the boring stuff handled. **Getting Started** h1. **Nuxt SEO** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs Nuxt SEO gives you sitemaps, robots.txt, meta tags, Schema.org, and OG images in one install. Zero config, sensible defaults. ``` npx nuxi module add @nuxtjs/seo ``` New to SEO? See the [**Nuxt SEO Guide**](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/learn-seo/nuxt) first. h2. [Modules Included](#modules-included) [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/robots/getting-started/introduction) [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/sitemap/getting-started/introduction) [OG Image **v5.1.13** 2.9M 495 Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/og-image/getting-started/introduction) [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/schema-org/getting-started/introduction) [Link Checker **v4.3.9** 2.3M 95 Find and magically fix links that may be negatively effecting your SEO.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/link-checker/getting-started/introduction) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/seo-utils/getting-started/introduction) Install all at once with `@nuxtjs/seo`, or pick individual modules. h2. [Features](#features) - **Zero config** — sensible defaults out of the box - **Modules work together** — Site Config shares URL, name, and metadata across all modules - **DevTools integration** — debug and preview SEO output - **I18n ready** — full multilingual support with Nuxt I18n - **Edge compatible** — Vercel, Netlify, Cloudflare, and more h2. [Site Config](#site-config) All modules need your site URL for canonical links and sitemaps. Set it once: ``` export default defineNuxtConfig({ site: { url: 'https://example.com', name: 'My Site' } }) ``` Every module uses these values. No duplication. [Site Config **v3.2.14** 8.9M 75 Powerful build and runtime shared site configuration for Nuxt modules.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/site-config/getting-started/introduction) [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/1.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/nuxt-seo/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt SEO by installing the dependency to your project.](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction/docs/nuxt-seo/getting-started/installation) **On this page** - [Modules Included](#modules-included) - [Features](#features) - [Site Config](#site-config) --- ### Install Nuxt SEO · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/getting-started/installation Description: Get started with Nuxt SEO by installing the dependency to your project. **Getting Started** h1. **Install Nuxt SEO** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs `npx nuxt module add @nuxtjs/seo` `npm i @nuxtjs/seo` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/seo', ], }) ``` `yarn add @nuxtjs/seo` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/seo', ], }) ``` `pnpm i @nuxtjs/seo` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/seo', ], }) ``` `bun i @nuxtjs/seo` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/seo', ], }) ``` h3. [Install Modules Separately](#install-modules-separately) Install individual modules instead: [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/robots/getting-started/introduction) [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/sitemap/getting-started/introduction) [OG Image **v5.1.13** 2.9M 495 Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/og-image/getting-started/introduction) [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/schema-org/getting-started/introduction) [Link Checker **v4.3.9** 2.3M 95 Find and magically fix links that may be negatively effecting your SEO.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/link-checker/getting-started/introduction) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/seo-utils/getting-started/introduction) h2. [Verify Installation](#verify-installation) Check Nuxt DevTools for the SEO tab, or visit `/sitemap.xml` and `/robots.txt` in your browser. h2. [Next Steps](#next-steps) See [**Using the Modules**](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/nuxt-seo/guides/using-the-modules) to configure each module. h2. [Playgrounds](#playgrounds) Try Nuxt SEO without installing: - [**Basic**](https://stackblitz.com/edit/nuxt-starter-zycxux?file=nuxt.config.ts) - [**i18n**](https://stackblitz.com/edit/nuxt-starter-pnej8lvb?file=nuxt.config.ts) [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/1.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/nuxt-seo/getting-started/installation.md) **Did this page help you? ** [**Introduction** Everything you need for technical SEO in Nuxt. One install, zero config, all the boring stuff handled.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/nuxt-seo/getting-started/introduction) [**Troubleshooting** Common issues and fixes for Nuxt SEO modules.](https://nuxtseo.com/docs/nuxt-seo/getting-started/installation/docs/nuxt-seo/getting-started/troubleshooting) **On this page** - [Install Modules Separately](#install-modules-separately) - [Verify Installation](#verify-installation) - [Next Steps](#next-steps) - [Playgrounds](#playgrounds) --- ### Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/getting-started/introduction Description: The quickest and easiest way to build Schema.org graphs for Nuxt. **Getting Started** h1. **Nuxt Schema.org** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#98) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-schema-org/pull/98). [Copy for LLMs Nuxt Schema.org automatically generates JSON-LD structured data for your pages, enabling [**rich snippets**](https://developers.google.com/search/docs/appearance/structured-data/search-gallery) in Google search results like star ratings, FAQ accordions, and recipe cards. Rich snippets can increase click-through rates by up to 30% and provide users with more information before they visit your site. New to Schema.org? Check out the [**Schema.org**](https://nuxtseo.com/docs/schema-org/getting-started/introduction/learn/mastering-meta/schema-org) guide to learn more. h2. [Features](#features) - 😎 Simple API based on Google and Yoast best practices - 🧙 30+ Nodes with automated relations, date, URL resolving and more for best practice Schema.org - 💡 Simple global meta provides for minimal boilerplate - 🌳 Minimal code, optimised for tree-shaking and SSR - Nuxt Dev Tools integration [](https://nuxtseo.com/docs/schema-org/getting-started/introduction/tools/schema-validator)**Validate your structured data** - Use our free [**Schema.org Validator**](https://nuxtseo.com/docs/schema-org/getting-started/introduction/tools/schema-validator) to test JSON-LD markup and find errors. [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/0.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/getting-started/introduction/docs/schema-org/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt Schema.org by installing the dependency to your project.](https://nuxtseo.com/docs/schema-org/getting-started/introduction/docs/schema-org/getting-started/installation) --- ### Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/getting-started/introduction Description: Find and magically fix links that may be negatively affecting your Nuxt sites SEO. **Getting Started** h1. **Nuxt Link Checker** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#69) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-link-checker/pull/69). [Copy for LLMs h2. [Why use Nuxt Link Checker?](#why-use-nuxt-link-checker) Nuxt Link Checker scans your site for broken links, SEO issues, and accessibility problems during development and build time. It runs 13 inspections covering errors like 404s, missing anchors, and URL best practice violations. By keeping your links in check, you can ensure that your site is discoverable and accessible to [**search engine crawlers**](https://nuxtseo.com/docs/link-checker/getting-started/introduction/learn-seo/nuxt/controlling-crawlers) and your users. The module does more than just check for valid links. It guides you in setting up links correctly, avoiding common URL pitfalls: whitespaces, non-ascii characters, uppercase letters, non-root relative URLs, and more. While it's simple to manually check your links, it can be time-consuming and error-prone to do so consistently, especially as your site grows. Nuxt Link Checker automatically scans your links during development and build time, catching issues before they reach production. Ready to get started? Check out the [**installation guide**](https://nuxtseo.com/docs/link-checker/getting-started/introduction/docs/link-checker/getting-started/installation). h2. [Features](#features) - ✅ 13 SEO focused link inspections - ✨ See live inspections right in your Nuxt App - 🧙 Magically fix them in Nuxt Dev Tools - 🚩 Generate reports on build (html, markdown) [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/0.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/getting-started/introduction/docs/link-checker/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt Link Checker by installing the dependency to your project.](https://nuxtseo.com/docs/link-checker/getting-started/introduction/docs/link-checker/getting-started/installation) **On this page** - [Why use Nuxt Link Checker?](#why-use-nuxt-link-checker) - [Features](#features) --- ### Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/getting-started/introduction Description: Nuxt Robots manages the robots crawling your site with minimal config and best practice defaults. **Getting Started** h1. **Nuxt Robots** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#257) Co-authored-by: Claude Opus 4.5 ](https://github.com/nuxt-modules/robots/pull/257). [Copy for LLMs h2. [Why use Nuxt Robots?](#why-use-nuxt-robots) Control how search engines and AI crawlers interact with your Nuxt site using robots.txt, meta tags, and X-Robots-Tag headers. Nuxt Robots provides minimal config with best practice defaults. The core feature of the module is: - Telling [**crawlers**](https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers) which paths they can and cannot access using a [**robots.txt**](https://developers.google.com/search/docs/crawling-indexing/robots/intro) file. - Telling [**search engine crawlers**](https://developers.google.com/search/docs/crawling-indexing/googlebot) what they can show in search results from your site using a `` `X-Robots-Tag` HTTP header. New to robots or SEO? Check out the [**Controlling Web Crawlers**](https://nuxtseo.com/docs/robots/getting-started/introduction/learn/controlling-crawlers) guide to learn more about why you might need these features. [Conquering Web Crawlers 10 min read Being able to tell crawlers what to do can help with your SEO strategy, learn how to do it in Vue and Nuxt.](https://nuxtseo.com/docs/robots/getting-started/introduction/learn/controlling-crawlers) While it's simple to create your own robots.txt file, the module makes sure your non-production environments get disabled from indexing. This is important to avoid duplicate content issues and to avoid search engines serving your development or staging content to users. The module also acts as an integration point for other modules. For example: - [**Nuxt Sitemap**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/sitemap/getting-started/introduction) ensures pages you've marked as disallowed from indexing are excluded from the sitemap. - [**Nuxt Schema.org**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/schema-org/getting-started/introduction) skips rendering Schema.org data if the page is marked as excluded from indexing. Ready to get started? Check out the [**installation guide**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/getting-started/installation). h2. [Features](#features) Nuxt Robots manages the robots crawling your site with minimal config and best practice defaults. h3. [🤖 Robots.txt Config](#robotstxt-config) Configuring the rules is as simple as adding a production robots.txt file to your project. - [**Config using Robots.txt**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/guides/robots-txt) h3. [🗿 X-Robots-Tag Header, Meta Tag](#x-robots-tag-header-meta-tag) Ensures pages that should not be indexed are not indexed with the following: - `X-Robots-Tag` header - `` meta tag Both enabled by default. - [**How it works**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/guides/how-it-works) h3. [🕵️ Bot Detection](#️-bot-detection) Detect and classify bots with server-side header analysis and optional client-side browser fingerprinting. Identify search engines, social media crawlers, AI bots, automation tools, and security scanners to optimize your application for both human users and automated agents. - [**Bot Detection Guide**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/guides/bot-detection) h3. [🔒 Production only indexing](#production-only-indexing) The module uses [**Nuxt Site Config**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/site-config/getting-started/background) to determine if the site is in production mode. It will disable non-production environments from being indexed, avoiding duplicate content issues. - [**Environment Config**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/guides/disable-indexing) h3. [🔄 Easy and powerful configuration](#easy-and-powerful-configuration) Use route rules to easily target subsets of your site. When you need even more control, use the runtime Nitro hooks to dynamically configure your robots rules. - [**Route Rules**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/guides/route-rules) - [**Nitro Hooks**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/nitro-api/nitro-hooks) h3. [🌎 I18n Support](#i18n-support) Will automatically fix any non-localised paths within your `allow` and `disallow` rules. - [**I18n Integration**](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/advanced/i18n) [](https://nuxtseo.com/docs/robots/getting-started/introduction/tools/robots-txt-generator)**Test your robots.txt** - Use our free [**Robots.txt Generator & Tester**](https://nuxtseo.com/docs/robots/getting-started/introduction/tools/robots-txt-generator) to validate rules and test against 50+ user agents. [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/1.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt Robots by installing the dependency to your project.](https://nuxtseo.com/docs/robots/getting-started/introduction/docs/robots/getting-started/installation) **On this page** - [Why use Nuxt Robots?](#why-use-nuxt-robots) - [Features](#features) --- ### Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/getting-started/introduction Description: Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt. **Getting Started** h1. **Nuxt Sitemap** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#540) Co-authored-by: Claude Opus 4.5 ](https://github.com/nuxt-modules/sitemap/pull/540). [Copy for LLMs h2. [Why use Nuxt Sitemap?](#why-use-nuxt-sitemap) Nuxt Sitemap automatically generates XML sitemaps with zero configuration, including automatic lastmod dates, image discovery, and i18n support. The module outputs a [**sitemap.xml**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview) file that search engines use to understand your site structure and index it more effectively. While it's not required to have a sitemap, it can be a powerful tool in getting your content indexed more frequently and more accurately, especially for larger sites or sites with complex structures. While it's simple to create your own sitemap.xml file, it can be time-consuming to keep it up-to-date with your site's content and easy to miss best practices. Nuxt Sitemap automatically generates the sitemap for you based on your site's content, including lastmod, image discovery and more. Ready to get started? Check out the [**installation guide**](https://nuxtseo.com/docs/sitemap/getting-started/introduction/docs/sitemap/getting-started/installation) or learn more on the [**Controlling Web Crawlers**](https://nuxtseo.com/docs/sitemap/getting-started/introduction/learn/controlling-crawlers) guide. h2. [Features](#features) - 🌴 Single /sitemap.xml or multiple /posts-sitemap.xml, /pages-sitemap.xml - 📊 Fetch your sitemap URLs from anywhere - 😌 Automatic lastmod, image discovery and best practice sitemaps - 🔄 SWR caching, route rules support - 🎨 Debug using the Nuxt DevTools integration or the XML Stylesheet - 🤝 Integrates seamlessly with Nuxt I18n and Nuxt Content [](https://nuxtseo.com/docs/sitemap/getting-started/introduction/tools/xml-sitemap-validator)**Validate your sitemap** - Use our free [**XML Sitemap Validator**](https://nuxtseo.com/docs/sitemap/getting-started/introduction/tools/xml-sitemap-validator) to check structure and ensure Google compliance. [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/0.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/getting-started/introduction/docs/sitemap/getting-started/introduction.md) **Did this page help you? ** h3. **Related ** [**Nuxt Robots**](https://nuxtseo.com/docs/sitemap/getting-started/introduction/docs/robots/getting-started/installation) [**Nuxt Site Config**](https://nuxtseo.com/docs/sitemap/getting-started/introduction/docs/site-config/getting-started/installation) [**Controlling Web Crawlers**](https://nuxtseo.com/docs/sitemap/getting-started/introduction/learn/controlling-crawlers) [**Installation** Get started with Nuxt Sitemap by installing the dependency to your project.](https://nuxtseo.com/docs/sitemap/getting-started/introduction/docs/sitemap/getting-started/installation) **On this page** - [Why use Nuxt Sitemap?](#why-use-nuxt-sitemap) - [Features](#features) --- ### Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/getting-started/introduction Description: Generate OG Images with Vue templates in Nuxt. **Getting Started** h1. **Nuxt OG Image** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#406) Co-authored-by: Claude Opus 4.5 ](https://github.com/nuxt-modules/og-image/pull/406). [Copy for LLMs h2. [Why use Nuxt OG Image?](#why-use-nuxt-og-image) Nuxt OG Image generates social media preview images (og:image) using Vue templates. Images are rendered at build time or on-demand using Satori or Chromium. When you share a link of your site on social media or some chat platforms, the link will be [**unfurled**](https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254), displaying a title, description, and an image. All of these are powered by the [**Open Graph Protocol**](https://ogp.me/). New to Open Graph? Check out the [**Mastering Open Graph Tags**](https://nuxtseo.com/docs/og-image/getting-started/introduction/learn/mastering-meta/open-graph) guide to learn more. For example, the OG image for the current page is: ![](https://nuxtseo.com/__og-image__/static/docs/og-image/getting-started/introduction/og.png) While it may not help with your organic traffic, it can significantly improve the click-through rate of your pages when shared. While it's simple to create your own OG images, it can be time-consuming to keep them up-to-date with your site's content and easy to misconfigure the meta tags for each platform. Ready to get started? Check out the [**installation guide**](https://nuxtseo.com/docs/og-image/getting-started/introduction/docs/og-image/getting-started/installation). h2. [Features](#features) - ✨ Create an og:image using the built-in templates or make your own with Vue components - 🎨 Design and test your og:image in the Nuxt DevTools OG Image Playground with full HMR - ▲ Render using Satori: Tailwind / UnoCSS with your theme, Google fonts, 6 emoji families supported and more! - 🤖 Or prerender using the Browser: Supporting painless, complex templates - 📸 Feeling lazy? Just generate screenshots for every page: hide elements, wait for animations, and more - ⚙️ Works on the edge: Vercel Edge, Netlify Edge and Cloudflare Workers [](https://nuxtseo.com/docs/og-image/getting-started/introduction/tools/social-share-debugger)**Preview your OG images** - Use our free [**Social Share Debugger**](https://nuxtseo.com/docs/og-image/getting-started/introduction/tools/social-share-debugger) to see how your links appear on Twitter, Facebook, LinkedIn, and more. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/0.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/getting-started/introduction/docs/og-image/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt OG Image by installing the dependency to your project.](https://nuxtseo.com/docs/og-image/getting-started/introduction/docs/og-image/getting-started/installation) **On this page** - [Why use Nuxt OG Image?](#why-use-nuxt-og-image) - [Features](#features) --- ### Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/getting-started/introduction Description: SEO utilities to improve your Nuxt sites discoverability and shareability. **Getting Started** h1. **Nuxt SEO Utils** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Why use Nuxt SEO Utils?](#why-use-nuxt-seo-utils) Nuxt SEO Utils is a collection of defaults and utilities to improve your Nuxt site's discoverability and shareability. While there are several features covering many aspects of SEO, it covers important defaults such as [**automatic canonical URLs**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/learn-seo/nuxt/controlling-crawlers/canonical-urls) and [**open graph tags**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/learn-seo/nuxt/mastering-meta/open-graph). h2. [Features](#features) h3. [Default Canonical URLs](#default-canonical-urls) Automatically generate canonical URLs for all pages. - Whitelisted query params with `canonicalQueryParams` - Lowercase URLs with respected trailing slash config h3. [Metadata Files](#metadata-files) Inspired by [**Next.js Metadata Files**](https://nextjs.org/docs/app/api-reference/file-conventions/metadata), this allows you to configure your head tags using metadata files. - [**Icons**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/guides/app-icons) and [**Open Graph Images**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/guides/open-graph-images) h3. [Breadcrumb composable](#breadcrumb-composable) - Easily generate site-wide breadcrumb using the [`useBreadcrumbItems()`](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/api/breadcrumbs) composable (test). - Integrates with Nuxt I18n and Nuxt Schema.org - Plugs directly into [**Nuxt UI Breadcrumb**](https://ui.nuxt.com/navigation/breadcrumb) h3. [✨ SEO meta in nuxt.config and route rules](#seo-meta-in-nuxtconfig-and-route-rules) Enjoy the DX of `useSeoMeta` in your nuxt.config and route rules - [**Nuxt Config SEO Meta**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/guides/nuxt-config-seo-meta) - [**Route Rules SEO Meta**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/guides/route-rules) h3. [🤖 Automatic OG Meta Tags](#automatic-og-meta-tags) Never worry about setting `og:title` and `og:description` again. This uses the [**Infer SEO Meta**](https://unhead.unjs.io/plugins/plugins/infer-seo-meta-tags) Unhead plugin. h3. [🧙 Validate and fix broken tags](#validate-and-fix-broken-tags) Automatically fix broken tags, for example will ensure `og:image` is an absolute URL. h3. [⚡ Extra head optimizations](#extra-head-optimizations) Reduce your page weight by treeshaking `useSeoMeta` and implementing other optimizations. - [**Treeshake Plugin**](https://unhead.unjs.io/plugins/plugins/vite-plugin) - [**Capo.js**](https://unhead.unjs.io/plugins/plugins/capo) h3. [Best Practice Default Meta](#best-practice-default-meta) - Canonical URLs will be automatically generated for all pages. - Description and open-graph meta tags will be set for you. See [**Default Meta**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/guides/default-meta) for more information. h3. [Enhanced Titles](#enhanced-titles) - Ensures that every page has a title by generating one from the last slug segment. See the [**Fallback Title**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/guides/fallback-title) guide for more information. - Sets a default title template for you with your [**site name**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/site-config/guides/setting-site-config). [](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/tools/meta-tag-checker)**Check your meta tags** - Use our free [**Meta Tag Checker**](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/tools/meta-tag-checker) to validate title and description length and preview SERP appearance. [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/0.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt SEO Utils by installing the dependency to your project.](https://nuxtseo.com/docs/seo-utils/getting-started/introduction/docs/seo-utils/getting-started/installation) **On this page** - [Why use Nuxt SEO Utils?](#why-use-nuxt-seo-utils) - [Features](#features) --- ### Introduction · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/getting-started/introduction Description: Eliminate version skew issues in your Nuxt application with intelligent update notifications and long-lived build assets. **Getting Started** h1. **Introduction** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/b0a8d3323dc5a3cc926dab76e878dfa4aa8ab630). [Copy for LLMs h2. [Why Nuxt Skew Protection?](#why-nuxt-skew-protection) **Version skew** is a mismatch between your deployed build and the chunks running in user browsers and crawler sessions. It can lead to several issues: - 🕷️ **Crawlers 404 on stale chunks** - Googlebot requests `_nuxt/builds/abc123.js` which no longer exists post-deploy, logging 500s and potentially impacting indexing - 💥 **ChunkLoadError in production** - Users mid-session get `Failed to fetch dynamically imported module` when navigating to routes with invalidated chunks - 🔄 **Delayed rollout** - Your latest release sits unloaded until users hard refresh, sometimes hours or days later Nuxt's built-in behavior (hard-reload when a new deployment is detected) helps, but in many cases it's [**not enough**](https://github.com/nuxt/nuxt/issues/29624). Nuxt Skew Protection does this with proactive update prompts and persistent build assets across deploys. Ready to get started? Check out the [**installation guide**](https://nuxtseo.com/docs/skew-protection/getting-started/introduction/docs/skew-protection/getting-started/installation). **License:** Nuxt Skew Protection is a [**Nuxt SEO Pro**](https://nuxtseo.com/pro) module and requires a license. h3. [Nuxt Chunk Loading Errors](#nuxt-chunk-loading-errors) If you've come across any of the following errors in Google Search Console, Sentry, or the browser console, they may be related to chunk loading failures, which the module prevents: > "Cannot read properties of undefined (reading 'default')" > "Couldn't resolve component 'default' at /" > "undefined is not an object (evaluating 'r.default')" > "Failed to fetch dynamically imported module" > "Importing a module script failed" h2. [Features](#features) h3. [🕷️ Persistent Build Assets](#️-persistent-build-assets) Previous build artifacts remain accessible across deploys, avoiding broken requests from: - Search engine crawlers hitting stale chunk URLs - Users on old sessions navigating your app - Progressive web apps with cached routes Assets are stored with smart deduplication to minimize storage overhead. h3. [⚡ Instant Update Prompts](#instant-update-prompts) Zero-config real-time notifications when a new version is deployed. Multiple transport strategies available: - **Polling** - Works everywhere, uses Nuxt's built-in `app:manifest:update` hook - **SSE** - Server-Sent Events for Node.js/Bun/Deno runtimes - **WebSocket** - Real-time updates via Cloudflare Durable Objects - **Adapters** - Third-party providers for any platform (see below) h3. [🎯 Chunk-Aware Targeting](#chunk-aware-targeting) Not every deploy needs to interrupt users. Notifications fire only when the user's **currently loaded chunks** are invalidated by the new build. A service worker tracks loaded JavaScript modules and detects when they're deleted in subsequent deployments - no false positives for unrelated updates. h3. [🎨 Headless UI](#headless-ui) Drop-in `` component with first-class Nuxt UI support: ``` ``` h3. [📊 Live Connection Monitoring](#live-connection-monitoring) Track active connections and version distribution across your users in real-time: - Monitor total connected users - See which build versions are running - Track rollout progress as users adopt new deployments Perfect for admin dashboards and deployment monitoring. See [**View Active Connections**](https://nuxtseo.com/docs/skew-protection/getting-started/introduction/docs/skew-protection/guides/live-connections) for setup. h3. [🔌 Third-Party Adapters](#third-party-adapters) Get real-time update notifications on **any platform** including static sites using external WebSocket providers: - [**Pusher**](https://pusher.com) - Hosted WebSocket service with generous free tier - [**Ably**](https://ably.com) - Realtime messaging platform See [**External Providers**](https://nuxtseo.com/docs/skew-protection/getting-started/introduction/docs/skew-protection/providers/external) for setup guides. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/1.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/getting-started/introduction/docs/skew-protection/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt Skew Protection by installing the dependency to your project.](https://nuxtseo.com/docs/skew-protection/getting-started/introduction/docs/skew-protection/getting-started/installation) **On this page** - [Why Nuxt Skew Protection?](#why-nuxt-skew-protection) - [Features](#features) --- ### Nuxt SEO Meta Tags Guide · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/mastering-meta Description: Set up meta tags in Nuxt with useSeoMeta. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org. with SSR patterns that actually get indexed. h1. **Nuxt SEO Meta Tags Guide** Set up meta tags in Nuxt with useSeoMeta. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org. with SSR patterns that actually get indexed. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Jan 4, 2026** Nuxt renders pages on the server by default, so meta tags work out of the box. This guide covers SSR-compatible patterns using Nuxt's built-in [**Unhead**](https://unhead.unjs.io/) integration. ``` My Page · My Site ``` In Nuxt, you manage these with `useSeoMeta()` or `useHead()`: ``` useSeoMeta({ title: 'My Page', description: 'What this page is about', ogImage: 'https://mysite.com/og.png' }) ``` h2. [What Meta Tags Control](#what-meta-tags-control) | **Meta Tag** | **Used By** | **Purpose** | | --- | --- | --- | | [``](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/titles) | Search engines, browsers | Page title in search results and browser tab | | [`<meta name="description">`](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/descriptions) | Search engines | Snippet text in search results (Google rewrites ~70%) | | [`<meta property="og:*">`](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/open-graph) | Facebook, LinkedIn, Discord, Slack | Link preview cards | | [`<meta name="twitter:*">`](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/twitter-cards) | Twitter/X | Tweet cards (falls back to OG tags) | | [`<script type="application/ld+json">`](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/schema-org) | Search engines | Rich results (stars, FAQs, recipes) | h3. [Search Engine Tags](#search-engine-tags) These affect how your pages appear in Google, Bing, and other search results. - [**Page Titles**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/titles) - Your main call-to-action in search results. Keep under 60 characters. - [**Meta Descriptions**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/descriptions) - The snippet below your title. Google rewrites most of them, but write them anyway for the 30% that stick. h3. [Social Sharing Tags](#social-sharing-tags) These control link previews when someone shares your URL. - [**Open Graph**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/open-graph) - The standard for Facebook, LinkedIn, Discord, and most platforms. Set `og:title`, `og:description`, `og:image`. - [**Twitter Cards**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/twitter-cards) - Twitter-specific tags. Falls back to Open Graph if not set, so often you don't need them. - [**Slack Unfurls**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/slack) - Slack uses Open Graph. Same tags, different quirks. h3. [Structured Data](#structured-data) - [**Schema.org**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/schema-org) - JSON-LD markup for rich results. Overkill for most sites, but useful for recipes, products, FAQs. h2. [Quick Setup](#quick-setup) Most sites need just this: ``` // app.vue or layouts/default.vue useHead({ titleTemplate: '%s · My Site', }) useSeoMeta({ ogSiteName: 'My Site', twitterCard: 'summary_large_image' }) ``` Then per-page: ``` <script setup lang="ts"> useSeoMeta({ title: 'About Us', description: 'We build things.', ogImage: 'https://mysite.com/about-og.png' }) </script> ``` That's it. Twitter falls back to OG tags. Slack uses OG tags. LinkedIn uses OG tags. Don't duplicate unless you need platform-specific content. h2. [Priority Order](#priority-order) When tags conflict, here's what wins: 1. **Page-level** `useSeoMeta()` in your page component 2. **Layout-level** `useSeoMeta()` in your layout 3. **App-level** setup in app.vue Nuxt merges these automatically. Page tags override layout tags. h2. [Common Mistakes](#common-mistakes) **Forgetting absolute URLs for images** ``` // ❌ Won't work - relative path ogImage: '/og.png' // ✅ Works - absolute URL ogImage: 'https://mysite.com/og.png' ``` **Setting the same description everywhere** Each page needs a unique description. Template descriptions like "Welcome to {page}" are lazy and hurt click-through rates. **Ignoring the preview** Test your meta tags before deploying: - [**Google Rich Results Test**](https://search.google.com/test/rich-results) - Structured data validation - [**Facebook Sharing Debugger**](https://developers.facebook.com/tools/debug/) - OG tags for most platforms - X/Twitter - Compose a tweet (don't post) to preview cards [**Start with Page Titles**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/titles) --- [**Nuxt SEO** Complete Nuxt SEO resource. Meta tags, crawl control, rendering modes, and performance monitoring. Powered by the Nuxt SEO ecosystem.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt) [**Titles** Set dynamic page titles in Nuxt with useHead. Learn title templates, reactive titles, and SSR patterns that Google indexes correctly.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/learn-seo/nuxt/mastering-meta/titles) **On this page** - [What Meta Tags Control](#what-meta-tags-control) - [Quick Setup](#quick-setup) - [Priority Order](#priority-order) - [Common Mistakes](#common-mistakes) --- ### Introduction · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/getting-started/introduction Description: Make your Nuxt site discoverable by AI agents through llms.txt, MCP, and markdown APIs. **Getting Started** h1. **Introduction** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: sync](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/71282c76b050b356ed447c7e42176e5780eac034). [Copy for LLMs h2. [Why use Nuxt AI Ready?](#why-use-nuxt-ai-ready) Nuxt AI Ready makes your site discoverable by AI agents and LLMs with minimal config and best practice defaults. Users increasingly ask AI assistants questions your site could answer—but LLMs only cite sources they can parse. Two standards are emerging to solve this: - **[**llms.txt**](https://llmstxt.org/)** — AI-readable site summaries - **[**MCP**](https://modelcontextprotocol.io/)** — Protocol for agents to query your content directly Rather than manually maintaining these files, Nuxt AI Ready generates them automatically based on your site content. **License:** Nuxt AI Ready is a [**Nuxt SEO Pro**](https://nuxtseo.com/pro) module. h2. [Features](#features) h3. [📄 llms.txt Generation](#llmstxt-generation) Automatic [**llms.txt standard**](https://llmstxt.org/) files at build time: - `/llms.txt` — Site overview with page links - `/llms-full.txt` — Full markdown content for all pages h3. [📝 On-Demand Markdown](#on-demand-markdown) Any route available as `.md` (e.g., `/about` → `/about.md`) with smart bot detection and cache headers. h3. [🤖 MCP Server](#mcp-server) [**Model Context Protocol**](https://modelcontextprotocol.io/) server with `list_pages` and `search_pages_fuzzy` tools for AI agent integration. h3. [🛡️ Content Signals](#️-content-signals) Configure AI training/search permissions via [**Content Signals**](https://nuxtseo.com/docs/ai-ready/getting-started/introduction/docs/ai-ready/guides/content-signals) in `robots.txt`. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/1.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/getting-started/introduction/docs/ai-ready/getting-started/introduction.md) **Did this page help you? ** h3. **Related ** [**Installation**](https://nuxtseo.com/docs/ai-ready/getting-started/introduction/docs/ai-ready/getting-started/installation) [**llms.txt Generation**](https://nuxtseo.com/docs/ai-ready/getting-started/introduction/docs/ai-ready/guides/llms-txt) [**Model Context Protocol (MCP)**](https://nuxtseo.com/docs/ai-ready/getting-started/introduction/docs/ai-ready/guides/mcp) [**Installation** Get started with Nuxt AI Ready by installing the dependency to your project.](https://nuxtseo.com/docs/ai-ready/getting-started/introduction/docs/ai-ready/getting-started/installation) **On this page** - [Why use Nuxt AI Ready?](#why-use-nuxt-ai-ready) - [Features](#features) --- ### Launching and Monitoring Your Nuxt Site · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen Description: Submit sitemap to Search Console, verify indexing, monitor rankings. Complete post-launch SEO workflow for Nuxt. h1. **Launching and Monitoring Your Nuxt Site** Submit sitemap to Search Console, verify indexing, monitor rankings. Complete post-launch SEO workflow for Nuxt. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Nov 3, 2024** Updated **Jan 4, 2026** Submit your sitemap to Google Search Console, verify your site, and monitor the Page Indexing report weekly. New sites take 1-4 weeks to get indexed. Sites with backlinks and regular updates get crawled faster. h2. [Pre-Launch Checklist](#pre-launch-checklist) Before deploying to production: - Production domain configured with SSL certificate - [**robots.txt**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/controlling-crawlers/robots-txt) allows search engine crawlers - No `noindex` [**meta tags**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/controlling-crawlers/meta-tags) on pages you want indexed - [**Sitemap**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/controlling-crawlers/sitemaps) generated at `/sitemap.xml` - [**Canonical URLs**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/controlling-crawlers/canonical-urls) point to production domain - Social preview images working - Mobile-friendly design - [**Core Web Vitals**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/core-web-vitals) passing - [**404 pages**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/routes-and-rendering/404-pages) return proper status codes - [**Redirects**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/controlling-crawlers/redirects) from old URLs configured (if redesigning) Don't delay your launch chasing perfection. Ship with working fundamentals, then iterate. h2. [Getting Indexed](#getting-indexed) Your site needs two things to appear in search results: be crawlable and be indexed. **Crawlability** means search engines can access your pages. **Indexing** means they've added your pages to their database. Read the [**Going Live guide**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/going-live) for deployment strategies and common pitfalls. h3. [Submit to Search Engines](#submit-to-search-engines) Google and Bing won't automatically know your site exists. [**Set up Google Search Console**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/search-console) and submit your sitemap: 1. Add your property (domain or URL prefix) 2. Verify ownership (DNS record, HTML file, or meta tag) 3. Submit sitemap at **Indexing > Sitemaps** For Bing, submit through [**Bing Webmaster Tools**](https://www.bing.com/webmasters) or use [**IndexNow**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/indexnow) for instant notification. Indexing takes days to weeks. [**Google prioritizes sites**](https://developers.google.com/search/docs/fundamentals/how-search-works) with backlinks and regular content updates. h3. [Check Indexing Status](#check-indexing-status) ``` h1. Search for your domain site:yourdomain.com h1. Check specific page site:yourdomain.com/specific-page ``` Google Search Console shows detailed indexing data under Page Indexing reports: - **Indexed**: in Google's index - **Discovered - currently not indexed**: found but not crawled yet - **Crawled - currently not indexed**: crawled but Google chose not to index See [**Debugging Indexing Issues**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/indexing-issues) for fixes when pages won't index. h2. [Monitoring Performance](#monitoring-performance) Set up these tools after launch: | **Tool** | **What It Tracks** | **Cost** | | --- | --- | --- | | [**Google Search Console**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/search-console) | Impressions, clicks, rankings, indexing | Free | | GA4 / Plausible / Fathom | Organic traffic, user behavior | Free / Paid | | Ahrefs / SEMrush | Backlinks, keyword rankings, competitors | Paid | See [**SEO Monitoring Tools**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/seo-monitoring) for setup guides and recommendations. h3. [Weekly Metrics](#weekly-metrics) Track these metrics every week: **Search Console:** - Total impressions and clicks - Click-through rate (CTR) - Average position for target keywords - Coverage errors **Analytics:** - Organic traffic trends - Top landing pages - Bounce rate **[**Core Web Vitals**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/core-web-vitals):** - LCP under 2.5s - INP under 200ms - CLS under 0.1 Don't obsess over daily fluctuations. Look for trends over weeks and months. h2. [Common Post-Launch Issues](#common-post-launch-issues) **Site not indexing after weeks:** Check [**robots.txt**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/controlling-crawlers/robots-txt), verify no `noindex` tags, confirm [**sitemap**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/controlling-crawlers/sitemaps) submitted correctly. See [**Indexing Issues**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/indexing-issues). **Pages indexed but not ranking:** Normal for new sites. Improve [**title tags**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/mastering-meta/titles) and [**meta descriptions**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/mastering-meta/descriptions). Add internal links. **High impressions, low clicks:** Your titles and descriptions need work. Check [**Mastering Meta**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/mastering-meta) guides. **Migrating to a new domain?** See [**Site Migration SEO**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/site-migration) for redirect mapping and recovery timelines. h2. [Deep Dive Guides](#deep-dive-guides) | **Guide** | **What You'll Learn** | | --- | --- | | [**Going Live**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/going-live) | First-time indexing, deployment strategies, common Nuxt issues | | [**Google Search Console**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/search-console) | Verification, reports, URL inspection | | [**Core Web Vitals**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/core-web-vitals) | LCP, INP, CLS optimization for Nuxt | | [**Indexing Issues**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/indexing-issues) | Fix "crawled not indexed" errors | | [**SEO Monitoring**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/seo-monitoring) | Analytics setup, rank tracking, alerts | | [**Site Migration**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/site-migration) | Domain changes, redirects, recovery | | [**IndexNow**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/indexnow) | Instant indexing for Bing/Yandex | | [**AI Search Optimization**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/ai-optimized-content) | GEO, AI Overviews, ChatGPT citations | --- [**Security** Robots.txt is a polite suggestion. Malicious crawlers ignore it. Here's how to actually protect your Nuxt app.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/routes-and-rendering/security) [**Getting Indexed** How to get your Nuxt site crawled and indexed for the first time by Google.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/learn-seo/nuxt/launch-and-listen/going-live) **On this page** - [Pre-Launch Checklist](#pre-launch-checklist) - [Getting Indexed](#getting-indexed) - [Monitoring Performance](#monitoring-performance) - [Common Post-Launch Issues](#common-post-launch-issues) - [Deep Dive Guides](#deep-dive-guides) --- ### Control Web Crawlers and Crawl Budget in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers Description: Manage how search engines crawl and index your Nuxt app. Configure robots.txt, sitemaps, canonical URLs, and redirects for better SEO. h1. **Control Web Crawlers and Crawl Budget in Nuxt** Manage how search engines crawl and index your Nuxt app. Configure robots.txt, sitemaps, canonical URLs, and redirects for better SEO. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Jan 4, 2026** Web crawlers determine what gets indexed and how often. Controlling them affects your [**crawl budget**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget): the number of pages Google will crawl on your site in a given timeframe. Most sites don't need to worry about crawl budget. But if you have 10,000+ pages, frequently updated content, or want to block AI training bots, crawler control matters. h2. [Types of Crawlers](#types-of-crawlers) **Search engines**: Index your pages for search results - [**Googlebot**](https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers) (28% of bot traffic) - [**Bingbot**](https://ahrefs.com/seo/glossary/bingbot) **Social platforms**: Generate link previews when shared - [**FacebookExternalHit**](https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/) - Twitterbot, Slackbot, Discordbot **AI training**: Scrape content for model training - [**GPTBot**](https://platform.openai.com/docs/bots/overview-of-openai-crawlers) (7.5% of bot traffic, [**most blocked in 2024**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/)) - [**ClaudeBot**](https://www.anthropic.com), [**CCBot**](https://commoncrawl.org/ccbot), [**Google-Extended**](https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers) **Malicious**: Ignore robots.txt, spoof user agents, scan for vulnerabilities. Block these at the [**firewall level**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/security), not with robots.txt. h2. [Control Mechanisms](#control-mechanisms) | **Mechanism** | **Use When** | | --- | --- | | [**robots.txt**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/robots-txt) | Block site sections, manage crawl budget, block AI crawlers | | [**Sitemaps**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/sitemaps) | Help crawlers discover pages, especially on large sites | | [**Meta robots**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/meta-tags) | Control indexing per page (noindex, nofollow) | | [**Canonical URLs**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/canonical-urls) | Consolidate duplicate content, handle URL parameters | | [**Redirects**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/redirects) | Preserve SEO when moving/deleting pages | | [**llms.txt**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/llms-txt) | Guide AI tools to your documentation (via nuxt-llms) | | [**X-Robots-Tag**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) | Control non-HTML files (PDFs, images) | | [**Firewall**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/security) | Block malicious bots at network level | h2. [Quick Recipes](#quick-recipes) **Block page from indexing**: [**Full guide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/meta-tags) pages/admin.vue ``` <script setup> useSeoMeta({ robots: 'noindex, follow' }) </script> ``` **Block AI training bots**: [**Full guide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/robots-txt) public/robots.txt ``` User-agent: GPTBot User-agent: ClaudeBot User-agent: CCBot Disallow: / ``` **Fix duplicate content**: [**Full guide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/canonical-urls) pages/products/[id].vue ``` <script setup> const route = useRoute() useHead({ link: [{ rel: 'canonical', href: \`https://mysite.com/products/${route.params.id}\` }] }) </script> ``` **Redirect moved page**: [**Full guide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/redirects) nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/old-url': { redirect: { to: '/new-url', statusCode: 301 } } } }) ``` h2. [When Crawler Control Matters](#when-crawler-control-matters) Most small sites don't need to optimize crawler behavior. But it matters when: **Crawl budget concerns**: Sites with 10,000+ pages need Google to prioritize important content. Block low-value pages (search results, filtered products, admin areas) so crawlers focus on what matters. **Duplicate content**: URLs like `/about` and `/about/` compete against each other. Same with `?sort=price` variations. [**Canonical tags**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/canonical-urls) consolidate these. **Staging environments**: Search engines index any public site they find. Block staging/dev environments in [**robots.txt**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/robots-txt) to avoid duplicate content issues. **AI training opt-out**: [**GPTBot was the most-blocked crawler in 2024**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/). Block AI training bots without affecting search rankings. **Server costs**: Bots consume CPU. Heavy pages (maps, infinite scroll, SSR) cost money per request. Blocking unnecessary crawlers reduces load. h2. [Nuxt SEO Modules](#nuxt-seo-modules) Nuxt handles crawler control through dedicated modules. Install once, configure in `nuxt.config.ts`, and forget about it. [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/docs/nuxt-seo/getting-started/introduction) [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/docs/robots/getting-started/introduction) [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/docs/sitemap/getting-started/introduction) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/docs/seo-utils/getting-started/introduction) --- [**Twitter Cards** Configure Twitter/X Cards to control how your links appear when shared on Twitter with rich previews, images, and metadata.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/mastering-meta/twitter-cards) [**Robots.txt** Robots.txt tells crawlers what they can access. Here's how to set it up in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/learn-seo/nuxt/controlling-crawlers/robots-txt) **On this page** - [Types of Crawlers](#types-of-crawlers) - [Control Mechanisms](#control-mechanisms) - [Quick Recipes](#quick-recipes) - [When Crawler Control Matters](#when-crawler-control-matters) - [Nuxt SEO Modules](#nuxt-seo-modules) --- ### Nuxt Routing and SEO · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering Description: URL patterns, rendering modes, and routing decisions that affect your Nuxt site's search rankings. SSR vs SSG vs SPA explained. h1. **Nuxt Routing and SEO** URL patterns, rendering modes, and routing decisions that affect your Nuxt site's search rankings. SSR vs SSG vs SPA explained. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Nov 3, 2024** Updated **Jan 4, 2026** Use path segments (`/products/shoes`) instead of query parameters (`/products?id=123`), keep URLs under 60 characters, and use hyphens between words. Nuxt's SSR renders your content server-side by default, so search engines see it immediately without waiting for JavaScript. Google can render JavaScript, but the [**second-wave indexing**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) delays content discovery by days or weeks. SSR avoids this entirely. h2. [Section Overview](#section-overview) | **Topic** | **What You'll Learn** | | --- | --- | | [**URL Structure**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/url-structure) | Slugs, hyphens vs underscores, URL length, keyword placement | | [**Pagination**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/pagination) | Self-referencing canonicals, infinite scroll vs pagination, crawlable links | | [**Trailing Slashes**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/trailing-slashes) | Consistent URL formats, redirect configuration | | [**Query Parameters**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/query-parameters) | Filters, tracking params, canonical handling | | [**Hreflang & i18n**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/i18n) | Multilingual sites, x-default, return links | | [**404 Pages**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/404-pages) | Soft 404s, proper status codes, crawl budget | | [**Dynamic Routes**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/dynamic-routes) | Route params, per-route meta tags, SSR patterns | | [**Rendering Modes**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/rendering) | SSR vs SSG vs SPA, when to use each | h2. [Rendering Mode Quick Reference](#rendering-mode-quick-reference) | **Mode** | **Crawler Sees HTML** | **Best For** | | --- | --- | --- | | **SSR** (default) | Immediately | Dynamic content, personalization | | **SSG** | Immediately | Blogs, docs, marketing pages | | **SPA** | After JavaScript | Admin panels, authenticated apps | Nuxt uses SSR by default. Configure rendering per route with `routeRules` in `nuxt.config.ts`: ``` export default defineNuxtConfig({ routeRules: { '/blog/**': { prerender: true }, // SSG '/dashboard/**': { ssr: false }, // SPA '/api/**': { ssr: false } // Client-only } }) ``` Google's JavaScript rendering has a [**second wave of indexing**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) that delays content discovery. SSR avoids this delay. h2. [URL Structure Quick Reference](#url-structure-quick-reference) **Path segments over query parameters:** ``` ✅ /products/electronics/laptop ❌ /products?category=electronics&item=laptop ``` **Hyphens over underscores:** ``` ✅ /nuxt-routing-guide ❌ /nuxt_routing_guide ``` **Lowercase, short URLs:** ``` ✅ /blog/nuxt-seo ❌ /Blog/The-Complete-Guide-To-Nuxt-SEO-Optimization-2025 ``` Read [**URL Structure**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/url-structure) for implementation details. h2. [Crawl Budget](#crawl-budget) Google allocates limited time to crawl your site. Poor URL structure wastes budget on duplicate or low-value pages. **Common crawl budget problems:** | **Problem** | **Solution** | | --- | --- | | Inconsistent trailing slashes | [**Configure redirects**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/trailing-slashes) | | Pagination without canonicals | [**Self-referencing canonicals**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/pagination) | | Infinite filter combinations | [**Noindex or block in robots.txt**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/query-parameters) | | Soft 404s returning 200 | [**Proper status codes**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/404-pages) | Sites under 10,000 pages rarely need to worry about crawl budget. Large sites should monitor [**Google Search Console**](https://search.google.com/search-console) crawl stats. h2. [File-Based Routing](#file-based-routing) Nuxt creates routes automatically from the `/pages` directory: ``` pages/ blog/ [slug].vue → /blog/:slug products/ [category]/ [id].vue → /products/:category/:id ``` Generates `/blog/nuxt-seo-guide` and `/products/electronics/123`. Each dynamic route needs unique meta tags: ``` <script setup lang="ts"> const route = useRoute() const { data: post } = await useFetch(\`/api/posts/${route.params.slug}\`) useSeoMeta({ title: post.value.title, description: post.value.excerpt }) </script> ``` Read [**Dynamic Routes**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/dynamic-routes) for SSR patterns, optional params, and common SEO issues. h2. [Multilingual Sites](#multilingual-sites) Use hreflang tags to tell search engines which language version to show users: ``` useHead({ link: [ { rel: 'alternate', hreflang: 'en', href: 'https://example.com/en' }, { rel: 'alternate', hreflang: 'fr', href: 'https://example.com/fr' }, { rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/en' } ] }) ``` The `@nuxtjs/i18n` module handles this automatically: ``` export default defineNuxtConfig({ modules: ['@nuxtjs/i18n'], i18n: { locales: ['en', 'fr'], defaultLocale: 'en', strategy: 'prefix_except_default' } }) ``` Read [**Hreflang & i18n**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/i18n) for configuration, bidirectional links, and common mistakes. --- [**llms.txt** Help AI assistants understand your Nuxt documentation with the llms.txt standard. Configure nuxt-llms for automatic generation.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/controlling-crawlers/llms-txt) [**URL Structure** Create search-optimized URLs using file-based routing. Learn slug formatting, parameter handling, and route patterns that improve rankings.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/learn-seo/nuxt/routes-and-rendering/url-structure) **On this page** - [Section Overview](#section-overview) - [Rendering Mode Quick Reference](#rendering-mode-quick-reference) - [URL Structure Quick Reference](#url-structure-quick-reference) - [Crawl Budget](#crawl-budget) - [File-Based Routing](#file-based-routing) - [Multilingual Sites](#multilingual-sites) --- ### MCP Server · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/guides/mcp Description: Connect AI assistants to Nuxt SEO documentation using the Model Context Protocol. **Core Concepts** h1. **MCP Server** [Copy for LLMs The Nuxt SEO MCP server gives AI assistants access to module documentation, configuration examples, and quick reference guides. h2. [Quick Install](#quick-install) [Install MCP in Cursor](https://nuxtseo.com/mcp/deeplink?ide=cursor) [Install MCP in VS Code](https://nuxtseo.com/mcp/deeplink?ide=vscode) h2. [Manual Installation](#manual-installation) ``` claude mcp add --transport http nuxt-seo https://nuxtseo.com/mcp ``` ``` // .cursor/mcp.json { "mcpServers": { "nuxt-seo": { "type": "http", "url": "https://nuxtseo.com/mcp" } } } ``` ``` // .vscode/mcp.json { "mcpServers": { "nuxt-seo": { "type": "http", "url": "https://nuxtseo.com/mcp" } } } ``` ``` // ~/.codeium/windsurf/mcp_config.json { "mcpServers": { "nuxt-seo": { "serverUrl": "https://nuxtseo.com/mcp" } } } ``` ``` // Zed settings { "context_servers": { "nuxt-seo": { "settings": { "url": "https://nuxtseo.com/mcp" } } } } ``` ``` 1. Go to **Settings** > **Connectors** 2. Add a new connector with URL: \`https://nuxtseo.com/mcp\` ``` h2. [Tools](#tools) h3. [get_module_docs](#get_module_docs) Fetch documentation for any Nuxt SEO module. Returns a summary and page list—request specific pages separately for full content. | **Parameter** | **Type** | **Description** | | --- | --- | --- | | `module` | `string` | `nuxt-seo`, `robots`, `sitemap`, `og-image`, `schema-org`, `link-checker`, `seo-utils`, `site-config` | | `section` | `string` | Filter to `config`, `composables`, `components`, or `examples` | | `page` | `string` | Specific page path to get full content | ``` // Get module summary get_module_docs({ module: 'schema-org' }) // Get specific section get_module_docs({ module: 'og-image', section: 'composables' }) // Get full page content get_module_docs({ module: 'sitemap', page: '/api/config' }) ``` h3. [list_modules](#list_modules) Lists all Nuxt SEO modules with stats (stars, downloads, versions). ``` list_modules() ``` h2. [Resources](#resources) h3. [nuxt-seo://reference](#nuxt-seoreference) Complete reference for all modules, config, and schema.org patterns in one resource. **URI:** `nuxt-seo://reference` Includes: - Module overview with key config - Key composables quick reference - Complete nuxt.config.ts example - Schema.org patterns for Article, Product, Person, FAQ, Organization h3. [nuxtseo://modules](#nuxtseomodules) List of all modules with stats (stars, downloads, versions). **URI:** `nuxtseo://modules` h2. [Nuxt SEO Pro MCP](#nuxt-seo-pro-mcp) Need code generation, page analysis, or keyword research? [**Nuxt SEO Pro**](https://nuxtseo.com/docs/nuxt-seo/guides/mcp/docs/nuxt-seo-pro/mcp/installation) adds: - `analyze_page` - Analyze Vue files for SEO issues - `generate_schema_org` - Generate `useSchemaOrg()` code - `generate_og_image_template` - Generate OG image components - Content Intelligence tools (keyword research, SERP analysis, ranking checks) - Content generation prompts [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/2.guides/3.mcp.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/guides/mcp/docs/nuxt-seo/guides/mcp.md) **Did this page help you? ** [**Nuxt Content** Integrating Nuxt SEO with Nuxt Content.](https://nuxtseo.com/docs/nuxt-seo/guides/mcp/docs/nuxt-seo/guides/nuxt-content) [**LLMs.txt** Help AI tools understand Nuxt SEO modules so they can assist you better.](https://nuxtseo.com/docs/nuxt-seo/guides/mcp/docs/nuxt-seo/guides/llms-txt) **On this page** - [Quick Install](#quick-install) - [Manual Installation](#manual-installation) - [Tools](#tools) - [Resources](#resources) - [Nuxt SEO Pro MCP](#nuxt-seo-pro-mcp) --- ### Vue SEO Meta Tags Guide · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/mastering-meta Description: Set up meta tags in Vue 3 with Unhead. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org with SSR patterns. h1. **Vue SEO Meta Tags Guide** Set up meta tags in Vue 3 with Unhead. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org with SSR patterns. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - Use `useSeoMeta()` from @unhead/vue for type-safe meta tag management - Meta tags must render server-side. client-only apps show empty `<head>` to crawlers - Twitter, Slack, LinkedIn all use Open Graph tags. only set `twitterCard` separately Vue SPAs are invisible to search engines by default. Meta tags only work when rendered server-side. client-only apps show empty `<head>` tags to crawlers. This guide covers SSR-compatible patterns using [**Unhead**](https://unhead.unjs.io/). ``` <head> <title>My Page · My Site ``` In Vue, you manage these with `useSeoMeta()` or `useHead()` from @unhead/vue: ``` useSeoMeta({ title: 'My Page', description: 'What this page is about', ogImage: 'https://mysite.com/og.png' }) ``` h2. [What Meta Tags Control](#what-meta-tags-control) | **Meta Tag** | **Used By** | **Purpose** | | --- | --- | --- | | [``](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/titles) | Search engines, browsers | Page title in search results and browser tab | | [`<meta name="description">`](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/descriptions) | Search engines | Snippet text in search results (Google rewrites ~70%) | | [`<meta property="og:*">`](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/social-sharing) | Facebook, LinkedIn, Discord, Slack | Link preview cards | | [`<meta name="twitter:*">`](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/social-sharing) | Twitter/X | Tweet cards (falls back to OG tags) | | [`<script type="application/ld+json">`](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/schema-org) | Search engines | Rich results (stars, FAQs, recipes) | h3. [Search Engine Tags](#search-engine-tags) These affect how your pages appear in Google, Bing, and other search results. - [**Page Titles**](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/titles) - Your main call-to-action in search results. Keep under 60 characters. - [**Meta Descriptions**](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/descriptions) - The snippet below your title. Google rewrites most of them, but write them anyway for the 30% that stick. h3. [Social Sharing Tags](#social-sharing-tags) These control link previews when someone shares your URL. - [**Social Sharing**](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/social-sharing) - Open Graph (Facebook, LinkedIn, Discord, Slack) and Twitter Cards in one guide. Set `og:title`, `og:description`, `og:image`, and `twitterCard`. h3. [Structured Data](#structured-data) - [**Schema.org**](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/schema-org) - JSON-LD markup for rich results. Overkill for most sites, but useful for recipes, products, FAQs. h3. [Migrating from vue-meta?](#migrating-from-vue-meta) vue-meta was the Vue 2 standard but never shipped Vue 3 support. See [**Migrating from vue-meta**](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/migrating-vue-meta) for side-by-side syntax and migration steps. h2. [Quick Setup](#quick-setup) Most sites need just this: ``` // app.vue or main component useHead({ title: 'My Site', titleTemplate: '%s · My Site', }) useSeoMeta({ ogSiteName: 'My Site', twitterCard: 'summary_large_image' }) ``` Then per-component: ``` <script setup lang="ts"> useSeoMeta({ title: 'About Us', description: 'We build things.', ogImage: 'https://mysite.com/about-og.png' }) </script> ``` That's it. Twitter falls back to OG tags. Slack uses OG tags. LinkedIn uses OG tags. Don't duplicate unless you need platform-specific content. h2. [Priority Order](#priority-order) When tags conflict, here's what wins: 1. **Component-level** `useSeoMeta()` in your component 2. **Parent component** `useSeoMeta()` in your parent 3. **App-level** setup in main.ts or app.vue Unhead merges these automatically. Child component tags override parent tags. h2. [Common Mistakes](#common-mistakes) **Forgetting absolute URLs for images** ``` // ❌ Won't work - relative path ogImage: '/og.png' // ✅ Works - absolute URL ogImage: 'https://mysite.com/og.png' ``` **Setting the same description everywhere** Each page needs a unique description. Template descriptions like "Welcome to {page}" are lazy and hurt click-through rates. **Ignoring the preview** Test your meta tags before deploying: - [**Google Rich Results Test**](https://search.google.com/test/rich-results) - Structured data validation - [**Facebook Sharing Debugger**](https://developers.facebook.com/tools/debug/) - OG tags for most platforms - X/Twitter - Compose a tweet (don't post) to preview cards [**Start with Page Titles →**](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/titles) h2. [Using Nuxt?](#using-nuxt) [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/mastering-meta/docs/nuxt-seo/getting-started/introduction) handles meta tags automatically. [**See the Nuxt guide →**](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/nuxt/mastering-meta) --- [**Vue SEO** Complete Vue.js SEO resource. Meta tags, crawl control, SPA solutions, SSR frameworks, and performance monitoring. with Unhead code examples.](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue) [**Titles** Set dynamic page titles in Vue 3 with useHead. Learn title templates, reactive titles, and SSR patterns that Google indexes correctly.](https://nuxtseo.com/learn-seo/vue/mastering-meta/learn-seo/vue/mastering-meta/titles) **On this page** - [What Meta Tags Control](#what-meta-tags-control) - [Quick Setup](#quick-setup) - [Priority Order](#priority-order) - [Common Mistakes](#common-mistakes) - [Using Nuxt?](#using-nuxt) --- ### Vue SSR Frameworks · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/ssr-frameworks Description: Compare Vue server-side rendering frameworks. Choose between Nuxt, Quasar, Vite SSR, and VitePress for better SEO and performance. h1. **Vue SSR Frameworks** Compare Vue server-side rendering frameworks. Choose between Nuxt, Quasar, Vite SSR, and VitePress for better SEO and performance. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** **What you'll learn** - Nuxt is the recommended choice for most Vue SSR projects (937k weekly downloads vs Quasar's 162k) - VitePress is best for static content like docs and blogs - Quasar is ideal for cross-platform apps (web + mobile + desktop) [**Vue server-side rendering**](https://vuejs.org/guide/scaling-up/ssr.html) makes your app SEO-friendly by shipping HTML instead of blank pages. But you'll need a framework. manual SSR setups are complex and error-prone. 45% of Vue developers now use SSR ([**State of Vue 2025**](https://stateofvue.framer.website/)). up from 31% in 2021. The ecosystem offers four main options. h2. [Framework Comparison](#framework-comparison) | **Framework** | **Best For** | **SEO Strength** | **Complexity** | **GitHub Stars** | | --- | --- | --- | --- | --- | | [**Nuxt**](#nuxt) | Full-stack apps, content sites | Excellent | Medium | 57.7k | | [**Quasar**](#quasar) | Cross-platform (web + mobile + desktop) | Good | High | 26.7k | | [**Vite SSR**](#vite-ssr) | Custom setups, library authors | Good | Very High | . | | [**VitePress**](#vitepress) | Documentation, blogs | Excellent | Low | 13.5k | Download stats show dominance: Nuxt has 937k weekly npm downloads vs Quasar's 162k ([**npm trends**](https://npmtrends.com/nuxt-vs-quasar-vs-vite-plugin-ssr)). h2. [Nuxt](#nuxt) [**Nuxt**](https://nuxt.com/) is the Vue equivalent of Next.js. Convention-over-configuration file routing, automatic code splitting, and a rich module ecosystem. **Why Nuxt for SEO:** - SSR, SSG, or hybrid per page. ship HTML however you need it - [**Module ecosystem**](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/docs/nuxt-seo/getting-started/introduction) handles sitemaps, robots.txt, OG images without config - Prerendering for static hosting (Netlify, Cloudflare Pages) - Active development and large community **When to use:** - Content-heavy sites (blogs, docs, marketing) - Apps needing both public SEO pages and auth-protected areas - Teams wanting "it just works" defaults **When not to:** - Mobile/desktop apps (Quasar better) - Maximum control needed (Vite SSR better) - Static docs only (VitePress simpler) h2. [Quasar](#quasar) [**Quasar**](https://quasar.dev/) is a complete framework for building cross-platform apps from one codebase. web (SPA/SSR/PWA), mobile (iOS/Android), and desktop (Electron). **Why Quasar for SEO:** - SSR mode ships full HTML for search engines - Built-in component library (faster dev, consistent UI) - PWA takeover after hydration for offline support **When to use:** - Multi-platform projects (web + native mobile) - Teams wanting pre-built UI components - Projects needing offline-first PWA features **When not to:** - Web-only projects (Nuxt has better ecosystem) - Need for Nuxt's rich plugin system ([**plugin gap noted by developers**](https://github.com/quasarframework/quasar/issues/11165)) - Minimal UI framework constraints wanted **Performance note:** Quasar's SSR adoption lags Nuxt ([**comparison shows Nuxt 6x more popular**](https://stackshare.io/stackups/nuxt-vs-quasar-framework)). Module ecosystem smaller. fewer integrations for SEO tools like auto-generated sitemaps. h2. [Vite SSR](#vite-ssr) [**Vite's native SSR API**](https://vite.dev/guide/ssr) is a low-level solution for framework authors and developers wanting complete control. **Why Vite SSR:** - No abstraction. full control over SSR behavior - Works with any server (Express, Fastify, Cloudflare Workers) - Can integrate with existing backends **When to use:** - Building your own framework - Migrating existing app with complex server setup - Need precise control over rendering pipeline **When not to:** - Building typical web app (Nuxt/Quasar faster) - Team unfamiliar with SSR internals - Want batteries-included solutions [**Vue's official docs**](https://vuejs.org/guide/scaling-up/ssr.html) recommend Nuxt over manual Vite SSR for most projects. The low-level API is "meant for library and framework authors." h2. [VitePress](#vitepress) [**VitePress**](https://vitepress.dev/) is a static site generator optimized for documentation and content-focused sites. **Why VitePress for SEO:** - Prerendered HTML at build time (perfect for search engines) - Fast page loads. minimal JavaScript - Markdown-based content (simple authoring) - Used by [**Vite, Vue, VueUse, Pinia docs**](https://patak.dev/vite/ecosystem.html) **When to use:** - Documentation sites - Blogs, marketing sites - Content rarely changes - No dynamic user data needed **When not to:** - Need dynamic SSR (user-specific data) - Building web apps with auth - E-commerce or dashboards VitePress pre-renders everything during build using [**Vue's SSR capabilities**](https://vitepress.dev/guide/ssr-compat). Zero runtime SSR overhead. h2. [Decision Matrix](#decision-matrix) **Start with this:** ``` Is it docs/blog only? ├─ Yes → VitePress └─ No → Does it need mobile/desktop apps? ├─ Yes → Quasar └─ No → Do you need SSR? ├─ Yes → Nuxt └─ No → SPA is fine ``` **When SEO matters most:** 1. **Nuxt** . Best ecosystem, most SEO modules available 2. **VitePress** . Perfect HTML, fastest loads (if static fits) 3. **Quasar** . Good SSR, but fewer SEO integrations 4. **Vite SSR** . Manual everything, including SEO h2. [Framework Details](#framework-details) Each approach has different implications for SEO implementation: h3. [Nuxt Implementation](#nuxt-implementation) ``` <script setup> // Meta tags just work useSeoMeta({ title: 'Page Title', description: 'Page description', ogImage: '/og-image.jpg' }) </script> ``` Modules handle sitemap generation, robots.txt, schema.org automatically. h3. [Quasar Implementation](#quasar-implementation) ``` // quasar.config.js module.exports = { ssr: { pwa: true // PWA takeover after hydration } } ``` Need to manually configure meta tags per page. Fewer pre-built SEO tools compared to Nuxt. h3. [Vite SSR Implementation](#vite-ssr-implementation) ``` // server.js - you write everything import { renderToString } from 'vue/server-renderer' app.get('*', async (req, res) => { const html = await renderToString(createApp()) res.send(\`<!DOCTYPE html>${html}\`) }) ``` Complete control means complete responsibility for SEO implementation. h3. [VitePress Implementation](#vitepress-implementation) ``` --- title: Page Title description: Page description --- h1. Content Just write markdown. ``` Meta tags and SEO handled automatically from frontmatter. h2. [Migration Paths](#migration-paths) **From Vue SPA → SSR:** - Nuxt easiest migration. similar patterns to Vue Router - Quasar if targeting mobile too - Check [**SPA SEO guide**](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/learn-seo/vue/spa) for SPA-specific concerns **From manual Vite SSR:** - Nuxt for production stability - Vite SSR if custom server logic required **From VitePress:** - Nuxt when outgrowing static (need auth, user data, dynamic content) h2. [Performance Comparison](#performance-comparison) All frameworks support modern optimizations. code splitting, lazy loading, tree shaking. Differences are in defaults and ease: **Build size** . VitePress smallest (static), Nuxt/Quasar similar, Vite SSR depends on your setup **Server response** . SSR frameworks (Nuxt, Quasar) slower first byte than static (VitePress), but serve personalized content **Hydration** . Nuxt and Quasar optimize automatically. Vite SSR requires manual optimization. [**2025 framework performance**](https://blog.logrocket.com/angular-vs-react-vs-vue-js-performance) focuses on reactivity models over SSR overhead. all Vue-based frameworks benefit from Vue 3.5's fine-grained reactivity. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/docs/nuxt-seo/getting-started/introduction) which automates most SEO tasks. [**Learn more about SSR in Nuxt →**](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/learn-seo/nuxt/routes-and-rendering/rendering) --- [**Security** Robots.txt is a polite suggestion. Malicious crawlers ignore it. Here's how to actually protect your Vue app.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/learn-seo/vue/routes-and-rendering/security) [**Nuxt vs Quasar** Nuxt dominates for SSR-first SEO. Quasar excels at cross-platform. Compare SSR capabilities, SEO features, and when to choose each.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar) **On this page** - [Framework Comparison](#framework-comparison) - [Nuxt](#nuxt) - [Quasar](#quasar) - [Vite SSR](#vite-ssr) - [VitePress](#vitepress) - [Decision Matrix](#decision-matrix) - [Framework Details](#framework-details) - [Migration Paths](#migration-paths) - [Performance Comparison](#performance-comparison) - [Using Nuxt?](#using-nuxt) --- ### Control Web Crawlers and Crawl Budget in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers Description: Manage how search engines crawl and index your Vue app. Configure robots.txt, sitemaps, canonical URLs, and redirects for better SEO. h1. **Control Web Crawlers and Crawl Budget in Vue** Manage how search engines crawl and index your Vue app. Configure robots.txt, sitemaps, canonical URLs, and redirects for better SEO. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - robots.txt is advisory. malicious crawlers ignore it, use firewalls for real security - Sites under 10,000 pages rarely need crawl budget optimization - GPTBot was the most-blocked crawler in 2024. block AI training without affecting search Web crawlers determine what gets indexed and how often. Controlling them affects your [**crawl budget**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget). the number of pages Google will crawl on your site in a given timeframe. Most sites don't need to worry about crawl budget. But if you have 10,000+ pages, frequently updated content, or want to block AI training bots, crawler control matters. h2. [Types of Crawlers](#types-of-crawlers) **Search engines** . Index your pages for search results - [**Googlebot**](https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers) (28% of bot traffic) - [**Bingbot**](https://ahrefs.com/seo/glossary/bingbot) **Social platforms** . Generate link previews when shared - [**FacebookExternalHit**](https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/) - Twitterbot, Slackbot, Discordbot **AI training** . Scrape content for model training - [**GPTBot**](https://platform.openai.com/docs/bots/overview-of-openai-crawlers) (7.5% of bot traffic, [**most blocked in 2024**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/)) - [**ClaudeBot**](https://www.anthropic.com), [**CCBot**](https://commoncrawl.org/ccbot), [**Google-Extended**](https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers) **Malicious** . Ignore robots.txt, spoof user agents, scan for vulnerabilities. Block these at the [**firewall level**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/routes-and-rendering/security), not with robots.txt. h2. [Control Mechanisms](#control-mechanisms) | **Mechanism** | **Use When** | | --- | --- | | [**robots.txt**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/robots-txt) | Block site sections, manage crawl budget, block AI bots | | [**Sitemaps**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/sitemaps) | Help crawlers discover pages, especially on large sites | | [**Meta robots**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/meta-tags) | Control indexing per page (noindex, nofollow) | | [**Canonical URLs**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/canonical-urls) | Consolidate duplicate content, handle URL parameters | | [**Redirects**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/redirects) | Preserve SEO when moving/deleting pages | | [**llms.txt**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/llms-txt) | Guide AI tools to your documentation (MCP servers, coding assistants) | | [**X-Robots-Tag**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) | Control non-HTML files (PDFs, images) | | [**Firewall**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/routes-and-rendering/security) | Block malicious bots at network level | h2. [Quick Recipes](#quick-recipes) **Block page from indexing** . [**Full guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/meta-tags) pages/admin.vue ``` <script setup> useSeoMeta({ robots: 'noindex, follow' }) </script> ``` **Block AI training bots** . [**Full guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/robots-txt) public/robots.txt ``` User-agent: GPTBot User-agent: ClaudeBot User-agent: CCBot Disallow: / ``` **Fix duplicate content** . [**Full guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/canonical-urls) pages/products/[id].vue ``` <script setup> useHead({ link: [{ rel: 'canonical', href: \`https://mysite.com/products/${route.params.id}\` }] }) </script> ``` **Redirect moved page** . [**Full guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/redirects) server.ts ``` app.get('/old-url', (req, res) => res.redirect(301, '/new-url')) ``` h2. [When Crawler Control Matters](#when-crawler-control-matters) Most small sites don't need to optimize crawler behavior. But it matters when: **Crawl budget concerns** . Sites with 10,000+ pages need Google to prioritize important content. Block low-value pages (search results, filtered products, admin areas) so crawlers focus on what matters. **Duplicate content** . URLs like `/about` and `/about/` compete against each other. Same with `?sort=price` variations. [**Canonical tags**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/canonical-urls) consolidate these. **Staging environments** . Search engines index any public site they find. Block staging/dev environments in [**robots.txt**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/robots-txt) to avoid duplicate content issues. **AI training opt-out** . [**GPTBot was the most-blocked crawler in 2024**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/). Block AI training bots without affecting search rankings. **Server costs** . Bots consume CPU. Heavy pages (maps, infinite scroll, SSR) cost money per request. Blocking unnecessary crawlers reduces load. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about controlling crawlers in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/nuxt/controlling-crawlers) --- [**Rich Results** Get rich results in Google search with Schema.org structured data. Learn which types still work after Google's 2023 FAQ/HowTo removals.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/mastering-meta/rich-results) [**Robots.txt** Robots.txt tells crawlers what they can access. Here's how to set it up in Vue.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/learn-seo/vue/controlling-crawlers/robots-txt) **On this page** - [Types of Crawlers](#types-of-crawlers) - [Control Mechanisms](#control-mechanisms) - [Quick Recipes](#quick-recipes) - [When Crawler Control Matters](#when-crawler-control-matters) - [Using Nuxt?](#using-nuxt) --- ### SEO for Single Page Applications (SPAs) · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/spa Description: Why Vue SPAs struggle with search engines and how to fix it. Learn when you need SSR, when prerendering works, and when SPA is fine as-is. h1. **SEO for Single Page Applications (SPAs)** Why Vue SPAs struggle with search engines and how to fix it. Learn when you need SSR, when prerendering works, and when SPA is fine as-is. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - SPAs show empty HTML to crawlers. Google queues JavaScript rendering separately from crawling - SSR needed for public content requiring indexing and social sharing - SPAs are fine for authenticated apps, dashboards, and private content Single page applications render content with JavaScript after the page loads. This creates a timing problem: search engines see an empty page before your JavaScript runs. Google can render JavaScript, but it [**queues pages for rendering**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) separately from crawling. Your page may wait seconds or longer before rendering happens. During this delay, other crawlers (Bing, social platforms, AI crawlers) often see nothing. h2. [The Core Problem](#the-core-problem) **Traditional HTML sites** . Server sends complete HTML. Crawler sees content immediately. **Vue SPAs** . Server sends minimal HTML with `<div id="app"></div>`. JavaScript downloads, executes, then renders content. Crawlers must wait and execute JavaScript to see anything. Google handles this better than other crawlers, but even Googlebot faces challenges. JavaScript rendering [**requires substantial computing capacity**](https://www.wedowebapps.com/spa-seo-optimize-single-page-applications/). downloading, parsing, and executing scripts takes more resources than reading static HTML. h3. [When SPAs Break SEO](#when-spas-break-seo) **Meta tags render too late** . Your `useSeoMeta()` calls run after JavaScript loads. Initial HTML has no title, description, or Open Graph tags. Social platforms and preview generators see nothing. **Content isn't indexed** . If rendering fails or times out, Google indexes an empty page. Even when successful, [**delayed indexing is common**](https://www.magnolia-cms.com/blog/spa-seo-mission-impossible.html). **Client-side routing hides pages** . SPAs typically use [**URL fragments**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) (`#/about`) or JavaScript-only routing. Crawlers may not discover all your routes. **Performance suffers** . JavaScript bundles delay First Contentful Paint. Search engines factor [**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/spa/learn-seo/vue/launch-and-listen/core-web-vitals) into rankings. h2. [When SPA Works Fine](#when-spa-works-fine) Not every Vue app needs server-side rendering. SPAs work when: **Your app requires authentication** . Admin panels, dashboards, internal tools. Block these from indexing with [**meta robots tags**](https://nuxtseo.com/learn-seo/vue/spa/learn-seo/vue/controlling-crawlers/meta-tags) anyway. **You don't need search traffic** . Apps used through direct links or bookmarks. No need to rank in Google. **You only share via direct links** . If users share app URLs to logged-in colleagues, Open Graph previews don't matter. **Content is user-generated and private** . Chat apps, project management tools, personal data views. For these cases, skip the SSR complexity. Focus on app performance and user experience instead. h2. [When You Need SSR](#when-you-need-ssr) Server-side rendering matters when: **Public content needs indexing** . Marketing sites, blogs, documentation, product pages, landing pages. If Google should index it, you need SSR. **Social sharing matters** . Link previews on Twitter, LinkedIn, Slack require meta tags in initial HTML. SPAs fail here without SSR or prerendering. **Performance is critical** . SSR delivers faster First Contentful Paint. Users see content before JavaScript loads. This improves both user experience and search rankings. **You want predictable indexing** . SSR guarantees search engines see your content. No waiting for JavaScript execution. No risk of rendering failures. h2. [Decision Tree](#decision-tree) ![SPA Decision Tree](https://nuxtseo.com/learn-seo/vue/spa/images/learn-seo/vue/spa-decision-tree.svg) h2. [Solutions Overview](#solutions-overview) h3. [Server-Side Rendering (SSR)](#server-side-rendering-ssr) Renders pages on server for every request. Guarantees search engines and users get complete HTML immediately. **Best for:** Dynamic content, frequent updates, personalized pages, anything requiring authentication combined with public pages. **Tools:** [**Nuxt**](https://nuxt.com) (recommended), [**Quasar SSR**](https://quasar.dev/quasar-cli-vite/developing-ssr/introduction), custom Vite SSR. **Trade-offs:** Requires Node.js server, more complex deployment, higher server costs. h3. [Prerendering](#prerendering) Generates static HTML at build time for known routes. Serves static files to crawlers and users. **Best for:** Marketing sites, blogs, documentation. content that doesn't change per request. **Tools:** [**vite-ssg**](https://github.com/antfu7/vite-ssg), [**prerender-spa-plugin**](https://github.com/chrisvfritz/prerender-spa-plugin). **Trade-offs:** Only works for routes you know at build time. Content updates require rebuilding. Can't handle dynamic routes like `/users/:id`. h3. [Dynamic Rendering](#dynamic-rendering) Serves prerendered HTML to crawlers, JavaScript app to users. [**Google deprecated this approach**](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) in 2024 but it still works. **Best for:** Migration path when you can't implement full SSR yet. **Tools:** [**Prerender.io**](https://prerender.io), [**Rendertron**](https://github.com/GoogleChrome/rendertron). **Trade-offs:** Requires bot detection (user agent sniffing), maintains two versions of your site, may be seen as cloaking if implementations differ. h2. [What Search Engines See](#what-search-engines-see) Test your SPA to understand what crawlers see: **Chrome DevTools** . Disable JavaScript in DevTools settings. Reload your page. This is what non-Google crawlers see. **View Page Source** . Right-click → View Page Source. This is the initial HTML Google receives before JavaScript runs. **Google Search Console** . [**URL Inspection tool**](https://search.google.com/search-console) shows how Google rendered your page. Compare "crawled page" vs "live page." **Mobile-Friendly Test** . [**Google's testing tool**](https://search.google.com/test/mobile-friendly) renders JavaScript and shows the result. If your content doesn't appear in these tests without JavaScript, search engines struggle with your site. h2. [Meta Tags in SPAs](#meta-tags-in-spas) Even with Google's JavaScript rendering, meta tags need to be in initial HTML for: **Social platforms** . Twitter, Facebook, LinkedIn, Slack don't run JavaScript. They only see initial HTML. **Speed** . Google uses meta tags from initial HTML when available, even if they change during rendering. **Reliability** . [**JavaScript execution can be blocked or delayed**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). Initial HTML guarantees tags are present. h3. [Wrong Approach](#wrong-approach) App.vue ``` <script setup> // ❌ Meta tags only exist after JavaScript runs useSeoMeta({ title: 'My SPA Site', description: 'This description only exists client-side' }) </script> ``` Initial HTML: ``` <!DOCTYPE html> <html> <head> <!-- Empty! No meta tags. --> </head> <body> <div id="app"></div> <script src="/app.js"></script> </body> </html> ``` h3. [Right Approach (SSR)](#right-approach-ssr) With server-side rendering, `useSeoMeta()` runs on the server: ``` <!DOCTYPE html> <html> <head> <title>My SPA Site
``` h2. [Client-Side Routing](#client-side-routing) SPAs change pages without reloading the browser. This creates problems: **URL fragments ignored** . URLs like `yoursite.com#/about` look like `yoursite.com` to search engines. The `#/about` is a browser-only detail. Google ignores it for indexing. **History API works** . Modern routers (Vue Router) use [**History API**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) with real URLs (`yoursite.com/about`). This works for SEO if you have SSR or prerendering. **Without SSR** . Every route returns the same empty HTML. Google discovers routes from links and sitemaps, but sees the same empty content for all URLs. **With SSR** . Each route renders its own HTML. Google sees different content per URL. This works correctly. h2. [URL Structure](#url-structure) Use Vue Router with History mode, not hash mode: router.ts ``` // ❌ Hash mode - SEO doesn't work createRouter({ history: createWebHashHistory(), routes: [/* ... */] }) // ✅ History mode - works with SSR/prerendering createRouter({ history: createWebHistory(), routes: [/* ... */] }) ``` Hash mode URLs (`#/about`) can't be distinguished by servers. History mode URLs (`/about`) work like traditional websites. Note: History mode requires [**server configuration**](https://router.vuejs.org/guide/essentials/history-mode.html#html5-mode) to handle direct URL access. All routes must serve your index.html. h2. [Sitemaps for SPAs](#sitemaps-for-spas) Even with SSR, generate a sitemap: **Static routes** . List all routes in `public/sitemap.xml`: public/sitemap.xml ``` https://yoursite.com/ 2025-12-17 https://yoursite.com/about 2025-12-17 https://yoursite.com/pricing 2025-12-17 ``` **Dynamic routes** . Generate sitemap from your data source during build or on demand. See [**sitemaps guide**](https://nuxtseo.com/learn-seo/vue/spa/learn-seo/vue/controlling-crawlers/sitemaps) for details. h2. [JavaScript SEO Resources](#javascript-seo-resources) Google's official guidance on JavaScript and SEO: - [**JavaScript SEO Basics**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) . How Google processes JavaScript - [**Fix JavaScript SEO Problems**](https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript) . Common issues and solutions - [**Dynamic Rendering**](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) . Deprecated workaround h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, most of this is handled automatically. Nuxt provides SSR by default, automatic sitemap generation, and proper meta tag handling. [**Learn more about SSR and SEO in Nuxt →**](https://nuxtseo.com/learn-seo/vue/spa/learn-seo/nuxt/routes-and-rendering/rendering) **Quick Check** **When does a Vue SPA need SSR for SEO?** - `Always. SPAs can't be indexed` - Google can render JavaScript, but SSR is still recommended for reliability - `When public content needs indexing and social sharing` - Correct! SSR guarantees crawlers see your content immediately - `Never. Google handles JavaScript fine` - True for search, but social platforms don't run JavaScript --- [**llms.txt** Help AI assistants understand your Vue documentation with the llms.txt standard. Learn the file format, implementation, and when it matters.](https://nuxtseo.com/learn-seo/vue/spa/learn-seo/vue/controlling-crawlers/llms-txt) [**Prerendering** Build-time and on-demand prerendering for client-side Vue apps. How to get SPA performance with SSR indexing.](https://nuxtseo.com/learn-seo/vue/spa/learn-seo/vue/spa/prerendering) **On this page** - [The Core Problem](#the-core-problem) - [When SPA Works Fine](#when-spa-works-fine) - [When You Need SSR](#when-you-need-ssr) - [Decision Tree](#decision-tree) - [Solutions Overview](#solutions-overview) - [What Search Engines See](#what-search-engines-see) - [Meta Tags in SPAs](#meta-tags-in-spas) - [Client-Side Routing](#client-side-routing) - [URL Structure](#url-structure) - [Sitemaps for SPAs](#sitemaps-for-spas) - [JavaScript SEO Resources](#javascript-seo-resources) - [Using Nuxt?](#using-nuxt) --- ### Mcp · Nuxt SEO Source: https://nuxtseo.com/admin/mcp Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/mcp/auth/github) --- ### Admin · Nuxt SEO Source: https://nuxtseo.com/admin Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/auth/github) --- ### Docs · Nuxt SEO Source: https://nuxtseo.com/admin/docs Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/docs/auth/github) --- ### Cache · Nuxt SEO Source: https://nuxtseo.com/admin/cache Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/cache/auth/github) --- ### Sites · Nuxt SEO Source: https://nuxtseo.com/admin/sites Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/sites/auth/github) --- ### Stripe · Nuxt SEO Source: https://nuxtseo.com/admin/stripe Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/stripe/auth/github) --- ### MCP Installation · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation Description: Add the Nuxt SEO Pro MCP server to Claude Desktop, Claude Code, or Cursor. One config file change and your AI assistant gains SEO tools. **Mcp** h1. **MCP Installation** [Copy for LLMs Add the SEO MCP server to your AI assistant's configuration. Works with Claude Desktop, Claude Code, Cursor, and other MCP-compatible clients. **Early Access** — The Pro MCP is in active development. Tools and prompts may change as we refine the experience. h2. [Configuration](#configuration) Add the server to your MCP client config: ``` claude mcp add nuxt-seo-pro https://nuxtseo.com/mcp/pro --transport http -H "Authorization: Bearer YOUR_API_KEY" ``` [Sign in](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/pro) to see your API key. Your API key starts with `nsp_` and is available in the [**Pro dashboard**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/pro/dashboard). h2. [Initialize Your Site](#initialize-your-site) After adding the config, restart your AI assistant and register your site: ``` Set up https://mysite.com for SEO analysis ``` This runs `init_site` which: 1. Registers the site with your Nuxt SEO Pro account 2. Profiles the site to detect type, industry, and audience 3. Enables sitemap, ranking, and site-specific tools You only need to do this once per site. h2. [Sample Queries](#sample-queries) Try these to verify everything is working: **Keyword research (uses DataForSEO)** ``` Find long-tail keyword opportunities for "nuxt seo" ``` **SERP analysis** ``` What SERP features appear for "vue meta tags"? Is there an AI Overview? ``` **Page analysis** ``` Analyze pages/index.vue for SEO issues and suggest fixes ``` **Schema.org generation** ``` Add Product schema to pages/products/[slug].vue ``` **OG image generation** ``` Create an OG image component for my blog posts with title and read time ``` **Competitive analysis** ``` What keywords does competitor.com rank for in positions 1-10? ``` **Traffic estimation** ``` How much organic traffic does competitor.com get monthly? ``` **Domain availability** ``` Check if myapp.dev, myapp.io, and myapp.com are available ``` **Social signals** ``` Is there community interest in "nuxt analytics"? Check GitHub and Reddit ``` **Full SEO improvement** ``` Run a full SEO audit on pages/pricing.vue ``` h2. [Troubleshooting](#troubleshooting) h3. ["Invalid API key" error](#invalid-api-key-error) Check that your key starts with `nsp_` and is correctly copied from the [**Pro dashboard**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/pro/dashboard). Keys are per-account, not per-project. h3. [Tools not appearing](#tools-not-appearing) 1. Restart your AI assistant after config changes 2. Verify the server URL is exactly `https://nuxtseo.com/mcp/pro` 3. Check your client's MCP logs for connection errors h3. ["No verified site found"](#no-verified-site-found) Run `init_site` first to register your site: ``` Set up https://mysite.com for SEO analysis ``` h2. [Free vs Pro Tools](#free-vs-pro-tools) | **Feature** | **Free MCP** | **Pro MCP** | | --- | --- | --- | | Module docs | ✓ | ✓ | | Page analysis | — | ✓ | | Schema.org generation | — | ✓ | | OG image templates | — | ✓ | | Keyword research | — | ✓ | | SERP analysis | — | ✓ | | Content generation | — | ✓ | The [**free MCP server**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo/guides/mcp) provides documentation access without an API key. Pro tools require a license. h2. [Next Steps](#next-steps) - [**Dev Assist Tools**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/dev-assist-tools) — Analyze pages and generate SEO code - [**Content Intelligence**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/content-intelligence) — Keyword research and SERP analysis - [**Social Signals**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/social-signals) — Validate market demand across GitHub and Reddit - [**Domain Availability**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/domain-tools) — Check domain registration status - [**Traffic Analysis**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/traffic-analysis) — Estimate competitor organic traffic - [**Prompts**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/prompts) — Pre-built workflows for content creation [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/installation.md) **Did this page help you? ** [**Pro MCP** MCP server with AI-powered SEO tools for Claude, Cursor, and other LLMs. Analyze pages, research keywords, generate schema.org and OG images.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp) [**Page Analysis** MCP tools for AI-powered SEO analysis. Scan Vue files for issues, generate schema.org code, create OG image templates—through any MCP client.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/installation/docs/nuxt-seo-pro/mcp/dev-assist-tools) **On this page** - [Configuration](#configuration) - [Initialize Your Site](#initialize-your-site) - [Sample Queries](#sample-queries) - [Troubleshooting](#troubleshooting) - [Free vs Pro Tools](#free-vs-pro-tools) - [Next Steps](#next-steps) --- ### Pro Mcp · Nuxt SEO Source: https://nuxtseo.com/admin/pro-mcp Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/pro-mcp/auth/github) --- ### Introduction · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/getting-started/introduction Description: Learn about the motivation behind Nuxt Site Config and a bit about how it works. **Getting Started** h1. **Introduction** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply linking opportunities (#73) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/73). [Copy for LLMs h2. [Background](#background) Site config aims to be two things: - A single source of truth for site config, for end-users and module authors. Site config can be considered config that is commonly used amongst modules but is not supported by the Nuxt core. - A set of APIs for working with "writeable runtime config", for end-users and module authors. h2. [Features](#features) - 😌 Zero-config, best practice site config defaults - 🎨 Site config from any source: Nuxt Config, Environment Variables or Programmatically - 🚀 Powerful and runtime agnostic APIs for module authors [`useSiteConfig`](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/site-config/api/use-site-config), [`createSitePathResolver`](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/site-config/api/create-site-path-resolver), `withSiteUrl`, [`getNitroOrigin`](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/site-config/api/get-nitro-origin), etc - 🤖 Ledger capabilities - 🤝 Integrates with [`@nuxtjs/i18n`](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/site-config/guides/i18n) h2. [Site Config Examples](#site-config-examples) h3. [`url`](#url) A canonical site URL is important for [**SEO**](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/nuxt-seo/getting-started/introduction) and performance h3. [`env`](#env) The environment the site is running in, importing so we can disable indexing for non-production environments. See [**this issue**](https://github.com/nuxt/nuxt/issues/19819) on why we can't use `process.env.NODE_ENV`. h3. [`indexable`](#indexable) Can the site be indexed by search engines? Used by [**robots**](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/robots/getting-started/introduction) and [**sitemap**](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/sitemap/getting-started/introduction) modules. Sometimes we have production sites that we don't want to be indexed. h3. [`name`](#name) The name of the site is often used in meta tags and other SEO related tags h3. [`trailingSlash`](#trailingslash) Trailing slashes are important for SEO and performance. h2. [What's the problem?](#whats-the-problem) Not having a single source of truth for site config can be difficult to maintain and error-prone, for end-users and module authors. Requiring a lot of duplication and boilerplate code to support the same features across modules. Nuxt Site Config aims to unify the experience of site config with a set of powerful and flexible APIs for end-users and module authors. h2. [Can't we just use the Request URL or module config?](#cant-we-just-use-the-request-url-or-module-config) Nuxt itself provides a great SSR utility for getting the site URL from the request headers at runtime. However, this has two major drawbacks: - It's not available at build time or when prerendering - When used for SEO content, it can cause duplicate content issues if the URL is not the canonical URL (e.g. `www.example.com` and `example.com`) h2. [Can't we just use `site` on runtime config?](#cant-we-just-use-site-on-runtime-config) Yes. In fact, this module uses `site` on the runtime config under the hood. It aims to keep all these in sync, resulting in a single source of truth for site config. h2. [How does it work?](#how-does-it-work) See [**How it works**](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/site-config/guides/how-it-works) for more details. h2. [End goal](#end-goal) We should be able to spin up multi-tenant or multi-lingual Nuxt app with minimal effort, and Nuxt modules should just work, without any additional configuration. This is quite far off, but it sets a good direction for the module. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/0.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/site-config/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get started with Nuxt Site Config by installing the dependency to your project.](https://nuxtseo.com/docs/site-config/getting-started/introduction/docs/site-config/getting-started/installation) **On this page** - [Background](#background) - [Features](#features) - [Site Config Examples](#site-config-examples) - [What's the problem?](#whats-the-problem) - [Can't we just use the Request URL or module config?](#cant-we-just-use-the-request-url-or-module-config) - [Can't we just use site on runtime config?](#cant-we-just-use-site-on-runtime-config) - [How does it work?](#how-does-it-work) - [End goal](#end-goal) --- ### Waitlist · Nuxt SEO Source: https://nuxtseo.com/admin/waitlist Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/waitlist/auth/github) --- ### Dataforseo · Nuxt SEO Source: https://nuxtseo.com/admin/dataforseo Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/dataforseo/auth/github) --- ### Tools · Nuxt SEO Source: https://nuxtseo.com/admin/tools Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/tools/auth/github) --- ### Social Sharing Meta Tags in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph Description: Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord. h1. **Social Sharing Meta Tags in Nuxt** Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Nov 5, 2024** Updated **Dec 17, 2025** **What you'll learn** - OG tags control link previews on Facebook, LinkedIn, Discord, Slack - Images must be absolute HTTPS URLs, minimum 1200x630px - Nuxt OG Image module generates images automatically at build time Social platforms use Open Graph (OG) and Twitter Card meta tags to generate link previews. Without them, shared links appear as plain text. Nuxt renders your meta tags on the server by default, so social crawlers receive them in the initial HTML response. no extra configuration needed. ``` ``` h2. [Quick Tips](#quick-tips) 1. **Images matter**: Posts with optimized OG images see [**40-60% higher click-through rates**](https://nogood.io/blog/open-graph-seo/). Images must be absolute URLs, minimum 1200x630px. 2. **Social titles can differ**: Your `og:title` doesn't have to match your [**page title**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph/learn-seo/nuxt/mastering-meta/titles). Use casual, provocative language for social. it's not search. 3. **Always set `twitterCard`**: X/Twitter has no fallback for card type. Skip it and you get plain text. h2. [Open Graph Tags](#open-graph-tags) Use [`useSeoMeta()`](https://unhead.unjs.io/usage/composables/use-seo-meta) for OG tags. it handles the `property` attribute automatically. h3. [Core Tags](#core-tags) ``` useSeoMeta({ ogTitle: 'Hey! Open graph images are important.', ogDescription: 'But who reads the description anyway?', ogImage: 'https://mysite.com/og.png', // must be absolute URL ogUrl: 'https://mysite.com/products/item', // canonical URL ogType: 'website' // or 'article', 'product', etc. }) ``` h3. [Image Requirements](#image-requirements) | **Spec** | **Value** | | --- | --- | | Minimum size | 1200x630px (1.91:1 ratio) | | Max file size | 5MB (1MB recommended) | | Formats | JPG, PNG, WebP, GIF | | URL | Absolute HTTPS | Include dimensions for faster rendering: ``` useSeoMeta({ ogImage: 'https://mysite.com/og-images/preview.jpg', ogImageAlt: 'Product preview showing 3 stacked boxes', ogImageWidth: 1200, ogImageHeight: 630, ogImageType: 'image/jpeg' }) ``` h3. [Article Metadata](#article-metadata) For blog posts, add article-specific tags: ``` useSeoMeta({ ogType: 'article', articleAuthor: ['Harlan Wilton'], articleSection: 'SEO Tutorials', articleTag: ['nuxt', 'seo', 'meta-tags'], articlePublishedTime: '2024-11-05T00:00:00Z', articleModifiedTime: '2025-12-17T00:00:00Z' }) ``` h2. [Twitter/X Cards](#twitterx-cards) X requires its own `twitter:*` meta tags. Without `twitter:card`, your links appear as plain text. no image, no description. h3. [Card Types](#card-types) **summary_large_image** . Full-width image. Use this for most pages. ``` useSeoMeta({ twitterCard: 'summary_large_image', twitterImage: '/preview.jpg' // 2:1 ratio, min 300x157px }) ``` **summary** . Small square thumbnail. For compact previews. ``` useSeoMeta({ twitterCard: 'summary', twitterImage: '/icon.jpg' // 1:1 ratio, min 144x144px }) ``` **player** and **app** cards require [**approval from X**](https://developer.x.com/en/docs/x-for-websites/cards/overview/player-card). h3. [X Fallbacks](#x-fallbacks) X [**falls back to Open Graph**](https://developer.x.com/en/docs/x-for-websites/cards/overview/markup) when `twitter:*` equivalents are missing: | **Twitter Tag** | **Fallback** | | --- | --- | | `twitter:title` | `og:title` | | `twitter:description` | `og:description` | | `twitter:image` | `og:image` | `twitter:card` has **no fallback**. skip it and you get no card. Minimal setup using fallbacks: ``` useSeoMeta({ twitterCard: 'summary_large_image', // required ogTitle: 'My page', ogImage: 'https://mysite.com/image.jpg' }) ``` h3. [Optional Twitter Tags](#optional-twitter-tags) ``` useSeoMeta({ twitterSite: '@yourhandle', // Site's X handle twitterCreator: '@authorhandle', // Author's handle twitterImageAlt: 'Alt text' // 420 chars max }) ``` h2. [Slack & Discord](#slack-discord) Both platforms read Open Graph tags. No platform-specific tags needed. For Slack-specific quirks, see the [**Slack Unfurl guide**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph/learn-seo/nuxt/mastering-meta/slack). ``` useSeoMeta({ ogTitle: 'Dashboard Analytics', ogDescription: '847 active users, 12.4% conversion rate', ogImage: 'https://mysite.com/og/dashboard.png' }) ``` Slack also reads Twitter Card tags as fallback. Discord supports animated GIFs and shows real-time previews. h3. [Slack-Specific Behavior](#slack-specific-behavior) - Cache: ~30 minutes globally - Ignores: `og:type`, `og:locale`, `article:*` tags, Schema.org - No redirect following for images. use direct URLs h2. [Platform Cache Times](#platform-cache-times) | **Platform** | **Cache Duration** | **Clear Cache** | | --- | --- | --- | | Facebook | 14-30 days | [**Sharing Debugger**](https://developers.facebook.com/tools/debug/) | | LinkedIn | 7 days | [**Post Inspector**](https://www.linkedin.com/post-inspector/) | | X/Twitter | ~7 days | Add `?v=2` to URL | | Slack | ~30 minutes | Add `?v=2` to URL | | Discord | Real-time | No cache | h2. [Dynamic Meta Tags](#dynamic-meta-tags) Fetch your data with `useFetch()` or `useAsyncData()`. Nuxt handles server-side rendering automatically. ``` ``` h2. [Automated OG Image Generation](#automated-og-image-generation) Creating OG images manually for every page is tedious. The Nuxt OG Image module generates them automatically: [OG Image **v5.1.13** 2.9M 495 Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph/docs/og-image/getting-started/introduction) ``` ``` The module generates images at build time or on-demand, with zero runtime overhead. It supports: - Vue SFCs as templates - Tailwind CSS styling - Custom fonts - Dynamic text and images - Edge function generation [**Read the OG Image docs**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph/docs/og-image/getting-started/introduction) for setup and advanced patterns. h2. [Testing Tools](#testing-tools) 1. [**Facebook Sharing Debugger**](https://developers.facebook.com/tools/debug/) . Also validates OG tags for Slack 2. [**LinkedIn Post Inspector**](https://www.linkedin.com/post-inspector/) 3. X/Twitter . Compose a tweet (don't post) to see preview 4. Discord . Paste link for real-time preview h2. [Troubleshooting](#troubleshooting) **Image not showing** - URL must be absolute HTTPS (not relative) - File under 5MB - Image returns 200 (not redirect) - Not blocked by robots.txt **Wrong title/description** - Platform cached old version. add `?v=2` to URL - For X: check you're setting `twitterCard` **Card not appearing at all** - Missing `twitterCard` for X (no fallback) - Page requires auth (crawlers can't log in) h2. [All-in-One Solution](#all-in-one-solution) The Nuxt SEO module includes OG Image generation, plus sitemaps, robots.txt, and Schema.org: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph/docs/nuxt-seo/getting-started/introduction) Configure social sharing site-wide in `nuxt.config.ts`: ``` export default defineNuxtConfig({ site: { url: 'https://mysite.com', name: 'My Site', defaultLocale: 'en' }, ogImage: { enabled: true, fonts: ['Inter:400', 'Inter:700'] } }) ``` Then override per-page as needed with `useSeoMeta()` or `defineOgImageComponent()`. --- [**Meta Description** Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Nuxt using composables and let your content do the heavy lifting.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph/learn-seo/nuxt/mastering-meta/descriptions) [**Rich Results** Get rich results in Google search with Schema.org structured data. Learn which types still work after Google's 2023 FAQ/HowTo removals.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/open-graph/learn-seo/nuxt/mastering-meta/rich-results) **On this page** - [Quick Tips](#quick-tips) - [Open Graph Tags](#open-graph-tags) - [Twitter/X Cards](#twitterx-cards) - [Slack & Discord](#slack-discord) - [Platform Cache Times](#platform-cache-times) - [Dynamic Meta Tags](#dynamic-meta-tags) - [Automated OG Image Generation](#automated-og-image-generation) - [Testing Tools](#testing-tools) - [Troubleshooting](#troubleshooting) - [All-in-One Solution](#all-in-one-solution) --- ### Social Sharing Meta Tags in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing Description: Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord. h1. **Social Sharing Meta Tags in Vue** Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Nov 5, 2024** Updated **Dec 17, 2025** **What you'll learn** - OG tags control social previews on Facebook, LinkedIn, Slack, Discord - Images must be absolute HTTPS URLs (not relative paths) - Twitter/X requires `twitterCard`. there's no fallback from OG tags Social platforms use Open Graph (OG) and Twitter Card meta tags to generate link previews. Without them, shared links appear as plain text. **SSR required**: Social crawlers don't run JavaScript. Your meta tags must be in the initial HTML response, which means [**server-side rendering**](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing/learn-seo/vue/routes-and-rendering/rendering) or prerendering. ``` ``` h2. [Quick Tips](#quick-tips) 1. **Images matter**: Posts with optimized OG images see [**40-60% higher click-through rates**](https://nogood.io/blog/open-graph-seo/). Images must be absolute URLs, minimum 1200x630px. 2. **Social titles can differ**: Your `og:title` doesn't have to match your [**page title**](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing/learn-seo/vue/mastering-meta/titles). Use casual, provocative language for social. it's not search. 3. **Always set `twitterCard`**: X/Twitter has no fallback for card type. Skip it and you get plain text. h2. [Open Graph Tags](#open-graph-tags) Use [`useSeoMeta()`](https://unhead.unjs.io/usage/composables/use-seo-meta) for OG tags. it handles the `property` attribute automatically. h3. [Core Tags](#core-tags) ``` useSeoMeta({ ogTitle: 'Hey! Open graph images are important.', ogDescription: 'But who reads the description anyway?', ogImage: 'https://mysite.com/og.png', // must be absolute URL ogUrl: 'https://mysite.com/products/item', // canonical URL ogType: 'website' // or 'article', 'product', etc. }) ``` h3. [Image Requirements](#image-requirements) | **Spec** | **Value** | | --- | --- | | Minimum size | 1200x630px (1.91:1 ratio) | | Max file size | 5MB (1MB recommended) | | Formats | JPG, PNG, WebP, GIF | | URL | Absolute HTTPS | Include dimensions for faster rendering: ``` useSeoMeta({ ogImage: 'https://mysite.com/og-images/preview.jpg', ogImageAlt: 'Product preview showing 3 stacked boxes', ogImageWidth: 1200, ogImageHeight: 630, ogImageType: 'image/jpeg' }) ``` OG images must use absolute URLs with HTTPS. Relative paths like `/images/og.png` won't work. social crawlers don't know your domain. Always include the full URL: `https://mysite.com/images/og.png` h3. [Article Metadata](#article-metadata) For blog posts, add article-specific tags: ``` useSeoMeta({ ogType: 'article', articleAuthor: ['Harlan Wilton'], articleSection: 'SEO Tutorials', articleTag: ['vue', 'seo', 'meta-tags'], articlePublishedTime: '2024-11-05T00:00:00Z', articleModifiedTime: '2025-12-17T00:00:00Z' }) ``` h2. [Twitter/X Cards](#twitterx-cards) X requires its own `twitter:*` meta tags. Without `twitter:card`, your links appear as plain text. no image, no description. h3. [Card Types](#card-types) **summary_large_image** . Full-width image. Use this for most pages. ``` useSeoMeta({ twitterCard: 'summary_large_image', twitterImage: '/preview.jpg' // 2:1 ratio, min 300x157px }) ``` **summary** . Small square thumbnail. For compact previews. ``` useSeoMeta({ twitterCard: 'summary', twitterImage: '/icon.jpg' // 1:1 ratio, min 144x144px }) ``` **player** and **app** cards require [**approval from X**](https://developer.x.com/en/docs/x-for-websites/cards/overview/player-card). h3. [X Fallbacks](#x-fallbacks) X [**falls back to Open Graph**](https://developer.x.com/en/docs/x-for-websites/cards/overview/markup) when `twitter:*` equivalents are missing: | **Twitter Tag** | **Fallback** | | --- | --- | | `twitter:title` | `og:title` | | `twitter:description` | `og:description` | | `twitter:image` | `og:image` | `twitter:card` has **no fallback**. skip it and you get no card. Minimal setup using fallbacks: ``` useSeoMeta({ twitterCard: 'summary_large_image', // required ogTitle: 'My page', ogImage: 'https://mysite.com/image.jpg' }) ``` h3. [Optional Twitter Tags](#optional-twitter-tags) ``` useSeoMeta({ twitterSite: '@yourhandle', // Site's X handle twitterCreator: '@authorhandle', // Author's handle twitterImageAlt: 'Alt text' // 420 chars max }) ``` h2. [Slack & Discord](#slack-discord) Both platforms read Open Graph tags. No platform-specific tags needed. ``` useSeoMeta({ ogTitle: 'Dashboard Analytics', ogDescription: '847 active users, 12.4% conversion rate', ogImage: 'https://mysite.com/og/dashboard.png' }) ``` Slack also reads Twitter Card tags as fallback. Discord supports animated GIFs and shows real-time previews. h3. [Slack-Specific Behavior](#slack-specific-behavior) - Cache: ~30 minutes globally - Ignores: `og:type`, `og:locale`, `article:*` tags, Schema.org - No redirect following for images. use direct URLs h2. [Platform Cache Times](#platform-cache-times) | **Platform** | **Cache Duration** | **Clear Cache** | | --- | --- | --- | | Facebook | 14-30 days | [**Sharing Debugger**](https://developers.facebook.com/tools/debug/) | | LinkedIn | 7 days | [**Post Inspector**](https://www.linkedin.com/post-inspector/) | | X/Twitter | ~7 days | Add `?v=2` to URL | | Slack | ~30 minutes | Add `?v=2` to URL | | Discord | Real-time | No cache | h2. [Dynamic Meta Tags](#dynamic-meta-tags) Data must be available during server render. `onMounted` runs too late for crawlers. ``` // entry-server.ts export async function render(url: string) { const product = await fetch(\`https://api.mysite.com/product/${url}\`).then(r => r.json()) const app = createSSRApp(App) app.provide('product', product) return { app, product } } ``` ``` ``` h2. [Testing Tools](#testing-tools) 1. [**Facebook Sharing Debugger**](https://developers.facebook.com/tools/debug/) . Also validates OG tags for Slack 2. [**LinkedIn Post Inspector**](https://www.linkedin.com/post-inspector/) 3. X/Twitter . Compose a tweet (don't post) to see preview 4. Discord . Paste link for real-time preview h2. [Troubleshooting](#troubleshooting) **Image not showing** - URL must be absolute HTTPS (not relative) - File under 5MB - Image returns 200 (not redirect) - Not blocked by robots.txt **Wrong title/description** - Platform cached old version. add `?v=2` to URL - For X: check you're setting `twitterCard` **Card not appearing at all** - Missing `twitterCard` for X (no fallback) - Page requires auth (crawlers can't log in) h2. [OG Image Tools](#og-image-tools) Creating OG images manually is tedious: - [**OG Image Playground**](https://og-playground.vercel.app/) . Design and export with code - [**x-satori**](https://github.com/Zhengqbbb/x-satori) . Vue SFCs + Tailwind to generate at build time h2. [Using Nuxt?](#using-nuxt) [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing/docs/nuxt-seo/getting-started/introduction) handles social sharing automatically with the OG Image module. [**Learn more about Social Sharing in Nuxt →**](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing/learn-seo/nuxt/mastering-meta/open-graph) --- [**Meta Description** Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Vue using composables and let your content do the heavy lifting.](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing/learn-seo/vue/mastering-meta/descriptions) [**Schema.org** Learn how to implement Schema.org structured data in Vue using Unhead. Get rich results in Google search with type-safe JSON-LD markup.](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing/learn-seo/vue/mastering-meta/schema-org) **On this page** - [Quick Tips](#quick-tips) - [Open Graph Tags](#open-graph-tags) - [Twitter/X Cards](#twitterx-cards) - [Slack & Discord](#slack-discord) - [Platform Cache Times](#platform-cache-times) - [Dynamic Meta Tags](#dynamic-meta-tags) - [Testing Tools](#testing-tools) - [Troubleshooting](#troubleshooting) - [OG Image Tools](#og-image-tools) - [Using Nuxt?](#using-nuxt) --- ### Meta Descriptions in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions Description: Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Vue using composables and let your content do the heavy lifting. h1. **Meta Descriptions in Vue** Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Vue using composables and let your content do the heavy lifting. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)7 mins read Published **Nov 5, 2024** Updated **Dec 17, 2025** **What you'll learn** - Google rewrites 62-70% of descriptions. focus on page content quality - Keep under 160 characters, front-load value for mobile truncation - Use `useSeoMeta()` for type-safe description management Meta descriptions don't directly impact rankings. They affect click-through rates by giving users context about your page. ``` ``` Unlike [**page titles**](https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions/learn-seo/vue/mastering-meta/titles), descriptions have more flexibility but require careful crafting to be effective. h3. [Quick Facts](#quick-facts) 1. **Google rewrites most of them**: Studies show 62-70% get rewritten ([**Ahrefs**](https://ahrefs.com/blog/meta-description-study/), [**Search Engine Journal**](https://www.searchenginejournal.com/google-rewrites-meta-descriptions-over-70-of-the-time/382140/)). Good page content matters more. 2. **Length varies by device**: Desktop shows ~155-160 characters, mobile ~120. [**Focus on front-loading value**](https://searchengineland.com/seo-meta-descriptions-everything-to-know-447910) rather than hitting a character count. 3. **Template descriptions are lazy**: "Buy {product} at great prices" won't differentiate you. Summarize using AI tools if you're short on time. h2. [Setting Meta Descriptions in Vue](#setting-meta-descriptions-in-vue) [**Unhead**](https://unhead.unjs.io/) provides type-safe composables for managing head tags. The `useSeoMeta` composable is the cleanest way to set descriptions: ``` useSeoMeta({ description: 'Your description here' }) ``` Alternatively, `useHead()` gives you more control: ``` useHead({ meta: [ { name: 'description', content: 'Your description here' } ] }) ``` h2. [Dynamic Meta Descriptions](#dynamic-meta-descriptions) Use refs for reactive descriptions that update when data changes: ``` ``` h2. [Open Graph Description `'og:description'`](#open-graph-description-ogdescription) Facebook, LinkedIn, Discord, Slack, and WhatsApp all [**use **`og:description`](https://w3things.com/blog/open-graph-meta-tags/) for link previews. X (Twitter) falls back to `og:description` when `twitter:description` isn't set. so you can skip the Twitter-specific tag. ``` useSeoMeta({ description: 'Technical description for search', ogDescription: 'More casual description for social sharing', // X falls back to ogDescription, no need for twitterDescription }) ``` **Testing tools:** - [**Facebook Sharing Debugger**](https://developers.facebook.com/tools/debug/) - [**LinkedIn Post Inspector**](https://www.linkedin.com/post-inspector/) - [**OpenGraph.xyz**](https://www.opengraph.xyz/) (tests Facebook, LinkedIn, Discord, X) h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about Meta Descriptions in Nuxt →**](https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions/learn-seo/nuxt/mastering-meta/descriptions) **Quick Check** **Should you use the same meta description on multiple pages?** - `Yes, for consistency` - Creates duplicate content signals and wastes the opportunity to describe each page - `No, each page needs a unique description` - Correct! Unique descriptions help differentiate pages and improve CTR - `Only for similar pages` - Even similar pages should have distinct descriptions highlighting their differences --- [**Titles** Set dynamic page titles in Vue 3 with useHead. Learn title templates, reactive titles, and SSR patterns that Google indexes correctly.](https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions/learn-seo/vue/mastering-meta/titles) [**Social Sharing** Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord.](https://nuxtseo.com/learn-seo/vue/mastering-meta/descriptions/learn-seo/vue/mastering-meta/social-sharing) **On this page** - [Quick Facts](#quick-facts) - [Setting Meta Descriptions in Vue](#setting-meta-descriptions-in-vue) - [Dynamic Meta Descriptions](#dynamic-meta-descriptions) - [Open Graph Description 'og:description'](#open-graph-description-ogdescription) - [Using Nuxt?](#using-nuxt) --- ### How to Set Page Titles in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/mastering-meta/titles Description: Set dynamic page titles in Vue 3 with useHead. Learn title templates, reactive titles, and SSR patterns that Google indexes correctly. h1. **How to Set Page Titles in Vue** Set dynamic page titles in Vue 3 with useHead. Learn title templates, reactive titles, and SSR patterns that Google indexes correctly. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Oct 24, 2024** Updated **Dec 17, 2025** **What you'll learn** - Use `useHead()` instead of `document.title` for SSR compatibility - Add a title template to append your site name consistently - Keep titles under 60 characters to avoid truncation - Set `og:title` separately for social sharing Page titles appear in browser tabs and as the clickable headline in search results. Google uses your `` tag [**over 80% of the time**](https://developers.google.com/search/docs/appearance/title-link) when generating title links. though [**studies show**](https://zyppy.com/seo/title-tags/google-title-rewrite-study/) it rewrites 61-76% of titles to some degree. ``` <head> <title>Mastering Titles in Vue · Vue SEO ``` Setting titles in Vue 3 requires a head manager like [**Unhead**](https://unhead.unjs.io/) since `document.title` won't work during server-side rendering. h2. [Quick Reference](#quick-reference) ``` // Basic title useHead({ title: 'Home' }) // With template (adds site name) useHead({ title: 'Home', titleTemplate: '%s | MySite' }) // Reactive title from data const post = ref({ title: 'Loading...' }) useHead({ title: () => post.value.title }) // SEO-focused (includes og:title) useSeoMeta({ title: 'Home', ogTitle: 'Home | MySite' }) ``` h2. [Why Not `document.title`?](#why-not-documenttitle) You might try setting titles directly: ``` // ❌ Breaks SSR, may not be indexed document.title = 'Home' ``` This fails during server-side rendering. the title won't exist in the initial HTML response. Search engines render JavaScript but [**may not wait**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) for client-side updates. Using `document.title` in a Vue SPA means search engines may index your pages with missing or incorrect titles. Always use a head manager like Unhead for SEO-critical metadata. Use [**Unhead**](https://unhead.unjs.io/) instead. It handles both SSR and client-side updates. For Google's official guidance, see [**Influencing your title links**](https://developers.google.com/search/docs/appearance/title-link). h2. [Setting Titles with `useHead()`](#setting-titles-with-usehead) The [`useHead()`](https://unhead.unjs.io/usage/composables/use-head) composable sets titles that work in SSR and client-side navigation: input.vue ``` ``` output.html ``` Home ``` Works in any component. You can set other head tags in the same call: ``` ``` h3. [Reactive Titles](#reactive-titles) Unhead accepts refs, reactive objects, and computed values. Pass the reactive reference directly. don't destructure it with `.value`. ``` useHead({ title: myTitle.value // ❌ Loses reactivity }) useHead({ title: myTitle // ✅ Stays reactive }) ``` Computed getter syntax works for derived titles: ``` const post = ref({ title: 'Loading...' }) useHead({ title: () => post.value.title // Updates when post changes }) ``` h3. [SSR and SEO](#ssr-and-seo) Fetch data before render, not in `onMounted()`. Client-only fetches mean search engines see your loading state: ``` ``` Use SSR-compatible data fetching (Vue's `onServerPrefetch()` or framework solutions like Nuxt's `useFetch()`). h2. [Title Templates](#title-templates) Most sites append a site name to titles for brand recognition. [**Google recommends**](https://developers.google.com/search/docs/appearance/title-link#page-titles) adding your site name with a delimiter: ``` Home | MySite ``` Use `titleTemplate` with a [**title template**](https://unhead.unjs.io/usage/guides/title-template): input.vue ``` ``` output.html ``` Home | MySite ``` The `%s` token gets replaced with your page title (or empty string if none set). h3. [Disabling the Template](#disabling-the-template) Override the template for specific pages by passing `null`: input.vue ``` ``` output.html ``` Home ``` h2. [Template Params](#template-params) For dynamic site names or separators, use the [**Template Params plugin**](https://unhead.unjs.io/usage/guides/template-params): ``` import { TemplateParamsPlugin } from '@unhead/vue' const head = injectHead() head.use(TemplateParamsPlugin) ``` Then use custom params: input.vue ``` ``` output.html ``` Home · MySite ``` Common separators: `|` `-` `.` `•` `·` Template params work in meta tags too: ``` useHead({ templateParams: { siteName: 'MyApp' }, title: 'Home', meta: [ { name: 'description', content: 'Welcome to %siteName' }, { property: 'og:title', content: 'Home | %siteName' } ] }) ``` h2. [Social Share Titles](#social-share-titles) Social platforms use `og:title` and `twitter:title` meta tags. Use [`useSeoMeta()`](https://unhead.unjs.io/usage/composables/use-seo-meta) to set these: input.vue ``` ``` output.html ``` Why you should eat more broccoli | Health Tips ``` Twitter/X falls back to `og:title` if `twitter:title` isn't set. h2. [Title Length](#title-length) Google displays roughly 50-60 characters before truncating. [**Studies show**](https://zyppy.com/title-tags/meta-title-tag-length/) titles between 51-60 characters have the lowest rewrite rates (39-42%). Longer titles still get indexed. Google just truncates the display. Front-load important keywords since users may only see the first 50 characters. h2. [Vue Router Integration](#vue-router-integration) Set titles from route meta for centralized title management: ``` // router.ts const routes = [ { path: '/', component: Home, meta: { title: 'Home' } }, { path: '/about', component: About, meta: { title: 'About Us' } }, { path: '/blog/:slug', component: BlogPost, meta: { title: 'Blog' } } ] ``` Use a navigation guard to apply titles on route change: ``` router.afterEach((to) => { const title = to.meta.title as string if (title) { useHead({ title }) } }) ``` For dynamic routes, override in the component: ``` ``` h3. [Navigation Guard Patterns](#navigation-guard-patterns) Set a global title template in your guard: ``` router.afterEach((to) => { useHead({ title: to.meta.title as string || 'MySite', titleTemplate: '%s | MySite' }) }) ``` Handle nested routes by walking matched routes: ``` router.afterEach((to) => { // Find deepest route with a title const title = [...to.matched] .reverse() .find(r => r.meta.title) ?.meta .title as string if (title) useHead({ title }) }) ``` Using Nuxt? Check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/mastering-meta/titles/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about Page Titles in Nuxt →**](https://nuxtseo.com/learn-seo/vue/mastering-meta/titles/learn-seo/nuxt/mastering-meta/titles) h2. [Test Your Knowledge](#test-your-knowledge) **Quick Check** **Which approach correctly sets a reactive title that updates when data changes?** - `useHead({ title: myTitle.value })` - Loses reactivity - `useHead({ title: myTitle })` - Correct! Stays reactive - `document.title = myTitle.value` - Breaks SSR - `useHead({ title: () => myTitle })` - Unnecessary wrapper h2. [Checklist](#checklist) **Checklist** - Use `useHead()` or `useSeoMeta()` instead of `document.title` - Set up a title template with your site name - Keep titles under 60 characters - Set `og:title` for social sharing - Fetch data server-side for dynamic titles - Test titles render correctly in SSR h2. [Related Resources](#related-resources) --- [**Mastering Meta** Set up meta tags in Vue 3 with Unhead. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org with SSR patterns.](https://nuxtseo.com/learn-seo/vue/mastering-meta/titles/learn-seo/vue/mastering-meta) [**Meta Description** Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Vue using composables and let your content do the heavy lifting.](https://nuxtseo.com/learn-seo/vue/mastering-meta/titles/learn-seo/vue/mastering-meta/descriptions) **On this page** - [Quick Reference](#quick-reference) - [Why Not document.title?](#why-not-documenttitle) - [Setting Titles with useHead()](#setting-titles-with-usehead) - [Title Templates](#title-templates) - [Template Params](#template-params) - [Social Share Titles](#social-share-titles) - [Title Length](#title-length) - [Vue Router Integration](#vue-router-integration) - [Test Your Knowledge](#test-your-knowledge) - [Checklist](#checklist) - [Related Resources](#related-resources) --- ### Sitemaps in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps Description: Generate sitemaps for Nuxt with the @nuxtjs/sitemap module or server routes for dynamic content. h1. **Sitemaps in Nuxt** Generate sitemaps for Nuxt with the @nuxtjs/sitemap module or server routes for dynamic content. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Jan 4, 2026** **What you'll learn** - Sitemaps limited to 50,000 URLs or 50MB. Use sitemap index for larger sites - Skip `changefreq` and `priority`. Google ignores them, only `lastmod` matters - Nuxt Sitemap module auto-generates from pages/ and handles large sites The `sitemap.xml` file helps search engines discover your pages. Google considers sites "small" if they have [**500 pages or fewer**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview). You likely need a sitemap if you exceed this, have new sites with few backlinks, or update content frequently. h2. [Automatic Generation](#automatic-generation) Nuxt generates sitemaps automatically with the Sitemap module: [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps/docs/sitemap/getting-started/introduction) Install and configure: ``` npx nuxi@latest module add sitemap ``` nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['@nuxtjs/sitemap'], site: { url: 'https://mysite.com' } }) ``` This creates a sitemap from your routes automatically. For static sites, it's generated at build time. For dynamic sites, it's generated on request. The module handles: - Route discovery from `pages/` directory - Dynamic routes with route rules - Sitemap indexes for large sites - Image and video sitemaps - News sitemaps - Multi-language sitemaps For full configuration options, see the [**Sitemap module docs**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps/docs/sitemap/getting-started/introduction). h2. [Static Sitemap](#static-sitemap) For small sites under 100 pages, create a static sitemap in your public directory: ``` public/ sitemap.xml ``` Add your URLs with proper formatting: ``` https://mysite.com/ 2024-11-03 https://mysite.com/about 2024-12-10 ``` Sitemaps are limited to [**50,000 URLs or 50MB uncompressed**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap). Use UTF-8 encoding and absolute URLs only. h2. [Dynamic Generation](#dynamic-generation) For sites with frequently changing content (e-commerce, blogs, news), generate sitemaps server-side with a Nitro route: server/routes/sitemap.xml.ts ``` export default defineEventHandler(async (event) => { const pages = await fetchAllPages() const urls = pages.map(page => \` https://mysite.com${page.path} ${page.updatedAt} \`).join('\n') const sitemap = \` ${urls} \` setHeader(event, 'Content-Type', 'application/xml') return sitemap }) ``` This runs on every request. For better performance, use the Sitemap module which caches and optimizes sitemap generation. h2. [XML Sitemap Tags](#xml-sitemap-tags) A basic sitemap uses these elements: ``` https://mysite.com/page 2024-11-03 ``` h3. [Required and Optional Tags](#required-and-optional-tags) | **Tag** | **Status** | **Google Uses?** | | --- | --- | --- | | `` | Required | Yes, the page URL | | `` | Recommended | Yes, [**if consistently accurate**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) | | `` | Skip | [**No, Google ignores this**](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping) | | `` | Skip | [**No, Google ignores this**](https://developers.google.com/search/blog/2014/10/best-practices-for-xml-sitemaps-rssatom) | Google only uses `` if it matches reality. If your page changed 7 years ago but you claim it updated yesterday, [**Google stops trusting your sitemap**](https://www.seocomponent.com/blog/ignore-priority-changefreq-fields-sitemap/). Don't bother with `` or ``. They increase file size without adding value. h2. [Sitemap Index for Large Sites](#sitemap-index-for-large-sites) If you exceed 50,000 URLs or 50MB, [**split your sitemap into multiple files**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps): sitemap-index.xml ``` https://mysite.com/sitemap-products.xml 2024-11-03 https://mysite.com/sitemap-blog.xml 2024-12-10 ``` Submit the index file to Google Search Console. It will crawl all referenced sitemaps. [**Maximize URLs per sitemap**](https://developers.google.com/search/blog/2014/10/best-practices-for-xml-sitemaps-rssatom) rather than creating many small files. The Sitemap module handles this automatically when your site exceeds limits. h2. [Specialized Sitemaps](#specialized-sitemaps) h3. [News Sitemap](#news-sitemap) News publishers should create [**separate news sitemaps**](https://fsidm.in/knowledge/seo/image-news-video-sitemaps/) with articles from the last 2 days: sitemap-news.xml ``` https://mysite.com/article Site Name en 2024-12-10T12:00:00+00:00 Article Title ``` Remove articles older than 2 days to keep the sitemap fresh. Don't create new sitemaps daily. Update the existing one. h3. [Image Sitemap](#image-sitemap) For galleries or image-heavy sites, add image metadata: sitemap.xml ``` https://mysite.com/page https://mysite.com/image.jpg Image Title ``` [**Image sitemaps help Google find images loaded via JavaScript**](https://www.docommunication.io/blog/sitemaps-explained) or not directly linked in HTML. h2. [Submit to Google Search Console](#submit-to-google-search-console) After generating your sitemap, [**submit it to Google Search Console**](https://support.google.com/webmasters/answer/7451001): 1. [**Verify site ownership**](https://seotesting.com/google-search-console/how-to-create-a-sitemap-for-google-search-console/) (DNS, HTML file upload, or Google Analytics) 2. Navigate to **Sitemaps** under **Indexing** 3. Enter your sitemap URL: `https://mysite.com/sitemap.xml` 4. Click **Submit** Google processes the sitemap and reports status: - **Success**: sitemap accepted, pages discovered - **Pending**: processing in progress - **Couldn't fetch**: URL wrong or blocked by robots.txt Check the Sitemaps report for coverage stats, indexing errors, and discovered URLs. h3. [Before Submitting](#before-submitting) Validate your sitemap: - XML syntax is valid (use an [**online validator**](https://www.xml-sitemaps.com/validate-xml-sitemap.html)) - All URLs return 200 status (not 404 or 500) - URLs match [**canonical URLs**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps/learn-seo/nuxt/controlling-crawlers/canonical-urls) exactly - `` dates are accurate - File uses UTF-8 encoding h3. [Alternative Submission](#alternative-submission) Reference your sitemap in `robots.txt`: public/robots.txt ``` User-agent: * Allow: / Sitemap: https://mysite.com/sitemap.xml ``` Google discovers this automatically when [**crawling your robots.txt**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps/learn-seo/nuxt/controlling-crawlers/robots-txt). --- [**Robots.txt** Robots.txt tells crawlers what they can access. Here's how to set it up in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps/learn-seo/nuxt/controlling-crawlers/robots-txt) [**Robot Meta Tag** Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Nuxt apps.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/sitemaps/learn-seo/nuxt/controlling-crawlers/meta-tags) **On this page** - [Automatic Generation](#automatic-generation) - [Static Sitemap](#static-sitemap) - [Dynamic Generation](#dynamic-generation) - [XML Sitemap Tags](#xml-sitemap-tags) - [Sitemap Index for Large Sites](#sitemap-index-for-large-sites) - [Specialized Sitemaps](#specialized-sitemaps) - [Submit to Google Search Console](#submit-to-google-search-console) --- ### Robots.txt in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt Description: Robots.txt tells crawlers what they can access. Here's how to set it up in Vue. h1. **Robots.txt in Vue** Robots.txt tells crawlers what they can access. Here's how to set it up in Vue. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - robots.txt is advisory. crawlers can ignore it, so never use it for security - Primary uses: crawl budget optimization and blocking AI training bots - Always include a sitemap reference in your robots.txt The `robots.txt` file controls which parts of your site crawlers can access. [**Officially adopted as RFC 9309**](https://datatracker.ietf.org/doc/html/rfc9309) in September 2022 after 28 years as a de facto standard, it's primarily used to [**manage crawl budget**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) on large sites and block AI training bots. Robots.txt is not a security mechanism. [**crawlers can ignore it**](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Robots_txt). For individual page control, use [**meta robots tags**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/controlling-crawlers/meta-tags) instead. h2. [Quick Setup](#quick-setup) To get started quickly with a static `robots.txt`, add the file in your public directory: ``` public/ robots.txt ``` Add your rules: robots.txt ``` h1. Allow all crawlers User-agent: * Disallow: h1. Optionally point to your sitemap Sitemap: https://mysite.com/sitemap.xml ``` h3. [Dynamic robots.txt](#dynamic-robotstxt) For environment-specific rules (e.g., blocking all crawlers in staging), generate `robots.txt` server-side: ``` import express from 'express' const app = express() app.get('/robots.txt', (req, res) => { const isDev = process.env.NODE_ENV !== 'production' const robots = isDev ? 'User-agent: *\nDisallow: /' : 'User-agent: *\nDisallow:\nSitemap: https://mysite.com/sitemap.xml' res.type('text/plain').send(robots) }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { if (req.path === '/robots.txt') { const isDev = process.env.NODE_ENV !== 'production' const robots = isDev ? 'User-agent: *\nDisallow: /' : 'User-agent: *\nDisallow:\nSitemap: https://mysite.com/sitemap.xml' return res.type('text/plain').send(robots) } next() }) ``` ``` import { defineEventHandler, setHeader } from 'h3' export default defineEventHandler((event) => { if (event.path === '/robots.txt') { const isDev = process.env.NODE_ENV !== 'production' const robots = isDev ? 'User-agent: *\nDisallow: /' : 'User-agent: *\nDisallow:\nSitemap: https://mysite.com/sitemap.xml' setHeader(event, 'Content-Type', 'text/plain') return robots } }) ``` h2. [Robots.txt Syntax](#robotstxt-syntax) The `robots.txt` file consists of directives grouped by user agent. Google [**uses the most specific matching rule**](https://developers.google.com/search/docs/crawling-indexing/robots/robots_txt) based on path length: robots.txt ``` h1. Define which crawler these rules apply to User-agent: * h1. Block access to specific paths Disallow: /admin h1. Allow access to specific paths (optional, more specific than Disallow) Allow: /admin/public h1. Point to your sitemap Sitemap: https://mysite.com/sitemap.xml ``` h3. [User-agent](#user-agent) The `User-agent` directive specifies which crawler the rules apply to: robots.txt ``` h1. All crawlers User-agent: * h1. Just Googlebot User-agent: Googlebot h1. Multiple specific crawlers User-agent: Googlebot User-agent: Bingbot Disallow: /private ``` Common crawler user agents: - [**Googlebot**](https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers): Google's search crawler (28% of all bot traffic in 2025) - [**Google-Extended**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/): Google's AI training crawler (separate from search) - [**GPTBot**](https://platform.openai.com/docs/bots/overview-of-openai-crawlers): OpenAI's AI training crawler (7.5% of bot traffic) - [**ClaudeBot**](https://www.playwire.com/blog/how-to-block-ai-bots-with-robotstxt-the-complete-publishers-guide): Anthropic's AI training crawler - [**CCBot**](https://commoncrawl.org/ccbot): Common Crawl's dataset builder (frequently blocked) - [**Bingbot**](https://ahrefs.com/seo/glossary/bingbot): Microsoft's search crawler - [**FacebookExternalHit**](https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/): Facebook's link preview crawler h3. [Allow / Disallow](#allow-disallow) The `Allow` and `Disallow` directives control path access: robots.txt ``` User-agent: * h1. Block all paths starting with /admin Disallow: /admin h1. Block a specific file Disallow: /private.html h1. Block files with specific extensions Disallow: /*.pdf$ h1. Block URL parameters Disallow: /*?* ``` Wildcards supported ([**RFC 9309**](https://datatracker.ietf.org/doc/html/rfc9309)): - `*` . matches zero or more characters - `$` . matches the end of the URL - Paths are case-sensitive and relative to domain root h3. [Sitemap](#sitemap) The `Sitemap` directive tells crawlers where to find your [**sitemap.xml**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/controlling-crawlers/sitemaps): robots.txt ``` Sitemap: https://mysite.com/sitemap.xml h1. Multiple sitemaps Sitemap: https://mysite.com/products-sitemap.xml Sitemap: https://mysite.com/blog-sitemap.xml ``` h3. [Crawl-Delay (Non-Standard)](#crawl-delay-non-standard) `Crawl-Delay` is not part of [**RFC 9309**](https://datatracker.ietf.org/doc/html/rfc9309). Google ignores it. Bing and Yandex support it: robots.txt ``` User-agent: Bingbot Crawl-delay: 10 # seconds between requests ``` For Google, [**crawl rate is managed in Search Console**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget). h2. [Security: Why robots.txt Fails](#security-why-robotstxt-fails) [**Robots.txt is not a security mechanism**](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Robots_txt). Malicious crawlers ignore it, and listing paths in `Disallow` [**reveals their location to attackers**](https://www.searchenginejournal.com/robots-txt-security-risks/289719/). **Common mistake:** ``` h1. ❌ Advertises your admin panel location User-agent: * Disallow: /admin Disallow: /wp-admin Disallow: /api/internal ``` Never use robots.txt to hide sensitive content. Listing paths in Disallow advertises their location to attackers, and malicious bots ignore robots.txt entirely. Use authentication and proper access controls instead. Use [**proper authentication**](https://developers.google.com/search/docs/crawling-indexing/block-indexing) instead. See our [**security guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/routes-and-rendering/security) for details. h2. [Crawling vs Indexing](#crawling-vs-indexing) Blocking a URL in `robots.txt` prevents crawling but [**doesn't prevent indexing**](https://developers.google.com/search/docs/crawling-indexing/robots/intro). If other sites link to the URL, Google can still index it without crawling, showing the URL with no snippet. To prevent indexing: - Use [`noindex`** meta tag**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/controlling-crawlers/meta-tags) (requires allowing crawl) - Use password protection or authentication - Return 404/410 status codes Don't block pages with `noindex` in `robots.txt`. Google can't see the tag if it can't crawl. h2. [Common Mistakes](#common-mistakes) h3. [1. Blocking JavaScript and CSS](#_1-blocking-javascript-and-css) [**Google needs JavaScript and CSS to render pages**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). Blocking them breaks indexing: robots.txt ``` h1. ❌ Prevents Google from rendering your Vue app User-agent: * Disallow: /assets/ Disallow: /*.js$ Disallow: /*.css$ ``` Vue apps are JavaScript-heavy. Never block `.js`, `.css`, or `/assets/` from Googlebot. h3. [2. Blocking Dev Sites in Production](#_2-blocking-dev-sites-in-production) Copy-pasting a dev `robots.txt` to production blocks all crawlers: robots.txt ``` h1. ❌ Accidentally left from staging User-agent: * Disallow: / ``` Use [**dynamic generation**](#dynamic-robots-txt) or environment checks to avoid this. h3. [3. Confusing robots.txt with noindex](#_3-confusing-robotstxt-with-noindex) Blocking pages doesn't remove them from search results. Use [`noindex`** meta tags**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/controlling-crawlers/meta-tags) for that. h2. [Testing Your robots.txt](#testing-your-robotstxt) 1. Check syntax: Visit `https://yoursite.com/robots.txt` to confirm it loads 2. [**Google Search Console robots.txt tester**](https://search.google.com/search-console/robots-txt) validates syntax and tests URLs 3. Verify crawlers can access: Check server logs for 200 status on `/robots.txt` h2. [Common Patterns](#common-patterns) h3. [Allow Everything (Default)](#allow-everything-default) ``` User-agent: * Disallow: ``` h3. [Block Everything](#block-everything) Useful for staging or development environments. ``` User-agent: * Disallow: / ``` See our [**security guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/routes-and-rendering/security) for more on environment protection. h3. [Block AI Training Crawlers](#block-ai-training-crawlers) [**GPTBot was the most blocked bot in 2024**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/), fully disallowed by 250 domains. Blocking AI training bots doesn't affect search rankings: ``` h1. Block AI model training (doesn't affect Google search) User-agent: GPTBot User-agent: ClaudeBot User-agent: CCBot User-agent: Google-Extended Disallow: / ``` `Google-Extended` is separate from `Googlebot`. blocking it won't hurt search visibility. h3. [AI Directives: Content-Usage & Content-Signal](#ai-directives-content-usage-content-signal) Two emerging standards let you express preferences about how AI systems use your content. without blocking crawlers entirely: - **[**Content-Usage**](https://ietf-wg-aipref.github.io/drafts/draft-ietf-aipref-vocab.html)** (IETF) . Uses `y`/`n` values for `train-ai` - **[**Content-Signal**](https://contentsignals.org/)** (Cloudflare) . Uses `yes`/`no` values for `search`, `ai-input`, `ai-train` ``` User-agent: * Allow: / h1. IETF aipref-vocab Content-Usage: train-ai=n h1. Cloudflare Content Signals Content-Signal: search=yes, ai-input=no, ai-train=no ``` This allows crawlers to access your content for search indexing while blocking AI training and RAG/grounding uses. Both can be used together for broader coverage. AI directives rely on voluntary compliance. Crawlers can ignore them. combine with User-agent blocks for stronger protection. h3. [Block Search, Allow Social Sharing](#block-search-allow-social-sharing) For private sites where you still want [**link previews**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/mastering-meta/social-sharing): ``` h1. Block search engines User-agent: Googlebot User-agent: Bingbot Disallow: / h1. Allow social link preview crawlers User-agent: facebookexternalhit User-agent: Twitterbot User-agent: Slackbot Allow: / ``` h3. [Optimize Crawl Budget for Large Sites](#optimize-crawl-budget-for-large-sites) If you have 10,000+ pages, [**block low-value URLs**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) to focus crawl budget on important content: ``` User-agent: * h1. Block internal search results Disallow: /search? h1. Block infinite scroll pagination Disallow: /*?page= h1. Block filtered/sorted product pages Disallow: /products?*sort= Disallow: /products?*filter= h1. Block print versions Disallow: /*/print ``` Sites under 1,000 pages don't need crawl budget optimization. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about robots.txt in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/nuxt/controlling-crawlers/robots-txt) **Quick Check** **Does robots.txt block malicious crawlers from accessing your site?** - `Yes, it blocks all crawlers` - Malicious crawlers ignore robots.txt entirely - `No, it's just a polite suggestion` - Correct! Robots.txt is advisory only. use authentication for real security - `Only if you use Disallow: /` - Malicious bots don't respect any robots.txt rules --- [**Controlling Crawlers** Manage how search engines crawl and index your Vue app. Configure robots.txt, sitemaps, canonical URLs, and redirects for better SEO.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/controlling-crawlers) [**Sitemaps** Generate sitemaps for Vue SPAs using Vite plugins, server-side rendering, or build-time generation.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/robots-txt/learn-seo/vue/controlling-crawlers/sitemaps) **On this page** - [Quick Setup](#quick-setup) - [Robots.txt Syntax](#robotstxt-syntax) - [Security: Why robots.txt Fails](#security-why-robotstxt-fails) - [Crawling vs Indexing](#crawling-vs-indexing) - [Common Mistakes](#common-mistakes) - [Testing Your robots.txt](#testing-your-robotstxt) - [Common Patterns](#common-patterns) - [Using Nuxt?](#using-nuxt) --- ### llms.txt for Nuxt Sites · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt Description: Help AI assistants understand your Nuxt documentation with the llms.txt standard. Configure nuxt-llms for automatic generation. h1. **llms.txt for Nuxt Sites** Help AI assistants understand your Nuxt documentation with the llms.txt standard. Configure nuxt-llms for automatic generation. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** The [**llms.txt standard**](https://llmstxt.org/) provides AI assistants with a concise summary of your site's content. Think of it as robots.txt for AI inference. Not blocking access, but guiding AI to your most useful documentation. [**Jeremy Howard**](https://github.com/AnswerDotAI/llms-txt) proposed the standard in September 2024. Unlike robots.txt, llms.txt uses Markdown and targets AI tools at inference time rather than crawlers at training time. Nuxt has dedicated support via [**nuxt-llms**](https://github.com/harlan-zw/nuxt-llms): automatic generation from your config or content. h2. [Quick Setup](#quick-setup) Install the module: ``` npx nuxi module add nuxt-llms ``` Configure in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['nuxt-llms'], llms: { domain: 'https://example.com', title: 'My Nuxt App', description: 'Documentation for My Nuxt App', sections: [ { title: 'Getting Started', links: [ { title: 'Installation', href: '/docs/installation' }, { title: 'Configuration', href: '/docs/configuration' } ] } ] } }) ``` The module generates `/llms.txt` and `/llms-full.txt` automatically at build time. h2. [When llms.txt Matters](#when-llmstxt-matters) llms.txt solves a specific problem: LLM context windows are too small to process entire websites. Your Nuxt documentation might be thousands of pages, but an AI assistant needs a curated entry point. **llms.txt is useful for:** - Documentation sites (API references, tutorials, guides) - Open source projects - Technical blogs with evergreen content - Sites where AI assistants frequently reference your content **llms.txt is overkill for:** - Marketing sites without technical docs - E-commerce product pages - News sites with time-sensitive content - Small sites with fewer than 10 pages h2. [llms.txt vs robots.txt](#llmstxt-vs-robotstxt) | **Feature** | **robots.txt** | **llms.txt** | | --- | --- | --- | | Purpose | Block/allow crawling | Guide AI to useful content | | Format | Custom syntax | Markdown | | When used | Training data collection | Inference (answering questions) | | Crawler support | All major crawlers | Limited, mostly AI coding tools | | Required | No | No | **Important:** [**AI crawlers don't currently request llms.txt**](https://www.longato.ch/llms-recommendation-2025-august/) during inference. GPTBot, ClaudeBot, and PerplexityBot use pre-built datasets and respect robots.txt, not llms.txt. The primary use case today is AI coding assistants (Cursor, Claude Code) and MCP servers that explicitly fetch llms.txt to understand project documentation. h2. [File Format](#file-format) llms.txt uses a structured Markdown format. Only the H1 title is required. Everything else is optional. /llms.txt ``` h1. Nuxt SEO Documentation > Complete SEO toolkit for Nuxt applications. Handles sitemaps, robots.txt, OG images, and schema.org. Key modules: sitemap, robots, og-image, schema-org, seo-utils. h2. Getting Started - [Installation](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction): Install the full Nuxt SEO module - [Site Config](https://nuxtseo.com/docs/site-config/getting-started/introduction): Configure your site URL and name h2. Modules - [Sitemap](https://nuxtseo.com/docs/sitemap/getting-started/introduction): Automatic sitemap generation - [Robots](https://nuxtseo.com/docs/robots/getting-started/introduction): robots.txt and meta robots - [OG Image](https://nuxtseo.com/docs/og-image/getting-started/introduction): Dynamic social images h2. Optional - [Changelog](https://github.com/harlan-zw/nuxt-seo/blob/main/CHANGELOG.md) ``` h3. [Required Sections](#required-sections) **H1 Title**: The only required element. Name of your project or site. h3. [Optional Sections](#optional-sections) **Blockquote**: Brief summary with key information for understanding the rest of the file. **Body content**: Paragraphs, lists, or any Markdown except headings. Provides context about the project. **H2 sections**: File lists with links to detailed documentation. Each entry is a Markdown link with optional description: ``` - [Link Text](https://url): Optional description of what this page covers ``` **Optional section**: An H2 titled "Optional" marks content that AI can skip if context is limited. h2. [nuxt-llms Configuration](#nuxt-llms-configuration) h3. [Manual Sections](#manual-sections) Define sections explicitly when you want full control: nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['nuxt-llms'], llms: { domain: 'https://example.com', title: 'My Nuxt App', description: 'A Nuxt 3 application with TypeScript.', sections: [ { title: 'Documentation', links: [ { title: 'API Reference', href: '/docs/api' }, { title: 'Components', href: '/docs/components' }, { title: 'Getting Started', href: '/docs/setup' } ] }, { title: 'Optional', links: [ { title: 'Changelog', href: '/changelog' } ] } ] } }) ``` h3. [With Nuxt Content](#with-nuxt-content) If you're using Nuxt Content, nuxt-llms can generate sections from your content automatically: nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['@nuxt/content', 'nuxt-llms'], llms: { domain: 'https://example.com', title: 'My Docs', // Automatically generates from /content/docs content: { collections: ['docs'] } } }) ``` h3. [Extended Version](#extended-version) The module generates both `/llms.txt` (concise) and `/llms-full.txt` (comprehensive) automatically. AI tools can choose based on their context limits. nuxt.config.ts ``` export default defineNuxtConfig({ llms: { // ... config full: { // Include full page content in llms-full.txt includeContent: true } } }) ``` h2. [Testing Your llms.txt](#testing-your-llmstxt) 1. **Check it loads**: Visit `https://yoursite.com/llms.txt` 2. **Validate format**: Ensure H1 exists and links are absolute URLs 3. **Test with AI**: Paste your llms.txt into ChatGPT or Claude and ask about your docs There's no official validator yet, but the [**llms-txt-hub**](https://github.com/thedaviddias/llms-txt-hub) directory lists sites implementing the standard. h2. [Who Uses llms.txt?](#who-uses-llmstxt) The standard has [**2,000+ GitHub stars**](https://github.com/AnswerDotAI/llms-txt) and growing adoption among documentation sites. Notable implementers include Answer.AI, fast.ai, and various open source projects. However, adoption by AI providers is limited. As of late 2025: - **No major AI crawler** (GPTBot, ClaudeBot, PerplexityBot) requests llms.txt during inference - **MCP servers** and AI coding tools can use it when explicitly configured - **Documentation frameworks** (VitePress, Docusaurus, Nuxt) generate it automatically The spec is still emerging. Implementing llms.txt today is forward-looking. It positions your docs for better AI integration as adoption grows. h2. [llms.txt and GEO](#llmstxt-and-geo) llms.txt complements [**Generative Engine Optimization**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt/learn-seo/nuxt/launch-and-listen/ai-optimized-content) but serves a different purpose: | **GEO** | **llms.txt** | | --- | --- | | Optimizes content for AI citations | Provides structured entry point to docs | | Targets AI search (ChatGPT, Perplexity) | Targets AI coding tools and MCP servers | | Uses schema.org, content structure | Uses Markdown file format | | Improves visibility in AI responses | Improves AI understanding of your project | For maximum AI visibility, implement both: 1. Schema.org structured data for GEO (use [**nuxt-schema-org**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt/docs/schema-org/getting-started/introduction)) 2. llms.txt for documentation discovery (use nuxt-llms) 3. Content structure optimized for extraction h2. [Full Nuxt SEO Stack](#full-nuxt-seo-stack) For comprehensive AI and search optimization, use the full Nuxt SEO module: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt/docs/nuxt-seo/getting-started/introduction) This includes automatic schema.org, sitemaps, robots.txt, and OG images. All signals that help both traditional search and AI search understand your content. --- [**Duplicate Content** Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt/learn-seo/nuxt/controlling-crawlers/duplicate-content) [**Routes & Rendering** URL patterns, rendering modes, and routing decisions that affect your Nuxt site's search rankings. SSR vs SSG vs SPA explained.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/llms-txt/learn-seo/nuxt/routes-and-rendering) **On this page** - [Quick Setup](#quick-setup) - [When llms.txt Matters](#when-llmstxt-matters) - [llms.txt vs robots.txt](#llmstxt-vs-robotstxt) - [File Format](#file-format) - [nuxt-llms Configuration](#nuxt-llms-configuration) - [Testing Your llms.txt](#testing-your-llmstxt) - [Who Uses llms.txt?](#who-uses-llmstxt) - [llms.txt and GEO](#llmstxt-and-geo) - [Full Nuxt SEO Stack](#full-nuxt-seo-stack) --- ### How to Use Meta Robots Tags in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags Description: Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Vue apps. h1. **How to Use Meta Robots Tags in Vue** Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Vue apps. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)9 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - `noindex` prevents page from appearing in search results - `nofollow` stops link equity from flowing through that page's links - Use X-Robots-Tag HTTP header for non-HTML files (PDFs, images) Meta robots tags give page-level control over search engine indexing. While [**robots.txt provides site-wide rules**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/learn-seo/vue/controlling-crawlers/robots-txt), the robots meta tag lets you handle individual pages differently. Use them to block filtered pages from indexing, prevent duplicate content issues on pagination, or control snippet appearance. For non-HTML files (PDFs, images) use [**X-Robots-Tag HTTP headers**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) instead. For site-wide rules stick with [**robots.txt**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/learn-seo/vue/controlling-crawlers/robots-txt). h2. [Setup](#setup) Add the robots meta tag using [**Unhead**](https://unhead.unjs.io/) in your Vue components. Must be [**server-side rendered**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/learn-seo/vue/routes-and-rendering/rendering) or crawlers won't see it. Block Indexing ``` useSeoMeta({ robots: 'noindex, follow' }) ``` Control Snippets ``` useSeoMeta({ robots: 'index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1' }) ``` Vue apps need [**manual Unhead installation**](https://unhead.unjs.io/guide/getting-started/installation). Nuxt includes it by default. h2. [How Meta Robots Tags Work](#how-meta-robots-tags-work) The robots meta tag goes in your page's `` and tells crawlers what to do with that specific page ([**Google's documentation**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag)): ``` ``` Without a robots meta tag, crawlers assume `index, follow` by default ([**MDN reference**](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/robots)). h3. [Common Directives](#common-directives) - `noindex` . Exclude from search results - `nofollow` . Don't follow links on this page - `noarchive` . Prevent cached copies - `nosnippet` . No description snippet in results - `max-snippet:[number]` . Limit snippet to N characters - `max-image-preview:[setting]` . Control image preview size (none, standard, large) - `max-video-preview:[number]` . Limit video preview to N seconds Combine multiple directives with commas. When directives conflict, the [**more restrictive one applies**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag). h3. [Critical Requirements](#critical-requirements) Must be server-side rendered. Google can execute JavaScript but does so in a [**second rendering wave**](https://medium.com/@emironic/server-side-rendering-ssr-vs-client-side-rendering-csr-why-it-matters-more-than-ever-for-ai-4dbf65142abc), which can delay or prevent proper indexing. SSR puts the tag in the HTML response immediately. If you block the page in robots.txt, crawlers [**never see the meta tag**](https://developers.google.com/search/docs/crawling-indexing/block-indexing). Don't combine robots.txt blocking with noindex. the noindex won't work. Target specific crawlers by replacing `robots` with a user agent token like `googlebot`. More specific tags override general ones. h2. [Noindex Follow vs Noindex Nofollow](#noindex-follow-vs-noindex-nofollow) Use `noindex, follow` when you want to block indexing but preserve link equity flow. The page won't rank, but links on it still count ([**Wolf of SEO guide**](https://wolf-of-seo.de/en/what-is/noindex-follow/)). Use `noindex, nofollow` for pages with no valuable links. login forms, checkout steps, truly private content. Noindex Follow - Preserve Links ``` // Filter pages, thank you pages, test pages useSeoMeta({ robots: 'noindex, follow' }) ``` Noindex Nofollow - Block Everything ``` // Login pages, admin sections useSeoMeta({ robots: 'noindex, nofollow' }) ``` Note that `follow` doesn't force crawling. it just allows it. Pages may still be crawled less over time if they remain noindexed. h2. [Common Use Cases](#common-use-cases) h3. [Search Results and Filtered Pages](#search-results-and-filtered-pages) Internal search results and filter combinations create duplicate content. [**Google recommends**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) blocking these with noindex or robots.txt: pages/search.vue ``` useSeoMeta({ robots: 'noindex, follow' }) ``` pages/products/[category].vue ``` const { query } = useRoute() useSeoMeta({ robots: Object.keys(query).length > 0 ? 'noindex, follow' : 'index, follow' }) ``` Combine with [**canonical tags**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/learn-seo/vue/controlling-crawlers/canonical-urls) pointing to the main category page. but don't use both noindex and canonical on the same page (sends [**conflicting signals**](https://www.oncrawl.com/technical-seo/use-robots-txt-meta-robots-canonical-tags-correctly/)). h3. [Pagination](#pagination) Google no longer uses `rel="next"` and `rel="prev"` tags ([**Google documentation**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading)). Instead, link to next/previous pages with regular `` tags and let Google discover the sequence naturally. You don't need to noindex pagination pages. Google treats them as part of the sequence. Only noindex if the paginated content is duplicate or low-value. h3. [User-Generated Content](#user-generated-content) Limit snippet length and prevent caching for dynamic user profiles: pages/user/[id]/profile.vue ``` useSeoMeta({ robots: 'index, follow, noarchive, max-snippet:50' }) ``` h2. [X-Robots-Tag for Non-HTML Files](#x-robots-tag-for-non-html-files) The meta robots tag only works in HTML. For PDFs, images, videos, or other files, use the [**X-Robots-Tag HTTP header**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) instead ([**MDN reference**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Robots-Tag)): ``` X-Robots-Tag: noindex, nofollow ``` X-Robots-Tag supports the same directives as the meta tag. It's also useful for bulk operations. you can apply it to entire directories or file patterns via server configuration. Don't use both meta robots and X-Robots-Tag on the same resource. easy to create [**conflicting instructions**](https://www.semrush.com/blog/robots-meta/). h2. [Verification](#verification) Use [**Google Search Console's URL Inspection tool**](https://support.google.com/webmasters/answer/9012289) to verify your robots meta tag: 1. Enter the URL 2. Check "Indexing allowed?" status 3. View the rendered HTML to confirm tag appears If a noindexed page still appears in search results, it hasn't been recrawled yet. Depending on the page's importance, [**it can take months**](https://developers.google.com/search/docs/crawling-indexing/block-indexing) for Google to revisit and apply the directive. Request a recrawl via the URL Inspection tool. Monitor "Excluded by 'noindex' tag" reports in Search Console. Sudden spikes indicate accidental noindexing of important pages. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about meta robots tags in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/learn-seo/nuxt/controlling-crawlers/meta-tags) **Quick Check** **What's the difference between robots.txt Disallow and meta noindex?** - `Both prevent indexing` - Disallow only prevents crawling, not indexing. Google can still index uncrawled URLs from external links - `Disallow blocks crawling, noindex blocks indexing` - Correct! Disallow stops the crawler from accessing the page, noindex tells the crawler not to add it to search results - `Noindex is site-wide, Disallow is per-page` - Opposite. robots.txt is site-wide, meta robots is per-page --- [**Sitemaps** Generate sitemaps for Vue SPAs using Vite plugins, server-side rendering, or build-time generation.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/learn-seo/vue/controlling-crawlers/sitemaps) [**Canonical Link Tag** Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Vue.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags/learn-seo/vue/controlling-crawlers/canonical-urls) **On this page** - [Setup](#setup) - [How Meta Robots Tags Work](#how-meta-robots-tags-work) - [Noindex Follow vs Noindex Nofollow](#noindex-follow-vs-noindex-nofollow) - [Common Use Cases](#common-use-cases) - [X-Robots-Tag for Non-HTML Files](#x-robots-tag-for-non-html-files) - [Verification](#verification) - [Using Nuxt?](#using-nuxt) --- ### Create an XML Sitemap in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps Description: Generate sitemaps for Vue SPAs using Vite plugins, server-side rendering, or build-time generation. h1. **Create an XML Sitemap in Vue** Generate sitemaps for Vue SPAs using Vite plugins, server-side rendering, or build-time generation. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - Sitemaps are limited to 50,000 URLs or 50MB. use sitemap index for larger sites - Only include indexable pages. skip noindexed, redirected, or error pages - Submit to Google Search Console and reference in robots.txt The `sitemap.xml` file helps search engines discover your pages. Google considers sites "small" if they have [**500 pages or fewer**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview). you likely need a sitemap if you exceed this, have new sites with few backlinks, or update content frequently. h2. [Static Sitemap](#static-sitemap) For small sites under 100 pages, create a static sitemap in your public directory: ``` public/ sitemap.xml ``` Add your URLs with proper formatting: ``` https://mysite.com/ 2024-11-03 https://mysite.com/about 2024-12-10 ``` Sitemaps are limited to [**50,000 URLs or 50MB uncompressed**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap). Use UTF-8 encoding and absolute URLs only. h2. [Build-Time Generation with Vite](#build-time-generation-with-vite) For most Vue projects, use [**vite-plugin-sitemap**](https://github.com/jbaubree/vite-plugin-sitemap) to auto-generate sitemaps during build: vite.config.ts ``` import Sitemap from 'vite-plugin-sitemap' export default defineConfig({ plugins: [ Sitemap({ hostname: 'https://mysite.com', dynamicRoutes: [ '/blog/post-1', '/blog/post-2' ] }) ] }) ``` After running `npm build`, this generates `sitemap.xml` and `robots.txt` in your dist folder. h3. [Vue Router Integration](#vue-router-integration) If using Vue Router, generate routes from your router configuration: vite.config.ts ``` import Sitemap from 'vite-plugin-sitemap' import routes from './src/router/routes' export default defineConfig({ plugins: [ Sitemap({ hostname: 'https://mysite.com', dynamicRoutes: routes.map(route => route.path) }) ] }) ``` Avoid hash-based routing (`/#/about`). [**search engines can't process sitemap entries with hash links**](https://dev.to/niickfraziier/building-seo-friendly-single-page-applications-spas-with-react-and-vue-44fa). Use history mode instead. h2. [Server-Side Sitemap Generation](#server-side-sitemap-generation) For sites with frequently changing content (e-commerce, blogs, news), generate sitemaps server-side: ``` import express from 'express' const app = express() app.get('/sitemap.xml', async (req, res) => { const pages = await fetchAllPages() const urls = pages.map(page => \` https://mysite.com${page.path} ${page.updatedAt} \`).join('\n') const sitemap = \` ${urls} \` res.type('application/xml').send(sitemap) }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use(async (req, res, next) => { if (req.path === '/sitemap.xml') { const pages = await fetchAllPages() const urls = pages.map(page => \` https://mysite.com${page.path} ${page.updatedAt} \`).join('\n') const sitemap = \` ${urls} \` return res.type('application/xml').send(sitemap) } next() }) ``` ``` import { defineEventHandler, setHeader } from 'h3' export default defineEventHandler(async (event) => { if (event.path === '/sitemap.xml') { const pages = await fetchAllPages() const urls = pages.map(page => \` https://mysite.com${page.path} ${page.updatedAt} \`).join('\n') const sitemap = \` ${urls} \` setHeader(event, 'Content-Type', 'application/xml') return sitemap } }) ``` This works with any Node.js server or SSR framework. For SPAs without SSR, use build-time generation instead. [**client-side rendering makes sitemaps harder**](https://medium.com/@ninapepite/spa-and-seo-b2735716dfc4) since you need to know all routes at build time. h2. [XML Sitemap Tags](#xml-sitemap-tags) A basic sitemap uses these elements: ``` https://mysite.com/page 2024-11-03 ``` h3. [Required and Optional Tags](#required-and-optional-tags) | **Tag** | **Status** | **Google Uses?** | | --- | --- | --- | | `` | Required | Yes. the page URL | | `` | Recommended | Yes. [**if consistently accurate**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) | | `` | Skip | [**No. Google ignores this**](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping) | | `` | Skip | [**No. Google ignores this**](https://developers.google.com/search/blog/2014/10/best-practices-for-xml-sitemaps-rssatom) | Google only uses `` if it matches reality. If your page changed 7 years ago but you claim it updated yesterday, [**Google stops trusting your sitemap**](https://www.seocomponent.com/blog/ignore-priority-changefreq-fields-sitemap/). Don't bother with `` or ``. they increase file size without adding value. h2. [Sitemap Index for Large Sites](#sitemap-index-for-large-sites) If you exceed 50,000 URLs or 50MB, [**split your sitemap into multiple files**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps): sitemap-index.xml ``` https://mysite.com/sitemap-products.xml 2024-11-03 https://mysite.com/sitemap-blog.xml 2024-12-10 ``` Submit the index file to Google Search Console. it will crawl all referenced sitemaps. [**Maximize URLs per sitemap**](https://developers.google.com/search/blog/2014/10/best-practices-for-xml-sitemaps-rssatom) rather than creating many small files. h2. [Specialized Sitemaps](#specialized-sitemaps) h3. [News Sitemap](#news-sitemap) News publishers should create [**separate news sitemaps**](https://fsidm.in/knowledge/seo/image-news-video-sitemaps/) with articles from the last 2 days: sitemap-news.xml ``` https://mysite.com/article Site Name en 2024-12-10T12:00:00+00:00 Article Title ``` Remove articles older than 2 days to keep the sitemap fresh. Don't create new sitemaps daily. update the existing one. h3. [Image Sitemap](#image-sitemap) For galleries or image-heavy sites, add image metadata: sitemap.xml ``` https://mysite.com/page https://mysite.com/image.jpg Image Title ``` [**Image sitemaps help Google find images loaded via JavaScript**](https://www.docommunication.io/blog/sitemaps-explained) or not directly linked in HTML. h2. [Submit to Google Search Console](#submit-to-google-search-console) After generating your sitemap, [**submit it to Google Search Console**](https://support.google.com/webmasters/answer/7451001): 1. [**Verify site ownership**](https://seotesting.com/google-search-console/how-to-create-a-sitemap-for-google-search-console/) (DNS, HTML file upload, or Google Analytics) 2. Navigate to **Sitemaps** under **Indexing** 3. Enter your sitemap URL: `https://mysite.com/sitemap.xml` 4. Click **Submit** Google processes the sitemap and reports status: - **Success**. sitemap accepted, pages discovered - **Pending**. processing in progress - **Couldn't fetch**. URL wrong or blocked by robots.txt Check the Sitemaps report for coverage stats, indexing errors, and discovered URLs. h3. [Before Submitting](#before-submitting) Validate your sitemap: - XML syntax is valid (use an [**online validator**](https://www.xml-sitemaps.com/validate-xml-sitemap.html)) - All URLs return 200 status (not 404 or 500) - URLs match [**canonical URLs**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps/learn-seo/vue/controlling-crawlers/canonical-urls) exactly - `` dates are accurate - File uses UTF-8 encoding h3. [Alternative Submission](#alternative-submission) Reference your sitemap in `robots.txt`: public/robots.txt ``` User-agent: * Allow: / Sitemap: https://mysite.com/sitemap.xml ``` Google discovers this automatically when [**crawling your robots.txt**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps/learn-seo/vue/controlling-crawlers/robots-txt). h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about sitemaps in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps/learn-seo/nuxt/controlling-crawlers/sitemaps) --- [**Robots.txt** Robots.txt tells crawlers what they can access. Here's how to set it up in Vue.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps/learn-seo/vue/controlling-crawlers/robots-txt) [**Robot Meta Tag** Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Vue apps.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps/learn-seo/vue/controlling-crawlers/meta-tags) **On this page** - [Static Sitemap](#static-sitemap) - [Build-Time Generation with Vite](#build-time-generation-with-vite) - [Server-Side Sitemap Generation](#server-side-sitemap-generation) - [XML Sitemap Tags](#xml-sitemap-tags) - [Sitemap Index for Large Sites](#sitemap-index-for-large-sites) - [Specialized Sitemaps](#specialized-sitemaps) - [Submit to Google Search Console](#submit-to-google-search-console) - [Using Nuxt?](#using-nuxt) --- ### Meta Descriptions in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/mastering-meta/descriptions Description: Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Nuxt using composables and let your content do the heavy lifting. h1. **Meta Descriptions in Nuxt** Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Nuxt using composables and let your content do the heavy lifting. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)7 mins read Published **Nov 5, 2024** Updated **Jan 4, 2026** **What you'll learn** - Use `useSeoMeta()` for type-safe descriptions with autocomplete - Google rewrites 62-70% of descriptions. focus on content quality - Target ~155 characters for desktop, front-load value for mobile truncation Meta descriptions don't directly impact rankings. They affect click-through rates by giving users context about your page. ``` ``` Unlike [**page titles**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/descriptions/learn-seo/nuxt/mastering-meta/titles), descriptions have more flexibility but require careful crafting to be effective. h3. [Quick Facts](#quick-facts) 1. **Google rewrites most of them**: Studies show 62-70% get rewritten ([**Ahrefs**](https://ahrefs.com/blog/meta-description-study/), [**Search Engine Journal**](https://www.searchenginejournal.com/google-rewrites-meta-descriptions-over-70-of-the-time/382140/)). Good page content matters more. 2. **Length varies by device**: Desktop shows ~155-160 characters, mobile ~120. [**Focus on front-loading value**](https://searchengineland.com/seo-meta-descriptions-everything-to-know-447910) rather than hitting a character count. 3. **Template descriptions are lazy**: "Buy {product} at great prices" won't differentiate you. Summarize using AI tools if you're short on time. h2. [Setting Meta Descriptions in Nuxt](#setting-meta-descriptions-in-nuxt) Nuxt includes [**Unhead**](https://unhead.unjs.io/) by default. Use `useSeoMeta` for type-safe descriptions: ``` useSeoMeta({ description: 'Your description here' }) ``` Alternatively, `useHead()` gives you more control: ``` useHead({ meta: [ { name: 'description', content: 'Your description here' } ] }) ``` h2. [Dynamic Meta Descriptions](#dynamic-meta-descriptions) Use Nuxt's data fetching composables for reactive descriptions: ``` ``` h2. [Open Graph Description `'og:description'`](#open-graph-description-ogdescription) Facebook, LinkedIn, Discord, Slack, and WhatsApp all [**use **`og:description`](https://w3things.com/blog/open-graph-meta-tags/) for link previews. X (Twitter) falls back to `og:description` when `twitter:description` isn't set. so you can skip the Twitter-specific tag. ``` useSeoMeta({ description: 'Technical description for search', ogDescription: 'More casual description for social sharing', // X falls back to ogDescription, no need for twitterDescription }) ``` **Testing tools:** - [**Facebook Sharing Debugger**](https://developers.facebook.com/tools/debug/) - [**LinkedIn Post Inspector**](https://www.linkedin.com/post-inspector/) - [**OpenGraph.xyz**](https://www.opengraph.xyz/) (tests Facebook, LinkedIn, Discord, X) h2. [Nuxt SEO Module](#nuxt-seo-module) Automates meta descriptions, titles, and social sharing: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/descriptions/docs/nuxt-seo/getting-started/introduction) --- [**Titles** Set dynamic page titles in Nuxt with useHead. Learn title templates, reactive titles, and SSR patterns that Google indexes correctly.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/descriptions/learn-seo/nuxt/mastering-meta/titles) [**Social Sharing** Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/descriptions/learn-seo/nuxt/mastering-meta/open-graph) **On this page** - [Quick Facts](#quick-facts) - [Setting Meta Descriptions in Nuxt](#setting-meta-descriptions-in-nuxt) - [Dynamic Meta Descriptions](#dynamic-meta-descriptions) - [Open Graph Description 'og:description'](#open-graph-description-ogdescription) - [Nuxt SEO Module](#nuxt-seo-module) --- ### Rich Results in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results Description: Get rich results in Google search with Schema.org structured data. Learn which types still work after Google's 2023 FAQ/HowTo removals. h1. **Rich Results in Nuxt** Get rich results in Google search with Schema.org structured data. Learn which types still work after Google's 2023 FAQ/HowTo removals. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** **What you'll learn** - FAQ/HowTo rich results removed for most sites in August 2023 - Product, Recipe, Event, Article types still show rich results - Valid markup doesn't guarantee display. Google decides based on query relevance Rich results are enhanced search listings that display more than the standard blue link. stars, prices, cooking times, FAQs. Google shows them when your page has valid [**Schema.org structured data**](https://schema.org/). Not all rich results survived. Google removed [**FAQ and HowTo rich results**](https://developers.google.com/search/blog/2023/08/howto-faq-changes) in August 2023 for most sites. h2. [What Changed in 2023](#what-changed-in-2023) Google made two major changes to rich results: **August 8, 2023:** FAQ rich results limited to authoritative government and health sites only. All other sites lost FAQ rich results. **September 13, 2023:** HowTo rich results completely removed from desktop and mobile. No exceptions. Google said the changes provide "a cleaner and more consistent search experience." The reality: these result types were overused and spammy. John Mueller [**referenced "the tragedy of the commons"**](https://www.searchenginejournal.com/google-downgrades-visibility-of-howto-and-faq-rich-results/493522/) when explaining the decision. Sites that relied on FAQ/HowTo rich results saw CTR drops. No way around it. those rich snippets are gone. h2. [Rich Results That Still Work](#rich-results-that-still-work) These rich result types remain active as of December 2025: | **Type** | **Shows** | **Example** | | --- | --- | --- | | [**Article**](https://developers.google.com/search/docs/appearance/structured-data/article) | Headline, image, date | News, blog posts | | [**Breadcrumb**](https://developers.google.com/search/docs/appearance/structured-data/breadcrumb) | Navigation path | Home > Category > Page | | [**Event**](https://developers.google.com/search/docs/appearance/structured-data/event) | Date, location, ticket info | Concerts, conferences | | [**LocalBusiness**](https://developers.google.com/search/docs/appearance/structured-data/local-business) | Hours, location, reviews | Stores, restaurants | | [**Product**](https://developers.google.com/search/docs/appearance/structured-data/product) | Price, availability, reviews | E-commerce | | [**Recipe**](https://developers.google.com/search/docs/appearance/structured-data/recipe) | Cook time, calories, ratings | Food sites | | [**Review**](https://developers.google.com/search/docs/appearance/structured-data/review-snippet) | Star rating | Product/service reviews | | [**Video**](https://developers.google.com/search/docs/appearance/structured-data/video) | Thumbnail, duration, upload date | YouTube, Vimeo embeds | [**See the full list in Google's Search Gallery**](https://developers.google.com/search/docs/appearance/structured-data/search-gallery). Product, Recipe, and Event rich results drive measurable CTR increases. Rotten Tomatoes saw a [**25% higher click-through rate**](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) on pages with structured data. h2. [Testing Your Markup](#testing-your-markup) ![Rich Results Testing Workflow](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results/images/learn-seo/nuxt/rich-results-workflow.svg) Use [**Rich Results Test**](https://search.google.com/test/rich-results) to validate structured data. Enter your URL or paste HTML directly. The tool shows which rich results are eligible and flags errors. **Two validation tools exist. use both:** 1. **Rich Results Test** . Tests Google-specific eligibility. Use this to see if your markup qualifies for rich results in search. 2. **Schema Markup Validator** . Tests schema.org syntax compliance. Use this for types that don't qualify for rich results but you want on the page anyway (like Organization, WebSite). The [**Schema Markup Validator**](https://validator.schema.org/) validates all schema.org types, not just those eligible for Google rich results. If you're implementing Organization or WebSite schema (which don't show rich results), use this tool. h2. [Monitoring in Search Console](#monitoring-in-search-console) Google Search Console shows a separate report for each rich result type found on your site: 1. Open Search Console 2. Go to **Enhancements** in the sidebar 3. Click the rich result type (Product, Recipe, Article, etc.) Each report shows valid items (can display as rich results) and invalid items (have errors blocking display). Fix errors immediately. they block rich results entirely. Address warnings when possible. they may reduce effectiveness. The reports show a sample of detected items, not every instance. Google drops reporting for types that no longer appear in search. [**September 2025 removed reporting**](https://jobnimbusmarketing.com/blog/september-2025-google-search-console-reports-update/) for Course Info, Estimated Salary, Learning Video, Special Announcement, Vehicle Listing, Claim Review. h2. [Implementation in Nuxt](#implementation-in-nuxt) Nuxt Schema.org provides type-safe helpers for all rich result types. See the full [**Schema.org module documentation**](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results/docs/schema-org/getting-started/introduction). Quick example for Product rich results: ``` ``` The `defineProduct()` helper ensures your markup meets [**Google's Product structured data guidelines**](https://developers.google.com/search/docs/appearance/structured-data/product). [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results/docs/schema-org/getting-started/introduction) h2. [Should You Remove FAQ/HowTo Markup?](#should-you-remove-faqhowto-markup) Leave existing FAQ/HowTo markup in place. Google confirms it doesn't hurt, and they could reverse course. Only skip adding new FAQ/HowTo markup. No. Google says structured data that's not used "does not cause problems for Search, but also has no visible effects." Leave it. it doesn't hurt, and Google could reverse course. If you're adding new markup, skip FAQ and HowTo unless you're a government/health site. Focus on Product, Recipe, Article, Event types that still work. h2. [Common Rich Result Mistakes](#common-rich-result-mistakes) **Missing required fields** . Each rich result type has required properties. Product needs `name`, `image`, `offers`. Recipe needs `name`, `image`, `totalTime`. Check Google's docs for each type. **Invalid image URLs** . Images must be absolute HTTPS URLs, not relative paths. Minimum 1200x630px for most types. **Blocking images in robots.txt** . Google must be able to crawl your images. Check your robots.txt doesn't block `/images/` or similar paths. **No visible content** . The structured data must match visible content on the page. Markup for a recipe that's not actually on the page violates [**Google's spam policies**](https://developers.google.com/search/docs/appearance/structured-data/sd-policies). **Syntax errors** . Invalid JSON-LD breaks all structured data on the page. Validate with the Schema Markup Validator before deploying. h2. [Rich Results Aren't Guaranteed](#rich-results-arent-guaranteed) Valid markup doesn't guarantee rich results will show. Google decides based on: - Search query relevance - User intent - Competition for that result type - Page authority You can have perfect structured data and still not see rich results. That's normal. Focus on adding markup for the user benefit (better organized data) not purely for search appearance. --- [**Social Sharing** Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results/learn-seo/nuxt/mastering-meta/open-graph) [**Schema.org** Learn how to implement Schema.org structured data in Nuxt. Get rich results in Google search with type-safe JSON-LD markup using the Nuxt Schema.org module.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/rich-results/learn-seo/nuxt/mastering-meta/schema-org) **On this page** - [What Changed in 2023](#what-changed-in-2023) - [Rich Results That Still Work](#rich-results-that-still-work) - [Testing Your Markup](#testing-your-markup) - [Monitoring in Search Console](#monitoring-in-search-console) - [Implementation in Nuxt](#implementation-in-nuxt) - [Should You Remove FAQ/HowTo Markup?](#should-you-remove-faqhowto-markup) - [Common Rich Result Mistakes](#common-rich-result-mistakes) - [Rich Results Aren't Guaranteed](#rich-results-arent-guaranteed) --- ### JSON-LD Structured Data in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org Description: Learn how to implement Schema.org structured data in Nuxt. Get rich results in Google search with type-safe JSON-LD markup using the Nuxt Schema.org module. h1. **JSON-LD Structured Data in Nuxt** Learn how to implement Schema.org structured data in Nuxt. Get rich results in Google search with type-safe JSON-LD markup using the Nuxt Schema.org module. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 5, 2024** Updated **Dec 17, 2025** **What you'll learn** - `useSchemaOrg()` provides type-safe JSON-LD with automatic graph linking - Nuxt Schema.org module sets up WebSite and WebPage schemas automatically - Rich results aren't guaranteed. test with Google's Rich Results Test Schema.org structured data helps Google display [**Rich Results**](https://developers.google.com/search/docs/appearance/structured-data/search-gallery). stars, FAQs, recipes, product prices. Rotten Tomatoes saw a [**25% higher click-through rate**](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) on pages with structured data compared to pages without. ``` ``` Google [**recommends JSON-LD**](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) as the easiest format to implement and maintain. Common rich result types: - [**Article**](https://developers.google.com/search/docs/data-types/article) - news, blog posts - [**Breadcrumb**](https://developers.google.com/search/docs/data-types/breadcrumb) - navigation trail - [**FAQ**](https://developers.google.com/search/docs/data-types/faqpage) - question/answer pairs - [**Product**](https://developers.google.com/search/docs/appearance/structured-data/product) - pricing, availability Rich results aren't guaranteed. Content must match the markup and follow [**Google's structured data guidelines**](https://developers.google.com/search/docs/appearance/structured-data/sd-policies). h2. [Automated Setup with Nuxt Schema.org](#automated-setup-with-nuxt-schemaorg) The Nuxt Schema.org module handles structured data automatically with zero config: [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org/docs/schema-org/getting-started/introduction) Or install the full Nuxt SEO module which includes Schema.org plus sitemaps, robots.txt, and OG images: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org/docs/nuxt-seo/getting-started/introduction) The module sets up base schema (WebSite, WebPage, Organization/Person) automatically and provides type-safe helpers for all common schema types. h2. [Manual Implementation](#manual-implementation) Nuxt includes [**Unhead**](https://unhead.unjs.io/) for head management. You can add JSON-LD via `useHead()`, but `useSchemaOrg()` from `@unhead/schema-org` provides type safety and automatic [**graph linking**](https://schema.org/docs/data-and-datasets.html). If you're not using the Nuxt Schema.org module, install the Unhead package: ``` pnpm add -D @unhead/schema-org ``` See [**Unhead Schema.org setup**](https://unhead.unjs.io/schema-org/getting-started/setup) for full install instructions. ``` import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue' useSchemaOrg([ defineArticle({ headline: 'JSON-LD Structured Data in Nuxt', author: { name: 'Your Name' }, datePublished: new Date(2024, 0, 15), }) ]) ``` ``` useHead({ script: [ { type: 'application/ld+json', innerHTML: JSON.stringify({ '@context': 'https://schema.org', '@type': 'Article', 'headline': 'JSON-LD Structured Data in Nuxt', 'author': { '@type': 'Person', 'name': 'Your Name' } }) } ], }) ``` The `defineX()` helpers align with [**Google's Structured Data Guidelines**](https://developers.google.com/search/docs/guides/sd-policies) and handle boilerplate: | **Helper** | **Rich Result Type** | | --- | --- | | [`defineArticle()`](https://unhead.unjs.io/schema-org/schema/article) | Article, NewsArticle, BlogPosting | | [`defineBreadcrumb()`](https://unhead.unjs.io/schema-org/schema/breadcrumb) | Breadcrumb navigation | | [`defineQuestion()`](https://unhead.unjs.io/schema-org/schema/question) | FAQ pages | | [`defineProduct()`](https://unhead.unjs.io/schema-org/schema/product) | Product listings | h2. [Reactive Data](#reactive-data) `useSchemaOrg()` accepts refs and computed getters: ``` const article = ref({ title: 'My Article', description: 'Article description' }) useSchemaOrg([ defineArticle({ headline: () => article.value.title, description: () => article.value.description, }) ]) ``` h2. [Site-Wide Setup](#site-wide-setup) Set up base schema in your root component or layout. Child components can add specific types that link to this graph automatically. app.vue ``` ``` If using the Nuxt Schema.org module, this configuration is handled automatically in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['nuxt-schema-org'], site: { url: 'https://mysite.com', name: 'My Site', }, schemaOrg: { identity: { type: 'Organization', name: 'My Company', logo: '/logo.png', } } }) ``` h2. [Blog Article Example](#blog-article-example) Nuxt's hierarchical head system means you don't need to repeat `WebSite` and `WebPage` if they're in the layout: pages/blog/[slug].vue ``` ``` h2. [Testing Your Markup](#testing-your-markup) Use [**Google's Rich Results Test**](https://search.google.com/test/rich-results) to validate your structured data. Enter your URL or paste the HTML directly. The tool shows which rich results are eligible and flags any errors. For local development, inspect the `` to verify the JSON-LD script tag is present and valid JSON. --- [**Rich Results** Get rich results in Google search with Schema.org structured data. Learn which types still work after Google's 2023 FAQ/HowTo removals.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org/learn-seo/nuxt/mastering-meta/rich-results) [**Slack** Configure Open Graph meta tags for Slack unfurl previews](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/schema-org/learn-seo/nuxt/mastering-meta/slack) **On this page** - [Automated Setup with Nuxt Schema.org](#automated-setup-with-nuxt-schemaorg) - [Manual Implementation](#manual-implementation) - [Reactive Data](#reactive-data) - [Site-Wide Setup](#site-wide-setup) - [Blog Article Example](#blog-article-example) - [Testing Your Markup](#testing-your-markup) --- ### Slack Link Previews in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/mastering-meta/slack Description: Configure Open Graph meta tags for Slack unfurl previews h1. **Slack Link Previews in Nuxt** Configure Open Graph meta tags for Slack unfurl previews [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Nov 3, 2024** Updated **Dec 5, 2024** **What you'll learn** - Slack reads standard OG tags. no Slack-specific meta needed - Images must be absolute HTTPS URLs under 5MB, no redirects - Cache is aggressive (30+ days). use URL parameters (?v=2) to force refresh Slack unfurls use Open Graph tags to generate link previews. No special Slack-specific tags needed, just standard OG meta. ``` ``` Slack reads these four tags. Skip `ogUrl` if you want, Slack fills it automatically from the shared link. h2. [Image Requirements](#image-requirements) Slack displays images at 360x200px in chat, 1200x600px on click. Your OG image should be at least 1200x630px. ``` useSeoMeta({ ogImage: 'https://example.com/og-image.png', ogImageWidth: 1200, ogImageHeight: 630, ogImageAlt: 'Dashboard showing 847 active users' }) ``` Width/height tags help Slack allocate space before download. Alt text appears if image fails. h2. [What Slack Ignores](#what-slack-ignores) Slack doesn't care about: - Twitter Card tags (use those for Twitter/X, not Slack) - `og:type` unless you're sharing video/audio - `og:locale` - `article:*` tags - Schema.org markup Don't waste time on tags Slack won't read. Works well for SaaS dashboards shared in team channels, blog posts, support docs, and product pages. Skip it for internal tools behind auth (Slack can't access them) or heavily personalized pages (Slack caches aggressively, everyone sees the same preview). h2. [Testing](#testing) Slack caches unfurls for 30+ days. Testing is painful. **Option 1: URL parameters** ``` https://example.com/page?v=2 ``` Different URL = fresh cache. Increment `v` each test. **Option 2: Slack Unfurl Tester** Visit `slack.com/apps/YOUR_APP_ID/event-subscriptions` if you have a Slack app. No app? Use URL parameters. **Option 3: Share in DM with yourself** Type `/unfurl reset https://example.com/page` in any channel. Doesn't always work. h2. [Common Issues](#common-issues) **Image not showing**: Check image size (must be under 5MB), format (PNG/JPG/GIF only), and SSL (HTTPS required). Slack rejects images > 5MB silently. **Wrong description**: Slack reads `og:description`, not meta description. Set both: ``` useSeoMeta({ description: 'For search engines', ogDescription: 'For Slack/social previews' }) ``` **Old preview stuck**: Slack's cache is aggressive. Only fix is URL parameters or waiting 30+ days. **Preview shows but no image**: Your `og:image` URL probably redirects. Slack doesn't follow redirects for images. Use direct image URLs. h2. [Authenticated Pages](#authenticated-pages) Slack's crawler can't log in. Pages behind auth show generic unfurls. Options: 1. Generate preview pages at public URLs 2. Use signed URLs that expire 3. Accept generic unfurls for private content Most SaaS apps do option 3. Not worth engineering effort. h2. [Dynamic Content](#dynamic-content) Slack caches the first unfurl for 30+ days. Users sharing `example.com/dashboard` all see the same preview, even if dashboard data differs. Generate unique URLs if previews should differ: ``` example.com/dashboard?user=alice example.com/dashboard?user=bob ``` Overkill for most cases. Static previews work fine. --- [**Schema.org** Learn how to implement Schema.org structured data in Nuxt. Get rich results in Google search with type-safe JSON-LD markup using the Nuxt Schema.org module.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/slack/learn-seo/nuxt/mastering-meta/schema-org) [**Twitter Cards** Configure Twitter/X Cards to control how your links appear when shared on Twitter with rich previews, images, and metadata.](https://nuxtseo.com/learn-seo/nuxt/mastering-meta/slack/learn-seo/nuxt/mastering-meta/twitter-cards) **On this page** - [Image Requirements](#image-requirements) - [What Slack Ignores](#what-slack-ignores) - [Testing](#testing) - [Common Issues](#common-issues) - [Authenticated Pages](#authenticated-pages) - [Dynamic Content](#dynamic-content) --- ### Robots.txt in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt Description: Robots.txt tells crawlers what they can access. Here's how to set it up in Nuxt. h1. **Robots.txt in Nuxt** Robots.txt tells crawlers what they can access. Here's how to set it up in Nuxt. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - robots.txt is advisory. crawlers can ignore it, never use for security - Use Nuxt Robots module for environment-aware generation (auto-block staging) - Include sitemap reference and consider blocking AI training bots separately The `robots.txt` file controls which parts of your site crawlers can access. [**Officially adopted as RFC 9309**](https://datatracker.ietf.org/doc/html/rfc9309) in September 2022 after 28 years as a de facto standard, it's primarily used to [**manage crawl budget**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) on large sites and block AI training bots. Robots.txt is not a security mechanism. [**crawlers can ignore it**](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Robots_txt). For individual page control, use [**meta robots tags**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/controlling-crawlers/meta-tags) instead. h2. [Quick Setup](#quick-setup) Nuxt provides multiple approaches for robots.txt. For most sites, use the Nuxt Robots module: [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/docs/robots/getting-started/introduction) Install the module: ``` npx nuxi@latest module add robots ``` The module automatically generates `robots.txt` with zero config. For environment-specific rules (e.g., blocking all crawlers in staging), configure in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['@nuxtjs/robots'], robots: { disallow: process.env.NODE_ENV !== 'production' ? '/' : undefined } }) ``` h3. [Static robots.txt](#static-robotstxt) For simple static rules, add the file in your public directory: ``` public/ robots.txt ``` Add your rules: robots.txt ``` h1. Allow all crawlers User-agent: * Disallow: h1. Optionally point to your sitemap Sitemap: https://mysite.com/sitemap.xml ``` h3. [Server Route](#server-route) For custom dynamic generation without the module, create a server route: server/routes/robots.txt.ts ``` export default defineEventHandler((event) => { const isDev = process.env.NODE_ENV !== 'production' const robots = isDev ? 'User-agent: *\nDisallow: /' : 'User-agent: *\nDisallow:\nSitemap: https://mysite.com/sitemap.xml' setHeader(event, 'Content-Type', 'text/plain') return robots }) ``` h2. [Robots.txt Syntax](#robotstxt-syntax) The `robots.txt` file consists of directives grouped by user agent. Google [**uses the most specific matching rule**](https://developers.google.com/search/docs/crawling-indexing/robots/robots_txt) based on path length: robots.txt ``` h1. Define which crawler these rules apply to User-agent: * h1. Block access to specific paths Disallow: /admin h1. Allow access to specific paths (optional, more specific than Disallow) Allow: /admin/public h1. Point to your sitemap Sitemap: https://mysite.com/sitemap.xml ``` h3. [User-agent](#user-agent) The `User-agent` directive specifies which crawler the rules apply to: robots.txt ``` h1. All crawlers User-agent: * h1. Just Googlebot User-agent: Googlebot h1. Multiple specific crawlers User-agent: Googlebot User-agent: Bingbot Disallow: /private ``` Common crawler user agents: - [**Googlebot**](https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers): Google's search crawler (28% of all bot traffic in 2025) - [**Google-Extended**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/): Google's AI training crawler (separate from search) - [**GPTBot**](https://platform.openai.com/docs/bots/overview-of-openai-crawlers): OpenAI's AI training crawler (7.5% of bot traffic) - [**ClaudeBot**](https://www.playwire.com/blog/how-to-block-ai-bots-with-robotstxt-the-complete-publishers-guide): Anthropic's AI training crawler - [**CCBot**](https://commoncrawl.org/ccbot): Common Crawl's dataset builder (frequently blocked) - [**Bingbot**](https://ahrefs.com/seo/glossary/bingbot): Microsoft's search crawler - [**FacebookExternalHit**](https://developers.facebook.com/docs/sharing/webmasters/web-crawlers/): Facebook's link preview crawler h3. [Allow / Disallow](#allow-disallow) The `Allow` and `Disallow` directives control path access: robots.txt ``` User-agent: * h1. Block all paths starting with /admin Disallow: /admin h1. Block a specific file Disallow: /private.html h1. Block files with specific extensions Disallow: /*.pdf$ h1. Block URL parameters Disallow: /*?* ``` Wildcards supported ([**RFC 9309**](https://datatracker.ietf.org/doc/html/rfc9309)): - `*` . matches zero or more characters - `$` . matches the end of the URL - Paths are case-sensitive and relative to domain root h3. [Sitemap](#sitemap) The `Sitemap` directive tells crawlers where to find your [**sitemap.xml**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/controlling-crawlers/sitemaps): robots.txt ``` Sitemap: https://mysite.com/sitemap.xml h1. Multiple sitemaps Sitemap: https://mysite.com/products-sitemap.xml Sitemap: https://mysite.com/blog-sitemap.xml ``` With the [**Nuxt Sitemap module**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/docs/sitemap/getting-started/introduction), the sitemap URL is automatically added to your `robots.txt`. h3. [Crawl-Delay (Non-Standard)](#crawl-delay-non-standard) `Crawl-Delay` is not part of [**RFC 9309**](https://datatracker.ietf.org/doc/html/rfc9309). Google ignores it. Bing and Yandex support it: robots.txt ``` User-agent: Bingbot Crawl-delay: 10 # seconds between requests ``` For Google, [**crawl rate is managed in Search Console**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget). h2. [Security: Why robots.txt Fails](#security-why-robotstxt-fails) [**Robots.txt is not a security mechanism**](https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Robots_txt). Malicious crawlers ignore it, and listing paths in `Disallow` [**reveals their location to attackers**](https://www.searchenginejournal.com/robots-txt-security-risks/289719/). **Common mistake:** ``` h1. ❌ Advertises your admin panel location User-agent: * Disallow: /admin Disallow: /wp-admin Disallow: /api/internal ``` Use [**proper authentication**](https://developers.google.com/search/docs/crawling-indexing/block-indexing) instead. See our [**security guide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/security) for details. h2. [Crawling vs Indexing](#crawling-vs-indexing) Blocking a URL in `robots.txt` prevents crawling but [**doesn't prevent indexing**](https://developers.google.com/search/docs/crawling-indexing/robots/intro). If other sites link to the URL, Google can still index it without crawling, showing the URL with no snippet. To prevent indexing: - Use [`noindex`** meta tag**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/controlling-crawlers/meta-tags) (requires allowing crawl) - Use password protection or authentication - Return 404/410 status codes Don't block pages with `noindex` in `robots.txt`. Google can't see the tag if it can't crawl. h2. [Common Mistakes](#common-mistakes) h3. [1. Blocking JavaScript and CSS](#_1-blocking-javascript-and-css) [**Google needs JavaScript and CSS to render pages**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). Blocking them breaks indexing: robots.txt ``` h1. ❌ Prevents Google from rendering your Nuxt app User-agent: * Disallow: /assets/ Disallow: /*.js$ Disallow: /*.css$ ``` Nuxt apps are JavaScript-heavy. Never block `.js`, `.css`, or `/assets/` from Googlebot. h3. [2. Blocking Dev Sites in Production](#_2-blocking-dev-sites-in-production) Copy-pasting a dev `robots.txt` to production blocks all crawlers: robots.txt ``` h1. ❌ Accidentally left from staging User-agent: * Disallow: / ``` The [**Nuxt Robots module**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/docs/robots/getting-started/introduction) handles this automatically based on environment. h3. [3. Confusing robots.txt with noindex](#_3-confusing-robotstxt-with-noindex) Blocking pages doesn't remove them from search results. Use [`noindex`** meta tags**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/controlling-crawlers/meta-tags) for that. h2. [Testing Your robots.txt](#testing-your-robotstxt) 1. Check syntax: Visit `https://yoursite.com/robots.txt` to confirm it loads 2. [**Google Search Console robots.txt tester**](https://search.google.com/search-console/robots-txt) validates syntax and tests URLs 3. Verify crawlers can access: Check server logs for 200 status on `/robots.txt` h2. [Common Patterns](#common-patterns) h3. [Allow Everything (Default)](#allow-everything-default) ``` User-agent: * Disallow: ``` h3. [Block Everything](#block-everything) Useful for staging or development environments. ``` User-agent: * Disallow: / ``` See our [**security guide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/security) for more on environment protection. h3. [Block AI Training Crawlers](#block-ai-training-crawlers) [**GPTBot was the most blocked bot in 2024**](https://blog.cloudflare.com/from-googlebot-to-gptbot-whos-crawling-your-site-in-2025/), fully disallowed by 250 domains. Blocking AI training bots doesn't affect search rankings: ``` h1. Block AI model training (doesn't affect Google search) User-agent: GPTBot User-agent: ClaudeBot User-agent: CCBot User-agent: Google-Extended Disallow: / ``` `Google-Extended` is separate from `Googlebot`. blocking it won't hurt search visibility. h3. [AI Directives: Content-Usage & Content-Signal](#ai-directives-content-usage-content-signal) Two emerging standards let you express preferences about how AI systems use your content. without blocking crawlers entirely: - **[**Content-Usage**](https://ietf-wg-aipref.github.io/drafts/draft-ietf-aipref-vocab.html)** (IETF) . Uses `y`/`n` values for `train-ai` - **[**Content-Signal**](https://contentsignals.org/)** (Cloudflare) . Uses `yes`/`no` values for `search`, `ai-input`, `ai-train` ``` User-agent: * Allow: / h1. IETF aipref-vocab Content-Usage: train-ai=n h1. Cloudflare Content Signals Content-Signal: search=yes, ai-input=no, ai-train=no ``` With the [**Nuxt Robots module**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/docs/robots/guides/ai-directives), configure programmatically: nuxt.config.ts ``` export default defineNuxtConfig({ robots: { groups: [{ userAgent: '*', allow: '/', contentUsage: { 'train-ai': 'n' }, contentSignal: { 'ai-train': 'no', 'search': 'yes' } }] } }) ``` AI directives rely on voluntary compliance. Crawlers can ignore them. combine with User-agent blocks for stronger protection. h3. [Block Search, Allow Social Sharing](#block-search-allow-social-sharing) For private sites where you still want [**link previews**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/mastering-meta/social-sharing): ``` h1. Block search engines User-agent: Googlebot User-agent: Bingbot Disallow: / h1. Allow social link preview crawlers User-agent: facebookexternalhit User-agent: Twitterbot User-agent: Slackbot Allow: / ``` h3. [Optimize Crawl Budget for Large Sites](#optimize-crawl-budget-for-large-sites) If you have 10,000+ pages, [**block low-value URLs**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) to focus crawl budget on important content: ``` User-agent: * h1. Block internal search results Disallow: /search? h1. Block infinite scroll pagination Disallow: /*?page= h1. Block filtered/sorted product pages Disallow: /products?*sort= Disallow: /products?*filter= h1. Block print versions Disallow: /*/print ``` Sites under 1,000 pages don't need crawl budget optimization. --- [**Controlling Crawlers** Manage how search engines crawl and index your Nuxt app. Configure robots.txt, sitemaps, canonical URLs, and redirects for better SEO.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/controlling-crawlers) [**Sitemaps** Generate sitemaps for Nuxt with the @nuxtjs/sitemap module or server routes for dynamic content.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/robots-txt/learn-seo/nuxt/controlling-crawlers/sitemaps) **On this page** - [Quick Setup](#quick-setup) - [Robots.txt Syntax](#robotstxt-syntax) - [Security: Why robots.txt Fails](#security-why-robotstxt-fails) - [Crawling vs Indexing](#crawling-vs-indexing) - [Common Mistakes](#common-mistakes) - [Testing Your robots.txt](#testing-your-robotstxt) - [Common Patterns](#common-patterns) --- ### Meta Robots Tags in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags Description: Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Nuxt apps. h1. **Meta Robots Tags in Nuxt** Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Nuxt apps. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)9 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - `noindex` prevents indexing, `nofollow` stops link equity flow through that page - Use X-Robots-Tag HTTP header for non-HTML files (PDFs, images) - Must be server-rendered. client-only meta tags may be missed by crawlers Meta robots tags give page-level control over search engine indexing. While [**robots.txt provides site-wide rules**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags/learn-seo/nuxt/controlling-crawlers/robots-txt), the robots meta tag lets you handle individual pages differently. Use them to block filtered pages from indexing, prevent duplicate content issues on pagination, or control snippet appearance. For non-HTML files (PDFs, images) use [**X-Robots-Tag HTTP headers**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) instead. For site-wide rules stick with [**robots.txt**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags/learn-seo/nuxt/controlling-crawlers/robots-txt). h2. [Setup](#setup) Add the robots meta tag using `useSeoMeta()` in your Nuxt pages. Nuxt server-renders these tags automatically. crawlers see them in the initial HTML response. Block Indexing ``` useSeoMeta({ robots: 'noindex, follow' }) ``` Control Snippets ``` useSeoMeta({ robots: 'index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1' }) ``` h2. [How Meta Robots Tags Work](#how-meta-robots-tags-work) The robots meta tag goes in your page's `` and tells crawlers what to do with that specific page ([**Google's documentation**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag)): ``` ``` Without a robots meta tag, crawlers assume `index, follow` by default ([**MDN reference**](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/robots)). h3. [Common Directives](#common-directives) - `noindex` . Exclude from search results - `nofollow` . Don't follow links on this page - `noarchive` . Prevent cached copies - `nosnippet` . No description snippet in results - `max-snippet:[number]` . Limit snippet to N characters - `max-image-preview:[setting]` . Control image preview size (none, standard, large) - `max-video-preview:[number]` . Limit video preview to N seconds Combine multiple directives with commas. When directives conflict, the [**more restrictive one applies**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag). h3. [Critical Requirements](#critical-requirements) Must be server-side rendered. Google can execute JavaScript but does so in a [**second rendering wave**](https://medium.com/@emironic/server-side-rendering-ssr-vs-client-side-rendering-csr-why-it-matters-more-than-ever-for-ai-4dbf65142abc), which can delay or prevent proper indexing. Nuxt's SSR puts the tag in the HTML response immediately. If you block the page in robots.txt, crawlers [**never see the meta tag**](https://developers.google.com/search/docs/crawling-indexing/block-indexing). Don't combine robots.txt blocking with noindex. the noindex won't work. Target specific crawlers by replacing `robots` with a user agent token like `googlebot`. More specific tags override general ones. h2. [Noindex Follow vs Noindex Nofollow](#noindex-follow-vs-noindex-nofollow) Use `noindex, follow` when you want to block indexing but preserve link equity flow. The page won't rank, but links on it still count ([**Wolf of SEO guide**](https://wolf-of-seo.de/en/what-is/noindex-follow/)). Use `noindex, nofollow` for pages with no valuable links. login forms, checkout steps, truly private content. Noindex Follow - Preserve Links ``` // Filter pages, thank you pages, test pages useSeoMeta({ robots: 'noindex, follow' }) ``` Noindex Nofollow - Block Everything ``` // Login pages, admin sections useSeoMeta({ robots: 'noindex, nofollow' }) ``` Note that `follow` doesn't force crawling. it just allows it. Pages may still be crawled less over time if they remain noindexed. h2. [Common Use Cases](#common-use-cases) h3. [Search Results and Filtered Pages](#search-results-and-filtered-pages) Internal search results and filter combinations create duplicate content. [**Google recommends**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) blocking these with noindex or robots.txt: pages/search.vue ``` useSeoMeta({ robots: 'noindex, follow' }) ``` pages/products/[category].vue ``` const { query } = useRoute() useSeoMeta({ robots: Object.keys(query).length > 0 ? 'noindex, follow' : 'index, follow' }) ``` Combine with [**canonical tags**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags/learn-seo/nuxt/controlling-crawlers/canonical-urls) pointing to the main category page. but don't use both noindex and canonical on the same page (sends [**conflicting signals**](https://www.oncrawl.com/technical-seo/use-robots-txt-meta-robots-canonical-tags-correctly/)). h3. [Pagination](#pagination) Google no longer uses `rel="next"` and `rel="prev"` tags ([**Google documentation**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading)). Instead, link to next/previous pages with regular `` tags and let Google discover the sequence naturally. You don't need to noindex pagination pages. Google treats them as part of the sequence. Only noindex if the paginated content is duplicate or low-value. h3. [User-Generated Content](#user-generated-content) Limit snippet length and prevent caching for dynamic user profiles: pages/user/[id]/profile.vue ``` useSeoMeta({ robots: 'index, follow, noarchive, max-snippet:50' }) ``` h2. [X-Robots-Tag for Non-HTML Files](#x-robots-tag-for-non-html-files) The meta robots tag only works in HTML. For PDFs, images, videos, or other files, use the [**X-Robots-Tag HTTP header**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) instead ([**MDN reference**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Robots-Tag)): ``` X-Robots-Tag: noindex, nofollow ``` X-Robots-Tag supports the same directives as the meta tag. It's also useful for bulk operations. you can apply it to entire directories or file patterns via server configuration. Don't use both meta robots and X-Robots-Tag on the same resource. easy to create [**conflicting instructions**](https://www.semrush.com/blog/robots-meta/). h2. [Verification](#verification) Use [**Google Search Console's URL Inspection tool**](https://support.google.com/webmasters/answer/9012289) to verify your robots meta tag: 1. Enter the URL 2. Check "Indexing allowed?" status 3. View the rendered HTML to confirm tag appears If a noindexed page still appears in search results, it hasn't been recrawled yet. Depending on the page's importance, [**it can take months**](https://developers.google.com/search/docs/crawling-indexing/block-indexing) for Google to revisit and apply the directive. Request a recrawl via the URL Inspection tool. Monitor "Excluded by 'noindex' tag" reports in Search Console. Sudden spikes indicate accidental noindexing of important pages. --- [**Sitemaps** Generate sitemaps for Nuxt with the @nuxtjs/sitemap module or server routes for dynamic content.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags/learn-seo/nuxt/controlling-crawlers/sitemaps) [**Canonical Link Tag** Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/meta-tags/learn-seo/nuxt/controlling-crawlers/canonical-urls) **On this page** - [Setup](#setup) - [How Meta Robots Tags Work](#how-meta-robots-tags-work) - [Noindex Follow vs Noindex Nofollow](#noindex-follow-vs-noindex-nofollow) - [Common Use Cases](#common-use-cases) - [X-Robots-Tag for Non-HTML Files](#x-robots-tag-for-non-html-files) - [Verification](#verification) --- ### Canonical URLs in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls Description: Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Nuxt. h1. **Canonical URLs in Nuxt** Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Nuxt. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Nov 3, 2024** Updated **Jan 4, 2026** **What you'll learn** - Canonical tags are hints, not directives. Google may choose differently - Always use absolute URLs ([**https://mysite.com/page**](https://mysite.com/page), not /page) - Self-referencing canonicals recommended even for unique pages Canonical URLs tell search engines which version of a page is the primary copy when duplicate content exists at multiple URLs. [**67.6% of websites have duplicate content issues**](https://www.womenintechseo.com/knowledge/dealing-with-duplicate-content-canonicalization-in-detail/) due to poor canonicalization. Use canonicals for URLs with query parameters (filters, sorting), same content on multiple paths, paginated sequences, and cross-domain syndication. For redirecting users, use [**HTTP redirects**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/redirects) instead. For blocking pages from search, use [**meta robots**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/meta-tags) with noindex. [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/docs/seo-utils/getting-started/introduction) h2. [Quick Setup](#quick-setup) Add canonical URLs to your Nuxt pages using `useHead()`: Basic Usage ``` useHead({ link: [ { rel: 'canonical', href: 'https://mysite.com/products/phone' } ] }) ``` With Query Params ``` // Keep sort parameter in canonical useHead({ link: [ { rel: 'canonical', href: \`https://mysite.com/products?sort=${sort}\` } ] }) ``` Cross Domain ``` useHead({ link: [ { rel: 'canonical', href: 'https://otherdomain.com/original-article' } ] }) ``` h2. [Understanding Canonical URLs](#understanding-canonical-urls) A canonical URL is implemented as a link tag in your page's head: ``` ``` h3. [Canonical Tags Are Hints, Not Directives](#canonical-tags-are-hints-not-directives) Google treats canonicals as [**strong signals, not mandatory rules**](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls). Google's Joachim Kupke: "It's a hint that we honor strongly. We'll take your preference into account, in conjunction with other signals, when calculating the most relevant page to display in search results." Google may choose a different canonical than you specify when: - Content differs significantly between URLs - Multiple conflicting canonical declarations exist - Google believes a different page is more authoritative [**Google uses the canonical page**](https://developers.google.com/search/docs/crawling-indexing/canonicalization) as the main source to evaluate content and quality. Non-canonical URLs may still be crawled but usually won't appear in search results. h3. [Self-Referencing Canonicals](#self-referencing-canonicals) [**Self-referencing canonicals are recommended**](https://searchengineland.com/canonicalization-seo-448161) even for unique pages. They establish a clear preferred URL and prevent search engines from guessing when tracking parameters or alternate URL formats appear. Google's John Mueller: "I recommend using a self-referential canonical because it really makes it clear to us which page you want to have indexed, or what the URL should be when it is indexed." h3. [Important Notes](#important-notes) - Must use absolute URLs ([**Google documentation**](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)) - Only one canonical per page (multiple declarations cause Google to ignore all hints) - Must be server-side rendered (crawlers don't run JavaScript) - Include canonical pages in [**sitemaps**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/sitemaps), exclude non-canonical pages - Don't combine canonical with noindex [**meta robots**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/meta-tags). Sends conflicting signals h2. [Common Patterns](#common-patterns) h3. [Filter and Sort Parameters](#filter-and-sort-parameters) pages/products/[category].vue ``` const route = useRoute() const { sort, filter, page } = route.query const category = route.params.category useHead({ link: [{ rel: 'canonical', // Only include sort in canonical, remove filter and pagination href: sort ? \`https://mysite.com/products/${category}?sort=${sort}\` : \`https://mysite.com/products/${category}\` }] }) ``` h3. [Pagination](#pagination) Use self-referencing canonicals on paginated pages. [**Don't point them all to page 1**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). Each page in the sequence has unique content and should be indexed separately. pages/blog/[page].vue ``` const route = useRoute() const page = route.params.page || '1' useHead({ link: [{ rel: 'canonical', // Each page references itself href: \`https://mysite.com/blog${page === '1' ? '' : \`?page=${page}\`}\` }] }) ``` Google [**deprecated rel=prev/next in 2019**](https://searchengineland.com/pagination-seo-what-you-need-to-know-453707) and now automatically recognizes pagination patterns. h3. [Mobile/Desktop Versions](#mobiledesktop-versions) If you have separate mobile URLs (m.domain.com), [**keep the desktop URL as canonical**](https://www.lumar.io/office-hours/separate-mobile/) even with mobile-first indexing. Google uses canonicals to understand which pages belong together, then internally selects the mobile version for indexing. pages/products/[id].vue ``` const route = useRoute() const id = route.params.id useHead({ link: [{ rel: 'canonical', // Mobile site (m.mysite.com) points to desktop href: \`https://mysite.com/products/${id}\` }] }) ``` [**Don't switch canonicals from desktop to mobile URLs**](https://www.seroundtable.com/google-m-dot-urls-the-canonical-37809.html). Google advises against this. Use responsive design with a single URL instead. h3. [Cross-Domain Syndication](#cross-domain-syndication) [**Google no longer recommends cross-domain canonicals**](https://searchengineland.com/google-no-longer-recommends-canonical-tags-for-syndicated-content-406491) for syndicated content. Instead, syndication partners should use noindex meta robots to block indexing. pages/article/[slug].vue ``` // If syndicating TO other sites, have them use noindex useSeoMeta({ robots: 'noindex, follow' }) ``` pages/original-article.vue ``` // If this IS the original, use self-referencing canonical useHead({ link: [{ rel: 'canonical', href: 'https://mysite.com/articles/original-slug' }] }) ``` Exception: News publishers syndicating to Google News should still use cross-domain canonicals per [**Google's news publisher guidance**](https://developers.google.com/search/blog/2009/12/handling-legitimate-cross-domain). h2. [Testing](#testing) h3. [Using Google Search Console](#using-google-search-console) Use the [**URL Inspection tool**](https://support.google.com/webmasters/answer/9012289) to verify canonical implementation: 1. Enter the URL you want to inspect 2. Check the "Page indexing" section 3. Compare "User-declared canonical" vs "Google-selected canonical" 4. If they don't match, [**Google found conflicting signals**](https://developers.google.com/search/blog/2019/03/how-to-discover-suggest-google-selected) Note: [**Live tests won't show Google-selected canonical**](https://www.conductor.com/academy/url-inspection-tool/). You'll only see this for already indexed pages. h3. [When Google Selects a Different Canonical](#when-google-selects-a-different-canonical) Common reasons Google ignores your canonical preference: - Content differs significantly between URLs - Canonical URL returns non-200 status code - Canonical URL uses HTTP instead of HTTPS - Multiple conflicting canonical declarations on page - Canonical chain detected (A → B → C) [**Fix canonicalization issues**](https://developers.google.com/search/docs/crawling-indexing/canonicalization-troubleshooting) by checking internal links, sitemap inclusion, and removing conflicting signals. h3. [Important Checks](#important-checks) - Validate absolute URL format - Check for canonical chains (A → B → C) - Verify SSR implementation (view page source, not inspected HTML) - Test with and without parameters - Don't combine canonical with noindex [**meta robots**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/meta-tags) h2. [Handling Edge Cases](#handling-edge-cases) h3. [Multiple Language Versions](#multiple-language-versions) For multilingual sites, combine canonicals with hreflang: ``` useHead({ link: [ { rel: 'canonical', href: 'https://mysite.com/en/page' }, { rel: 'alternate', hreflang: 'fr', href: 'https://mysite.com/fr/page' } ] }) ``` h3. [Protocol/WWW Variations](#protocolwww-variations) Handle through [**server redirects**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/redirects) in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // Redirect non-www to www 'http://mysite.com/**': { redirect: 'https://www.mysite.com/**' } } }) ``` Or handle with Nitro middleware: server/middleware/canonicalize.ts ``` export default defineEventHandler((event) => { const host = getRequestHost(event) if (!host.startsWith('www.')) { return sendRedirect(event, \`https://www.${host}${event.path}\`, 301) } }) ``` h3. [Dynamic Canonicals](#dynamic-canonicals) For dynamic routes, create a composable for consistent canonical URLs: composables/useCanonical.ts ``` export function useCanonical(path: string) { const config = useRuntimeConfig() return { link: [{ rel: 'canonical', href: \`${config.public.siteUrl}${path}\` }] } } ``` nuxt.config.ts ``` export default defineNuxtConfig({ runtimeConfig: { public: { siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'https://mysite.com' } } }) ``` h2. [Quick Check](#quick-check) **Quick Check** **You're permanently moving /old-page to /new-page. What should you use?** - `A canonical tag pointing to /new-page`: Canonicals are hints, not redirects. Users would still see the old URL and Google might ignore the hint - `A 301 redirect in routeRules`: Correct! Redirects move users and pass PageRank. Use canonicals for duplicate content, redirects for moved pages - `Both a canonical and a redirect`: Using both creates confusion. Pick one: redirect for moved content, canonical for duplicates --- [**Robot Meta Tag** Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Nuxt apps.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/meta-tags) [**HTTP Redirects** 301 redirects preserve SEO value when content moves. Nuxt handles server-side redirects through routeRules to pass link equity and maintain rankings.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/redirects) **On this page** - [Quick Setup](#quick-setup) - [Understanding Canonical URLs](#understanding-canonical-urls) - [Common Patterns](#common-patterns) - [Testing](#testing) - [Handling Edge Cases](#handling-edge-cases) - [Quick Check](#quick-check) --- ### HTTP Redirects for SEO in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects Description: 301 redirects preserve SEO value when content moves. Nuxt handles server-side redirects through routeRules to pass link equity and maintain rankings. h1. **HTTP Redirects for SEO in Nuxt** 301 redirects preserve SEO value when content moves. Nuxt handles server-side redirects through routeRules to pass link equity and maintain rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)9 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - 301 redirects pass ~100% link equity. use for permanent content moves - Server-side redirects required. JavaScript redirects don't pass SEO value - Avoid redirect chains (A→B→C). redirect directly to the final destination 301 redirects pass nearly 100% of link equity to the new URL ([**Google confirms**](https://www.searchenginejournal.com/301-redirect-pagerank/275503/)), preserving your SEO value when content moves. Server-side implementation required for search engines to recognize them. Use for permanent moves: site migrations, URL restructuring, domain changes, deleted pages with replacements. For duplicate content use [**canonical tags**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects/learn-seo/nuxt/controlling-crawlers/canonical-urls). For temporary moves use 302 redirects. h2. [Quick Setup](#quick-setup) Nuxt handles redirects through `routeRules` in your config or server middleware: ``` export default defineNuxtConfig({ routeRules: { '/old-page': { redirect: { to: '/new-page', statusCode: 301 } }, '/blog/**': { redirect: '/articles/**' } } }) ``` ``` export default defineEventHandler((event) => { if (event.path === '/old-page') { return sendRedirect(event, '/new-page', 301) } if (event.path.startsWith('/blog/')) { const slug = event.path.replace('/blog/', '') return sendRedirect(event, \`/articles/${slug}\`, 301) } }) ``` h2. [301 vs 302 Redirects](#_301-vs-302-redirects) h3. [When to Use Each](#when-to-use-each) **301 (Permanent)** - Transfers ~100% of link equity to new URL ([**Google**](https://www.searchenginejournal.com/301-redirect-pagerank/275503/)) - Permanent content moves - Domain migrations - URL structure changes - Deleted pages with direct replacements - HTTP to HTTPS upgrades **302 (Temporary)** - Keeps SEO value on original URL - A/B testing - Temporary promotions - Maintenance pages - Out-of-stock product redirects If a 302 stays active for months with no plans to revert, switch to 301 ([**SEO Clarity**](https://www.seoclarity.net/resources/knowledgebase/use-301-redirect-vs-302-redirect-15683/)). Search engines may eventually treat long-term 302s as permanent anyway. **307/308** - Like 302/301 but preserve HTTP method (POST remains POST). Rarely needed for typical SEO work. h3. [How Long to Keep Redirects Active](#how-long-to-keep-redirects-active) Google recommends keeping 301 redirects active for at least one year ([**Search Central**](https://www.searchenginejournal.com/google-keep-301-redirects-in-place-for-a-year/428998/)). This ensures Google transfers all ranking signals and recrawls links pointing to old URLs. Keep redirects longer if: - External sites still link to old URLs - Old URLs receive referral traffic - High-value pages with many backlinks Removing redirects before Google processes them loses the transferred SEO value permanently. h3. [Avoid Redirect Chains](#avoid-redirect-chains) Redirect chains (A → B → C) waste [**crawl budget**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects/learn-seo/nuxt/controlling-crawlers#crawler-budget) and slow page speed ([**Gotch SEO**](https://www.gotchseo.com/redirect-chains/)). Each hop degrades Core Web Vitals, particularly LCP and TTFB. Google follows up to 5 redirect hops, then aborts ([**Hike SEO**](https://www.hikeseo.co/learn/technical/redirect-chains)). Redirect directly to final destination: Bad: ``` /old → /interim → /final ``` Good: ``` /old → /final /interim → /final ``` h2. [Common Patterns](#common-patterns) h3. [Domain Migration](#domain-migration) ``` export default defineNuxtConfig({ routeRules: { // Redirect entire old domain to new domain '/**': { redirect: { to: path => \`https://new-domain.com${path}\`, statusCode: 301 } } } }) ``` ``` export default defineEventHandler((event) => { const host = getRequestHost(event) if (host === 'old-domain.com') { return sendRedirect(event, \`https://new-domain.com${event.path}\`, 301) } }) ``` h3. [URL Structure Changes](#url-structure-changes) ``` export default defineNuxtConfig({ routeRules: { '/old': { redirect: { to: '/new', statusCode: 301 } }, '/blog/**': { redirect: '/articles/**' }, '/products/**': { redirect: '/shop/**' } } }) ``` ``` export default defineEventHandler((event) => { if (event.path === '/old') { return sendRedirect(event, '/new', 301) } if (event.path.startsWith('/blog/')) { const slug = event.path.replace('/blog/', '') return sendRedirect(event, \`/articles/${slug}\`, 301) } if (event.path.startsWith('/products/')) { const id = event.path.replace('/products/', '') return sendRedirect(event, \`/shop/${id}\`, 301) } }) ``` h3. [HTTPS Enforcement](#https-enforcement) ``` export default defineNuxtConfig({ routeRules: { '/**': { headers: { 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains' } } } }) ``` ``` export default defineEventHandler((event) => { if (getHeader(event, 'x-forwarded-proto') !== 'https') { const host = getRequestHost(event) return sendRedirect(event, \`https://${host}${event.path}\`, 301) } }) ``` Learn more about HTTPS in our [**security guide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects/learn-seo/nuxt/security#https). h3. [WWW Standardization](#www-standardization) ``` export default defineNuxtConfig({ routeRules: { '/**': { redirect: { to: (path, { host }) => { if (!host?.startsWith('www.')) { return \`https://www.${host}${path}\` } }, statusCode: 301 } } } }) ``` ``` export default defineEventHandler((event) => { const host = getRequestHost(event) if (!host.startsWith('www.')) { return sendRedirect(event, \`https://www.${host}${event.path}\`, 301) } }) ``` h2. [Testing Redirects](#testing-redirects) Verify redirects work correctly before deploying: 1. **Check status code** - Use browser dev tools Network tab, confirm 301 or 302 2. **Test destination** - Ensure redirect points to correct final URL 3. **Verify no chains** - Confirm single hop to destination 4. **Test trailing slashes** - Check with and without trailing slash 5. **Check query parameters** - Verify parameters carry over if needed h3. [Tools](#tools) - [**Google Search Console**](https://search.google.com/search-console) - Monitor crawl errors and redirect issues - Browser Dev Tools Network tab - Check status codes and headers - [**Screaming Frog**](https://www.screamingfrog.co.uk/seo-spider/) - Bulk redirect testing and chain detection - curl - `curl -I https://example.com/old-page` shows redirect headers h2. [Common Mistakes](#common-mistakes) h3. [Redirecting to Irrelevant Pages](#redirecting-to-irrelevant-pages) Redirecting deleted pages to your homepage damages SEO and user experience. Google may treat this as a soft 404, ignoring link equity transfer ([**Victorious**](https://victorious.com/blog/301-redirects/)). nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // ❌ Bad - mass redirects to homepage '/blog/**': { redirect: '/' }, // ✅ Good - redirect to relevant content '/blog/vue-tips': { redirect: '/articles/vue-tips' }, '/blog/seo-guide': { redirect: '/articles/seo-guide' } } }) ``` h3. [Redirect Loops](#redirect-loops) Circular redirects break your site: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // ❌ Bad - creates infinite loop '/page-a': { redirect: '/page-b' }, '/page-b': { redirect: '/page-a' }, // ✅ Good - both redirect to final destination '/page-a': { redirect: '/final' }, '/page-b': { redirect: '/final' } } }) ``` h3. [Using Client-Side Redirects for SEO](#using-client-side-redirects-for-seo) JavaScript redirects don't pass link equity reliably. Search engines may not execute JavaScript before indexing. Always use server-side redirects (301/302 status codes) for SEO purposes. h3. [Not Updating Internal Links](#not-updating-internal-links) Relying on redirects for internal links wastes server resources and slows page speed. Update internal links to point directly to new URLs, keep redirects for external links and old bookmarks. --- [**Canonical Link Tag** Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects/learn-seo/nuxt/controlling-crawlers/canonical-urls) [**Duplicate Content** Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/redirects/learn-seo/nuxt/controlling-crawlers/duplicate-content) **On this page** - [Quick Setup](#quick-setup) - [301 vs 302 Redirects](#_301-vs-302-redirects) - [Common Patterns](#common-patterns) - [Testing Redirects](#testing-redirects) - [Common Mistakes](#common-mistakes) --- ### SEO-Friendly URLs in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure Description: Create search-optimized URLs using file-based routing. Learn slug formatting, parameter handling, and route patterns that improve rankings. h1. **SEO-Friendly URLs in Nuxt** Create search-optimized URLs using file-based routing. Learn slug formatting, parameter handling, and route patterns that improve rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** **What you'll learn** - Use hyphens (not underscores), lowercase, under 60 characters - Path segments for indexed content, query params for filters/sorting - Set canonical URLs to consolidate filter variations like `?sort=price` URLs appear in search results before users click. `/blog/vue-seo-guide` tells users what to expect. `/p?id=847` doesn't. Search engines use URLs to understand page hierarchy and relevance. Well-structured URLs improve click-through rates by up to 15%. Nuxt's file-based routing generates SEO-friendly URLs automatically from your `pages/` directory structure. h2. [Quick Setup](#quick-setup) Create SEO-friendly slugs from titles: composables/useSeoSlug.ts ``` export function useSeoSlug(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens .replace(/^-+|-+$/g, '') // Trim leading/trailing hyphens .substring(0, 60) // Keep under 60 chars } ``` File-based routing in `pages/`: ``` pages/ blog/ [slug].vue → /blog/vue-seo-guide products/ [category]/ [slug].vue → /products/phones/iphone-15 ``` pages/blog/[slug].vue ``` ``` h2. [URL Formatting Rules](#url-formatting-rules) h3. [Hyphens Over Underscores](#hyphens-over-underscores) [**Google treats hyphens as word separators**](https://developers.google.com/search/docs/crawling-indexing/url-structure). Underscores connect words into single terms. ``` ✅ /performance-optimization → "performance" + "optimization" ❌ /performance_optimization → "performanceoptimization" ``` Google's Matt Cutts confirmed in 2011: "We use the words in a URL as a very lightweight factor... we can't easily segment at underscores." **Nuxt file structure:** ``` pages/ learn-vue-router.vue ✅ Good learn_vue_router.vue ❌ Bad ``` h3. [Lowercase Only](#lowercase-only) URLs are case-sensitive. `/About`, `/about`, and `/ABOUT` are different pages. This creates duplicate content issues. ``` ✅ /about ✅ /products/phones ❌ /About ❌ /products/Phones ``` Enforce lowercase in your slug helper: ``` export function useSeoSlug(text: string): string { return text .toLowerCase() // ← Always lowercase .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') } ``` h3. [Keep URLs Short](#keep-urls-short) URLs under 60 characters perform better in search results. Longer URLs get truncated with ellipsis. **Length comparison:** | **URL** | **Length** | **Result** | | --- | --- | --- | | `/blog/vue-seo` | 14 chars | ✅ Displays fully | | `/blog/comprehensive-guide-to-vue-seo-optimization` | 50 chars | ⚠️ Works but verbose | | `/blog/a-comprehensive-guide-to-vue-server-side-rendering-seo-optimization-best-practices` | 98 chars | ❌ Truncated in results | ``` export function useSeoSlug(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .substring(0, 60) // ← Limit to 60 chars } ``` **When longer URLs make sense:** ``` ✅ /docs/getting-started/installation-guide (clear hierarchy) ✅ /blog/2025/fixing-vue-hydration-mismatch (date + topic) ❌ /the-ultimate-comprehensive-complete-guide (keyword stuffing) ``` h3. [Keywords Near the Start](#keywords-near-the-start) [**Including keywords in URLs provides a lightweight ranking boost**](https://www.americaneagle.com/insights/blog/post/creating-seo-friendly-urls). Front-load important terms. ``` ✅ /vue-router-seo-guide ✅ /seo/vue-best-practices ❌ /guides-and-tutorials-for-seo-in-vue-router ``` But don't sacrifice readability: ``` pages/ vue-seo/ [topic].vue ✅ Natural keyword placement vue-seo-guide-vue-router-seo-tutorial.vue ❌ Keyword stuffing ``` h3. [Avoid Dates (Usually)](#avoid-dates-usually) Dates in URLs prevent content updates. `/blog/2024/vue-guide` becomes outdated when you refresh it in 2025. ``` ❌ /blog/2024/vue-router-guide (looks stale) ❌ /blog/2024/12/17/post-title (prevents evergreen updates) ✅ /blog/vue-router-guide (can be updated anytime) ``` **Exception:** Time-sensitive content like news, events, changelogs: ``` pages/ changelog/ [year]/ [month]/ [slug].vue → /changelog/2025/12/new-feature events/ 2025/ [slug].vue → /events/2025/nuxt-conf blog/ [slug].vue → /blog/vue-router-guide (evergreen) ``` [**Removing dates allows republishing old posts**](https://www.octaria.com/blog/seo-friendly-url-structure-best-practices-2025) with new content without changing URLs. a strong SEO strategy. h2. [Path Segments vs Query Parameters](#path-segments-vs-query-parameters) ![Path vs Query Parameter Decision](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure/images/learn-seo/vue/path-vs-query-decision.svg) Search engines prefer path segments over query parameters. [**Path segments are indexed and ranked**](https://www.searchenginejournal.com/technical-seo/url-parameter-handling/). Query parameters often cause duplicate content. **Comparison:** | **Type** | **Example** | **SEO Impact** | | --- | --- | --- | | Path segments | `/products/phones/iphone-15` | ✅ Clean, indexed, ranks well | | Query parameters | `/products?category=phones&id=15` | ⚠️ Duplicate content risk | | Mixed | `/products/phones?sort=price` | ✅ Path for content, query for filters | **Problems with query parameters:** ``` /products /products?sort=price /products?sort=date /products?page=2 /products?sort=price&page=2 ``` Five URLs, same content. Google sees [**duplicate content and wastes crawl budget**](https://www.seozoom.com/a-guide-to-query-strings-url-parameters-for-the-seo/). h3. [Nuxt Dynamic Routes](#nuxt-dynamic-routes) Use dynamic segments for content that should be indexed: ``` pages/ products/ [category]/ [slug].vue → /products/phones/iphone-15 blog/ [year]/ [month]/ [slug].vue → /blog/2025/12/nuxt-seo-guide docs/ [section]/ [page].vue → /docs/getting-started/installation ``` h3. [Query Parameters for Filters](#query-parameters-for-filters) Use query parameters for sorting, filtering, pagination. features that modify display without changing core content: pages/products/[category].vue ``` ``` Set [**canonical URLs**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure/learn-seo/nuxt/controlling-crawlers/canonical-urls) to consolidate ranking signals: ``` // Filter/sort variations point to base URL useHead({ link: [{ rel: 'canonical', href: \`https://mysite.com/products/${category}\` }] }) ``` h2. [Dynamic Routes](#dynamic-routes) Nuxt's file-based routing creates SEO-friendly URLs automatically. h3. [Basic Dynamic Segments](#basic-dynamic-segments) ``` pages/ blog/ [slug].vue → /blog/:slug ``` pages/blog/[slug].vue ``` ``` h3. [Nested Dynamic Routes](#nested-dynamic-routes) ``` pages/ products/ [category]/ [slug].vue → /products/:category/:slug ``` Generates hierarchical URLs: - `/products/electronics/laptop` - `/products/clothing/jacket` pages/products/[category]/[slug].vue ``` ``` h3. [Catch-All Routes](#catch-all-routes) ``` pages/ search/ [...params].vue → /search/:params* ``` Matches: - `/search/vue-router` - `/search/vue-router/recent` - `/search/vue-router/recent/2025` **Important:** Catch-all routes create multiple URLs for similar content. Use canonical tags: pages/search/[...params].vue ``` ``` h2. [Slug Generation Patterns](#slug-generation-patterns) h3. [From CMS Content](#from-cms-content) composables/useSlugFromTitle.ts ``` export function useSlugFromTitle(title: string): string { return title .toLowerCase() .trim() // Replace accented characters .normalize('NFD') .replace(/[\u0300-\u036F]/g, '') // Replace non-alphanumeric with hyphens .replace(/[^a-z0-9]+/g, '-') // Remove leading/trailing hyphens .replace(/^-+|-+$/g, '') // Limit length .substring(0, 60) } ``` Example usage ``` const title = 'Vue Router: The Complete Guide (2025)' const slug = useSlugFromTitle(title) // Result: "vue-router-the-complete-guide-2025" ``` Composables in `composables/` are auto-imported in Nuxt. h3. [Preserving Non-ASCII Characters](#preserving-non-ascii-characters) For international content, [**use UTF-8 encoding in URLs**](https://developers.google.com/search/docs/crawling-indexing/url-structure): composables/useInternationalSlug.ts ``` export function useInternationalSlug(text: string): string { return text .toLowerCase() .trim() // Keep Unicode letters and numbers .replace(/[^\p{L}\p{N}]+/gu, '-') .replace(/^-+|-+$/g, '') .substring(0, 60) } ``` ``` // Preserves non-ASCII useInternationalSlug('Vue 路由指南') // Result: "vue-路由指南" // vs ASCII-only useSlugFromTitle('Vue 路由指南') // Result: "vue" ``` h3. [Handling Duplicates](#handling-duplicates) Append numbers when slugs collide: server/api/posts.ts ``` async function generateUniqueSlug(title: string): Promise { const baseSlug = useSlugFromTitle(title) let slug = baseSlug let counter = 1 while (await slugExists(slug)) { slug = \`${baseSlug}-${counter}\` counter++ } return slug } ``` Results: - First post: `/blog/vue-router-guide` - Second post with same title: `/blog/vue-router-guide-2` h2. [What NOT to Do](#what-not-to-do) **Don't use uppercase letters:** ``` ❌ /Blog/Vue-SEO-Guide ❌ /PRODUCTS/phones ✅ /blog/vue-seo-guide ✅ /products/phones ``` **Don't expose internal IDs:** ``` ❌ /products/db-id-84792 ❌ /posts?id=12345 ✅ /products/laptop-pro-15 ✅ /blog/vue-seo-guide ``` **Don't create infinite parameter variations:** ``` ❌ /products?color=red&size=large&material=cotton&style=casual ✅ /products/red-cotton-casual-shirt ``` **Don't change URLs without redirects:** nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/old-blog-post': { redirect: '/blog/new-post' }, '/old-path/**': { redirect: '/new-path/**' } } }) ``` These are server-side redirects that send proper 301 status codes. [**Learn more about redirects**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure/learn-seo/nuxt/controlling-crawlers/redirects). **Don't use special characters:** ``` ❌ /blog/vue&react-comparison ❌ /products/50%-off-sale! ✅ /blog/vue-react-comparison ✅ /products/50-percent-off-sale ``` h2. [Testing URL Structure](#testing-url-structure) **View in search results:** ``` h1. Test how Google displays your URLs site:yoursite.com "vue router" ``` **Check canonicalization:** Use [**Google Search Console URL Inspection**](https://search.google.com/search-console) to verify: - User-declared canonical matches your intent - Google-selected canonical agrees with your preference - No conflicting signals from redirects or alternates **Validate slug generation:** tests/slugs.test.ts ``` import { describe, expect, it } from 'vitest' import { useSlugFromTitle } from '~/composables/useSlugFromTitle' describe('useSlugFromTitle', () => { it('converts title to lowercase slug', () => { expect(useSlugFromTitle('Vue Router Guide')) .toBe('vue-router-guide') }) it('replaces spaces with hyphens', () => { expect(useSlugFromTitle('Learn Vue Router')) .toBe('learn-vue-router') }) it('removes special characters', () => { expect(useSlugFromTitle('Vue & React!')) .toBe('vue-react') }) it('limits length to 60 chars', () => { const longTitle = 'A'.repeat(100) expect(useSlugFromTitle(longTitle).length).toBe(60) }) }) ``` --- [**Routes & Rendering** URL patterns, rendering modes, and routing decisions that affect your Nuxt site's search rankings. SSR vs SSG vs SPA explained.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure/learn-seo/nuxt/routes-and-rendering) [**Pagination** How to implement SEO-friendly pagination in Nuxt using canonical tags, self-referencing URLs, and proper link structure.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/url-structure/learn-seo/nuxt/routes-and-rendering/pagination) **On this page** - [Quick Setup](#quick-setup) - [URL Formatting Rules](#url-formatting-rules) - [Path Segments vs Query Parameters](#path-segments-vs-query-parameters) - [Dynamic Routes](#dynamic-routes) - [Slug Generation Patterns](#slug-generation-patterns) - [What NOT to Do](#what-not-to-do) - [Testing URL Structure](#testing-url-structure) --- ### Duplicate Content SEO in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content Description: Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling. h1. **Duplicate Content SEO in Nuxt** Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)15 mins read Published **Dec 17, 2025** **What you'll learn** - 67.6% of websites have duplicate content issues. it dilutes ranking signals - Google doesn't penalize but picks which version to index (often not the one you want) - Use 301 redirects for permanent moves, canonical tags for duplicates you need to keep [**67.6% of websites have duplicate content issues**](https://www.womenintechseo.com/knowledge/dealing-with-duplicate-content-canonicalization-in-detail/). Same content at different URLs splits ranking signals and wastes crawl budget. Google picks which version to show. often not the one you want. [**Google doesn't penalize duplicate content**](https://www.hostpapa.com/blog/marketing/does-duplicate-content-hurt-seo/) unless you're deliberately scraping other sites. But it hurts SEO by diluting link equity across multiple URLs and confusing search engines about which page to rank. h2. [Common Causes](#common-causes) h3. [URL Variations](#url-variations) **www vs non-www** `www.mysite.com` and `mysite.com` are [**treated as separate sites**](https://yoast.com/video/ask-yoast-use-www-or-not/). Choose one, redirect the other. **HTTP vs HTTPS** `http://mysite.com` and `https://mysite.com` create duplicates. Always redirect HTTP to HTTPS. **Trailing slashes** `/products` and `/products/` are different URLs. [**Pick one format site-wide**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/routes-and-rendering/trailing-slashes). Nuxt handles these redirects in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ nitro: { prerender: { autoSubfolderIndex: false } }, routeRules: { // Force HTTPS redirect '/**': { redirect: { statusCode: 301 }, headers: { 'Strict-Transport-Security': 'max-age=31536000' } } } }) ``` For www vs non-www, configure at the server level (Netlify, Vercel, Cloudflare). h3. [Query Parameters](#query-parameters) [**URL parameters create exponential duplicates**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/routes-and-rendering/query-parameters). Three filters generate 8 combinations. Add sorting and pagination. hundreds of URLs. ``` /products /products?color=red /products?color=red&size=large /products?color=red&size=large&sort=price /products?color=red&size=large&sort=price&page=2 ``` **Fix: Canonical tags** pages/products.vue ``` ``` Or [**block filtered pages from indexing**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/controlling-crawlers/meta-tags): pages/products.vue ``` ``` h3. [Parameter Order](#parameter-order) `?sort=price&filter=red` and `?filter=red&sort=price` are identical content, different URLs. **Fix: Enforce consistent parameter order** composables/useCanonicalParams.ts ``` export function useCanonicalParams(params: Record) { const siteUrl = useSiteConfig().url const route = useRoute() // Define parameter order const paramOrder = ['category', 'sort', 'filter', 'page'] const orderedParams = Object.fromEntries( Object.entries(params) .sort(([a], [b]) => { const indexA = paramOrder.indexOf(a) const indexB = paramOrder.indexOf(b) if (indexA === -1) return 1 if (indexB === -1) return -1 return indexA - indexB }) ) const queryString = new URLSearchParams(orderedParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${siteUrl}${route.path}?${queryString}\` : \`${siteUrl}${route.path}\` }] } } ``` h3. [Tracking Parameters](#tracking-parameters) Analytics params (`utm_source`, `fbclid`, `gclid`) don't change content but create duplicate URLs. **Fix: Strip from canonical** composables/useCanonicalUrl.ts ``` export function useCanonicalUrl(path: string) { const siteUrl = useSiteConfig().url const route = useRoute() const trackingParams = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'msclkid', 'mc_cid', 'mc_eid', '_ga', 'ref' ] const cleanParams = Object.fromEntries( Object.entries(route.query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${siteUrl}${path}?${queryString}\` : \`${siteUrl}${path}\` }] } } ``` Better: [**Redirect tracking params at the server level**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/routes-and-rendering/query-parameters#server-side-parameter-handling) for proper 301 status codes. h3. [Pagination](#pagination) [**Each paginated page has unique content**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/routes-and-rendering/pagination). Use self-referencing canonicals. don't point page 2 to page 1. pages/blog.vue ``` ``` h3. [Print and Mobile Versions](#print-and-mobile-versions) Printer-friendly URLs (`/article?print=true`) and mobile subdomains (`m.mysite.com`) create duplicates. **Fix: Canonical to desktop version** pages/article.vue ``` ``` For print, use CSS `@media print` instead of separate URLs. h3. [Session IDs and Click Tracking](#session-ids-and-click-tracking) Session IDs in URLs create infinite variations. ``` /products?sessionid=abc123 /products?sessionid=xyz789 /products?sessionid=def456 ``` **Fix: Don't put session IDs in URLs.** Use cookies. If unavoidable, [**block with robots.txt**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/controlling-crawlers/robots-txt): public/robots.txt ``` User-agent: * Disallow: /*?sessionid= Disallow: /*&sessionid= Disallow: /*?sid= Disallow: /*&sid= ``` Or use the Robots module: [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/docs/robots/getting-started/introduction) h2. [Finding Duplicate Content](#finding-duplicate-content) h3. [Google Search Console](#google-search-console) [**Use the Page Indexing report**](https://support.google.com/webmasters/answer/12642436) to identify duplicates: 1. Open Search Console 2. Go to "Indexing" → "Pages" 3. Look for: - "Duplicate, Google chose different canonical than user" - "Duplicate without user-selected canonical" - "Alternate page with proper canonical tag" Click each category to see affected URLs. If Google chose a different canonical than you specified, [**conflicting signals exist**](https://developers.google.com/search/blog/2019/03/how-to-discover-suggest-google-selected). **Using URL Inspection:** 1. Enter any URL 2. Check "User-declared canonical" vs "Google-selected canonical" 3. If they differ, Google found stronger signals pointing to a different URL h3. [Screaming Frog](#screaming-frog) [**Screaming Frog detects exact and near-duplicate content**](https://www.screamingfrog.co.uk/seo-spider/tutorials/how-to-check-for-duplicate-content/): **Exact duplicates** . Pages with identical HTML (MD5 hash match) **Near duplicates** . Pages with 90%+ similarity (minhash algorithm) **Setup:** 1. Enable near duplicates: `Config > Content > Duplicates` 2. Crawl your site 3. Go to "Content" tab 4. Filter by "Exact Duplicates" or "Near Duplicates" Check these columns: - `Closest Similarity Match` . Percentage match to most similar page - `No. Near Duplicates` . Count of similar pages - `Hash` . MD5 hash for exact duplicate detection Screaming Frog auto-excludes nav and footer elements to focus on main content. [**Adjust threshold**](https://www.gtechme.com/insights/screaming-frog-duplicate-content-audit/) if needed (default 90%). h3. [Site Search](#site-search) Use Google site search to find duplicates manually: ``` site:mysite.com "exact title text" ``` If multiple URLs appear with the same title, you have duplicates. h3. [Siteliner and Copyscape](#siteliner-and-copyscape) **[**Siteliner**](https://www.siteliner.com/)** . Free tool that crawls up to 250 pages, shows duplicate content percentage **[**Copyscape**](https://www.copyscape.com/)** . Detects external duplicate content (other sites copying you) Both useful for content audits but don't replace Search Console or Screaming Frog for technical SEO. h2. [Canonical vs 301 Redirect](#canonical-vs-301-redirect) | **When to Use** | **Canonical Tag** | **301 Redirect** | | --- | --- | --- | | **Need both URLs live** | ✅ Yes | ❌ No | | **User should see one URL** | ❌ No | ✅ Yes | | **Products in multiple categories** | ✅ Yes | ❌ No | | **Old page no longer needed** | ❌ No | ✅ Yes | | **UTM tracking parameters** | ✅ Yes | ❌ No | | **www vs non-www** | ❌ No | ✅ Yes | | **HTTP vs HTTPS** | ❌ No | ✅ Yes | | **Moved/renamed pages** | ❌ No | ✅ Yes | **Canonical tags** are [**hints, not directives**](https://www.searchenginejournal.com/canonical-vs-301-redirect/383124/). Google may ignore them. Both versions remain accessible. Use for duplicates you need (tracking params, multiple category paths). **301 redirects** are permanent. Users see the redirect target. [**Pass the same link equity as canonicals**](https://seranking.com/blog/redirect-vs-canonical-tag/) but remove the duplicate from the index. Use for outdated or unnecessary URLs. **Don't combine:** Using both canonical tag and 301 redirect on the same page sends conflicting signals. Pick one. h2. [Decision Tree](#decision-tree) ![Duplicate Content Decision Tree](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/images/learn-seo/vue/duplicate-content-decision.svg) **Examples:** - `http://mysite.com` → `https://mysite.com` . **301 redirect** - `www.mysite.com` → `mysite.com` . **301 redirect** - `/products?utm_source=twitter` → `/products` . **Canonical tag** - `/products/shoes` and `/sale/shoes` (same product) . **Canonical tag** (one canonical, one alternate) - `/products?filter=red` . **Noindex + canonical to base URL** - `/old-page` → `/new-page` . **301 redirect** h2. [Common Mistakes](#common-mistakes) **Mistake 1: Canonicalizing all paginated pages to page 1** ``` ``` [**Each paginated page should reference itself**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/routes-and-rendering/pagination#self-referencing-canonical-tags). **Mistake 2: Using relative canonical URLs** ``` ``` [**Google requires absolute URLs**](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls). **Mistake 3: Combining canonical with noindex** ``` ``` Canonical says "this is a duplicate of X." Noindex says "don't index this." [**Pick one**](https://www.oncrawl.com/technical-seo/use-robots-txt-meta-robots-canonical-tags-correctly/). **Mistake 4: Canonical chains** ``` Page A → canonical → Page B → canonical → Page C ``` [**Google may ignore chained canonicals**](https://developers.google.com/search/docs/crawling-indexing/canonicalization-troubleshooting). Canonical directly to the final target. **Mistake 5: Client-side canonicals in SPAs** Googlebot doesn't execute JavaScript fast enough. Nuxt renders canonical tags on the server by default. this isn't an issue. h2. [Testing](#testing) **1. View page source (not DevTools)** ``` curl https://mysite.com/products?sort=price | grep canonical ``` Should return: ``` ``` **2. Google Search Console URL Inspection** 1. Enter URL with parameters 2. Check "User-declared canonical" 3. Compare to "Google-selected canonical" 4. Investigate if they differ **3. Check for canonicalization conflicts** - Multiple `rel="canonical"` tags on same page - Canonical in `` vs HTTP header - Canonical points to redirect - Canonical points to noindexed page - Canonical URL returns 4xx/5xx status **4. Test redirect chains** ``` curl -I https://mysite.com/old-url ``` Should show one 301 redirect, not a chain. h2. [Preventing Duplicate Content](#preventing-duplicate-content) h3. [Configure Trailing Slashes](#configure-trailing-slashes) Nuxt handles trailing slashes via `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ // Force trailing slashes nitro: { prerender: { autoSubfolderIndex: true } } }) ``` Or use route rules for redirects: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // Redirect /page to /page/ '/**': { redirect: path => !path.endsWith('/') ? \`${path}/\` : undefined } } }) ``` h3. [Validate Parameter Values](#validate-parameter-values) Prevent infinite URL variations by whitelisting allowed parameter values: ``` const allowedSortValues = ['price', 'name', 'date', 'rating'] const route = useRoute() const sort = route.query.sort if (sort && !allowedSortValues.includes(sort as string)) { // Redirect to base URL or default sort await navigateTo({ query: { ...route.query, sort: undefined } }) } ``` h3. [Block Low-Value Pages](#block-low-value-pages) Use [**robots.txt**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/controlling-crawlers/robots-txt) to block search results, filtered pages, and admin sections: public/robots.txt ``` User-agent: * h1. Block search results Disallow: /search? Disallow: /*?q= Disallow: /*?query= h1. Block filters Disallow: /*?filter= Disallow: /*&filter= h1. Block tracking params Disallow: /*?utm_source= Disallow: /*?fbclid= Disallow: /*?gclid= h1. Block session IDs Disallow: /*?sessionid= Disallow: /*?sid= ``` Or configure via the Robots module: [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/docs/robots/getting-started/introduction) h2. [Automatic Handling with Nuxt SEO Utils](#automatic-handling-with-nuxt-seo-utils) Nuxt SEO Utils handles canonical URLs, trailing slashes, and parameter normalization automatically through site config: [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/docs/seo-utils/getting-started/introduction) Configure once in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ site: { url: 'https://mysite.com', trailingSlash: false } }) ``` The module automatically: - Generates canonical URLs with correct trailing slash handling - Strips tracking parameters from canonicals - Normalizes URL formats across your site --- [**HTTP Redirects** 301 redirects preserve SEO value when content moves. Nuxt handles server-side redirects through routeRules to pass link equity and maintain rankings.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/controlling-crawlers/redirects) [**llms.txt** Help AI assistants understand your Nuxt documentation with the llms.txt standard. Configure nuxt-llms for automatic generation.](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/duplicate-content/learn-seo/nuxt/controlling-crawlers/llms-txt) **On this page** - [Common Causes](#common-causes) - [Finding Duplicate Content](#finding-duplicate-content) - [Canonical vs 301 Redirect](#canonical-vs-301-redirect) - [Decision Tree](#decision-tree) - [Common Mistakes](#common-mistakes) - [Testing](#testing) - [Preventing Duplicate Content](#preventing-duplicate-content) - [Automatic Handling with Nuxt SEO Utils](#automatic-handling-with-nuxt-seo-utils) --- ### Pagination SEO in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination Description: How to implement SEO-friendly pagination in Nuxt using canonical tags, self-referencing URLs, and proper link structure. h1. **Pagination SEO in Nuxt** How to implement SEO-friendly pagination in Nuxt using canonical tags, self-referencing URLs, and proper link structure. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)14 mins read Published **Dec 17, 2025** **What you'll learn** - Each paginated page needs its own self-referencing canonical. don't point all to page 1 - Google deprecated `rel=prev/next` in March 2019. use crawlable `` links instead - Infinite scroll requires hybrid approach with hidden pagination links for crawlers Pagination splits content across multiple pages. Search engines treat each page as separate. Set canonical tags wrong and Google indexes page 1 only. Set them right and all pages rank. Google [**no longer uses**](https://developers.google.com/search/blog/2011/09/pagination-with-relnext-and-relprev) `rel=prev/next` tags (deprecated March 2019). Modern pagination relies on self-referencing canonicals and crawlable `` links. h2. [Self-Referencing Canonical Tags](#self-referencing-canonical-tags) Each paginated page should have its own canonical URL pointing to itself. ![Pagination Canonical Flow](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination/images/learn-seo/vue/pagination-canonical-flow.svg) **Don't** point all pages to page 1. That tells Google only page 1 matters, hiding pages 2+ from search results. ``` ``` ``` ``` [**Self-referencing canonicals**](https://www.semrush.com/blog/pagination-seo/) tell Google each page has unique content worth indexing. h2. [Pagination URL Structure](#pagination-url-structure) Use query parameters or path segments. Both work for SEO. | **URL Pattern** | **Example** | **SEO Impact** | | --- | --- | --- | | Query parameter | `/blog?page=2` | Good - simple, flexible | | Path segment | `/blog/page/2` | Good - cleaner URLs | | Hash fragment | `/blog#page=2` | Bad - Google ignores `#` | **Query parameter approach:** Nuxt handles query parameters automatically through file-based routing: pages/blog.vue ``` ``` **Path segment approach:** Create nested route structure for cleaner URLs: pages/blog/index.vue ``` ``` pages/blog/page/[page].vue ``` ``` Never use fragment identifiers (`#page=2`). [**Google ignores everything after **`#`](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). h2. [Crawlable Links](#crawlable-links) Google needs `` tags to discover paginated pages. Nuxt's SSR ensures navigation is rendered in the initial HTML. ``` ``` `NuxtLink` generates proper `` tags while providing client-side navigation for users. h2. [Pagination Component](#pagination-component) Full example with prev/next links and numbered pages: ``` ``` [**Include links from each page to following pages**](https://www.amsive.com/insights/seo/how-to-correctly-implement-pagination-for-seo-user-experience/) using `` tags. Googlebot follows these to discover your content. h2. [View All Page Approach](#view-all-page-approach) Offer a single page with all content. Point canonicals from paginated pages to the View All page. ``` ``` **Drawbacks:** - Slow page load with 100+ items - Poor mobile experience - Images kill performance [**View All pages work**](https://seotactica.com/learn/canonical-pagination/) for small datasets (50 items). For large catalogs, use self-referencing canonicals. h2. [Infinite Scroll vs Pagination](#infinite-scroll-vs-pagination) ![Pagination vs Infinite Scroll Decision](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination/images/learn-seo/vue/pagination-vs-infinite-scroll.svg) | **Pattern** | **SEO Impact** | **UX** | **When to Use** | | --- | --- | --- | --- | | Pagination | Good - all pages indexable | Predictable | Catalogs, search results | | Infinite scroll | Poor - requires special handling | Frictionless | Social feeds, inspiration | | Load More button | Poor - unless URL changes | Balanced | Product listings | **Infinite scroll SEO challenges:** [**Googlebot cannot scroll**](https://www.seoclarity.net/blog/pagination-vs-infinite-scroll). Content below the fold stays hidden. Solutions: 1. **Hybrid approach** - Infinite scroll for users, paginated URLs for crawlers: ``` ``` 1. **Component pages** - Break infinite scroll into paginated URLs with unique canonicals. [**Google recommends paginated series**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) alongside infinite scroll. **Load More button:** Works for UX but [**Googlebot can't click buttons**](https://www.resultfirst.com/blog/ai-seo/pagination-vs-infinite-scroll-vs-load-more/). Use the hybrid approach above if SEO matters. h2. [Server-Side Pagination](#server-side-pagination) Nuxt Content provides built-in pagination support: pages/blog.vue ``` ``` For API-based pagination: server/api/posts.get.ts ``` export default defineEventHandler(async (event) => { const query = getQuery(event) const page = Number(query.page) || 1 const limit = 10 const offset = (page - 1) * limit const posts = await db.query( 'SELECT * FROM posts LIMIT ? OFFSET ?', [limit, offset] ) return { posts, page, totalPages: Math.ceil(totalPosts / limit) } }) ``` Limit queries to avoid performance issues. Use database indexes on sort columns. h2. [Meta Titles and Descriptions](#meta-titles-and-descriptions) Differentiate each paginated page for SEO: ``` ``` Unique titles prevent [**duplicate content confusion**](https://www.searchenginejournal.com/technical-seo/pagination/). h2. [Noindex on Paginated Pages](#noindex-on-paginated-pages) **Don't** noindex paginated pages. [**This loses indexed content**](https://www.seoclarity.net/blog/pagination-seo). Google stops crawling noindexed pages, hiding your products/articles. Only noindex if: - Filter/sort variations create infinite URLs (`/blog?sort=date&order=asc&filter=nuxt`) - You have a View All page as canonical - Pages have no unique content For most sites, [**keep paginated pages indexable**](https://ahrefs.com/blog/rel-prev-next-pagination/). h2. [Common Mistakes](#common-mistakes) **Mistake 1: Canonicalizing all pages to page 1** ``` ``` This tells Google pages 2+ are duplicates. Use self-referencing canonicals. **Mistake 2: Using hash fragments** ``` ❌ /blog#page=2 ✅ /blog?page=2 ✅ /blog/page/2 ``` Google ignores `#`. Your pagination won't be indexed. **Mistake 3: Client-only pagination links** ``` Next ``` **Mistake 4: Inconsistent trailing slashes** ``` /blog?page=1 /blog/?page=2 ← Duplicate content ``` Pick one format. See [**trailing slashes guide**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination/learn-seo/nuxt/routes-and-rendering/trailing-slashes). **Mistake 5: Blocking pagination in robots.txt** ``` h1. ❌ Hides paginated content User-agent: * Disallow: /*?page= ``` [**Don't block pagination URLs**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). Google needs to crawl them. h2. [Testing Pagination SEO](#testing-pagination-seo) **1. Check canonical tags** ``` curl -s https://nuxtseo.com/blog?page=2 | grep canonical ``` Should return: ``` ``` **2. Verify crawlable links** View page source (not DevTools). Look for `` tags with pagination URLs. Nuxt's SSR ensures these are present in the initial HTML. **3. Google Search Console** - URL Inspection tool - Check "Coverage" report for indexed pages - Look for paginated URLs in index **4. Site search** ``` site:nuxtseo.com/blog inurl:page ``` Shows indexed paginated pages. h2. [Sources](#sources) - [**Pagination and SEO: What you need to know in 2025**](https://searchengineland.com/pagination-seo-what-you-need-to-know-453707) - [**How to Correctly Implement Pagination for SEO**](https://www.amsive.com/insights/seo/how-to-correctly-implement-pagination-for-seo-user-experience/) - [**SEOs Are Breaking Pagination After Google Changed Rel=Prev/Next**](https://ahrefs.com/blog/rel-prev-next-pagination/) - [**Pagination Best Practices for Google**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) - [**Pagination and SEO: A Complete Guide**](https://www.semrush.com/blog/pagination-seo/) - [**What Is Canonical Pagination in SEO**](https://seotactica.com/learn/canonical-pagination/) - [**Is Pagination or Infinite Scroll Better for SEO?**](https://www.seoclarity.net/blog/pagination-vs-infinite-scroll) - [**Pagination vs Infinite Scroll vs Load More**](https://www.resultfirst.com/blog/ai-seo/pagination-vs-infinite-scroll-vs-load-more/) --- [**URL Structure** Create search-optimized URLs using file-based routing. Learn slug formatting, parameter handling, and route patterns that improve rankings.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination/learn-seo/nuxt/routes-and-rendering/url-structure) [**Trailing Slashes** Trailing slashes can cause duplicate content issues. Here's how to handle them in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/pagination/learn-seo/nuxt/routes-and-rendering/trailing-slashes) **On this page** - [Self-Referencing Canonical Tags](#self-referencing-canonical-tags) - [Pagination URL Structure](#pagination-url-structure) - [Crawlable Links](#crawlable-links) - [Pagination Component](#pagination-component) - [View All Page Approach](#view-all-page-approach) - [Infinite Scroll vs Pagination](#infinite-scroll-vs-pagination) - [Server-Side Pagination](#server-side-pagination) - [Meta Titles and Descriptions](#meta-titles-and-descriptions) - [Noindex on Paginated Pages](#noindex-on-paginated-pages) - [Common Mistakes](#common-mistakes) - [Testing Pagination SEO](#testing-pagination-seo) - [Sources](#sources) --- ### Trailing Slashes in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes Description: Trailing slashes can cause duplicate content issues. Here's how to handle them in Nuxt. h1. **Trailing Slashes in Nuxt** Trailing slashes can cause duplicate content issues. Here's how to handle them in Nuxt. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Nov 6, 2024** Updated **Dec 5, 2024** **What you'll learn** - Pick one format (with or without trailing slash) and enforce site-wide - Use `site.trailingSlash` config. Nuxt SEO applies it to sitemaps and canonicals - Missing redirects split ranking signals between /page and /page/ A trailing slash is the "/" at the end of a URL: - With trailing slash: `/about/` - Without trailing slash: `/about` While trailing slashes don't directly impact SEO rankings, they can create technical challenges: 1. **Duplicate Content**: When both `/about` and `/about/` serve the same content without proper canonicalization, search engines have to choose which version to index. While Google is generally good at figuring this out, it's better to be explicit. 2. **Crawl Budget**: On large sites, having multiple URLs for the same content can waste your [**crawl budget**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes/learn-seo/nuxt/controlling-crawlers#improve-organic-traffic). 3. **Analytics Accuracy**: Different URL formats can split your analytics data, making it harder to track page performance. The solution is simple: pick one format and stick to it by [**redirecting**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes/learn-seo/nuxt/controlling-crawlers/redirects) the other and set [**canonical URLs**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes/learn-seo/nuxt/controlling-crawlers/canonical-urls). h2. [Nuxt Configuration](#nuxt-configuration) Nuxt handles trailing slashes through the `trailingSlash` option in `nuxt.config.ts`: h3. [Removing Trailing Slashes (Default)](#removing-trailing-slashes-default) ``` export default defineNuxtConfig({ // Default behavior - trailing slashes are removed }) ``` By default, Nuxt removes trailing slashes from URLs. h3. [Adding Trailing Slashes](#adding-trailing-slashes) ``` export default defineNuxtConfig({ site: { trailingSlash: true } }) ``` This will automatically add trailing slashes to your sitemap, canonical URLs, and internal links generated by Nuxt SEO modules. h2. [Nuxt SEO Utils](#nuxt-seo-utils) If you're using [**Nuxt SEO Utils**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes/docs/seo-utils/getting-started/introduction), the `trailingSlash` configuration is handled automatically: ``` export default defineNuxtConfig({ site: { trailingSlash: true, // or false } }) ``` Nuxt SEO will: - Apply trailing slashes consistently across sitemap URLs - Set canonical URLs with the correct format - Handle OG image URLs properly [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes/docs/seo-utils/getting-started/introduction) h3. [`` Component](#sitelink-component) For internal links, use the `` component instead of ``. It automatically applies the correct trailing slash format: ``` ``` h4. [Props](#props) - `withBase` - Adds the `app.baseURL` to the link - `absolute` - Renders the link as an absolute path h2. [Route Rules for Redirects](#route-rules-for-redirects) Use `routeRules` to enforce redirects for inconsistent URLs: h3. [Remove Trailing Slashes](#remove-trailing-slashes) ``` export default defineNuxtConfig({ routeRules: { // Redirect trailing slash URLs '/**/**/': { redirect: { to: path => path.slice(0, -1), statusCode: 301 } } } }) ``` h3. [Add Trailing Slashes](#add-trailing-slashes) ``` export default defineNuxtConfig({ routeRules: { // Redirect non-trailing slash URLs '/**/': { redirect: { to: path => \`${path}/\`, statusCode: 301 } } } }) ``` h2. [Canonical URLs](#canonical-urls) Set canonical URLs to indicate your preferred URL format: ``` ``` Or use `useSeoMeta`: ``` ``` h2. [Best Practices](#best-practices) 1. **Choose One Format**: Pick with or without trailing slashes and stick to it site-wide 2. **Use Site Config**: Configure `trailingSlash` in `nuxt.config.ts` for consistency 3. **Use ``**: Replace `` with `` for automatic trailing slash handling 4. **Set Route Rules**: Use `routeRules` for 301 redirects of inconsistent URLs 5. **Canonical Tags**: Always set canonical URLs to your preferred format 6. **Internal Links**: Be consistent in your internal linking 7. **Sitemap**: Use consistent URLs in your sitemap h2. [What NOT to Do](#what-not-to-do) ❌ Don't mix URL formats across your site ❌ Don't rely only on client-side redirects for SEO ❌ Don't ignore the issue on large sites ❌ Don't change your format without proper redirects --- [**Pagination** How to implement SEO-friendly pagination in Nuxt using canonical tags, self-referencing URLs, and proper link structure.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes/learn-seo/nuxt/routes-and-rendering/pagination) [**Query Parameters** Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/trailing-slashes/learn-seo/nuxt/routes-and-rendering/query-parameters) **On this page** - [Nuxt Configuration](#nuxt-configuration) - [Nuxt SEO Utils](#nuxt-seo-utils) - [Route Rules for Redirects](#route-rules-for-redirects) - [Canonical URLs](#canonical-urls) - [Best Practices](#best-practices) - [What NOT to Do](#what-not-to-do) --- ### Query Parameters and SEO in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/query-parameters Description: Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Nuxt. h1. **Query Parameters and SEO in Nuxt** Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Nuxt. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** Query parameters (`?sort=price&filter=red`) create duplicate content. The URL `/products?sort=price` and `/products?sort=name` show the same products in different orders, but search engines treat them as separate pages. With filters, sorting, and pagination, a single page can generate hundreds of URL variations. Each variation wastes crawl budget and dilutes ranking signals across duplicates. h2. [Quick Setup](#quick-setup) Handle query parameters with canonical URLs to tell search engines which version to index: pages/products.vue ``` ``` pages/products.vue - Block All Parameters ``` ``` pages/products.vue - Block from Indexing ``` ``` h2. [Common Parameter Types](#common-parameter-types) Different query parameters need different handling: | **Parameter Type** | **Examples** | **SEO Treatment** | **Why** | | --- | --- | --- | --- | | **Filters** | `?color=red&size=large` | Canonical to base or noindex | Creates duplicates, thin content | | **Sorting** | `?sort=price` | Include in canonical | Changes value, users link to sorted views | | **Pagination** | `?page=2` | Self-referencing canonical | Each page has unique content | | **Tracking** | `?utm_source=twitter` | Strip from canonical | No content value, analytics only | | **Search** | `?q=shoes` | Depends on results | Index if unique results, noindex if duplicates | | **Sessions** | `?sessionid=abc` | Canonical to base | Creates infinite URLs | h2. [Filter Parameters](#filter-parameters) Filters create exponential URL variations. Three color filters generate 8 combinations (red, blue, green, red+blue, red+green, blue+green, red+blue+green, none). h3. [Block Filtered Pages](#block-filtered-pages) pages/products.vue ``` ``` h3. [Move Important Filters to Path](#move-important-filters-to-path) For SEO-valuable filters (categories, main attributes), use route paths instead of query params: ``` // /products?category=shoes // Not ideal for important categories ``` ``` // /products/shoes // Create pages/products/[category].vue ``` h2. [Sort Parameters](#sort-parameters) Sorting changes presentation but not content. Users share sorted URLs ("cheapest laptops" links to `?sort=price`). h3. [Include Sort in Canonical](#include-sort-in-canonical) pages/products.vue ``` ``` **Why validate sort values?** Prevents parameter manipulation creating infinite URLs (`?sort=abc`, `?sort=xyz`). h3. [Block Sort from Indexing](#block-sort-from-indexing) If sorted views don't add value (same content, different order), use noindex: ``` useSeoMeta({ robots: route.query.sort ? 'noindex, follow' : 'index, follow' }) ``` h2. [Pagination Parameters](#pagination-parameters) Each page in a sequence has unique content. [**Google recommends self-referencing canonicals**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). don't point page 2 to page 1. pages/blog.vue ``` ``` h3. [Validate Page Numbers](#validate-page-numbers) ``` const page = computed(() => { const pageNum = Number.parseInt(route.query.page) return pageNum > 0 && pageNum <= maxPages ? pageNum : 1 }) ``` Prevents crawlers requesting `?page=999999` and wasting server resources. h2. [Tracking Parameters](#tracking-parameters) Analytics parameters (utm_, fbclid, gclid) don't change content but create duplicate URLs. h3. [Strip Tracking Params](#strip-tracking-params) composables/useCanonicalUrl.ts ``` export function useCanonicalUrl(path: string) { const route = useRoute() // List of tracking params to ignore const trackingParams = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'msclkid', 'mc_cid', 'mc_eid', '_ga', 'ref', 'source' ] // Keep only non-tracking params const cleanParams = Object.fromEntries( Object.entries(route.query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${useSiteConfig().url}${path}?${queryString}\` : \`${useSiteConfig().url}${path}\` }] } } ``` h3. [Server-Side Parameter Handling](#server-side-parameter-handling) Nuxt handles tracking parameter redirects through route rules: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/**': { redirect: { // Strip tracking params via middleware } } } }) ``` Or use server middleware: server/middleware/tracking-params.ts ``` export default defineEventHandler((event) => { const query = getQuery(event) const trackingParams = ['utm_source', 'fbclid', 'gclid'] const hasTracking = trackingParams.some(param => query[param]) if (hasTracking) { const cleanQuery = Object.fromEntries( Object.entries(query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanQuery).toString() return sendRedirect(event, \`${event.path}${queryString ? \`?${queryString}\` : ''}\`, 301) } }) ``` h2. [Parameter Order Consistency](#parameter-order-consistency) `?sort=price&filter=red` and `?filter=red&sort=price` are identical content but different URLs. Enforce consistent parameter ordering: composables/useCanonicalUrl.ts ``` export function useCanonicalUrl(path: string, params: Record) { // Define parameter order const paramOrder = ['category', 'sort', 'filter', 'page'] // Sort params by predefined order const orderedParams = Object.fromEntries( Object.entries(params) .sort(([a], [b]) => { const indexA = paramOrder.indexOf(a) const indexB = paramOrder.indexOf(b) if (indexA === -1) return 1 if (indexB === -1) return -1 return indexA - indexB }) ) const queryString = new URLSearchParams(orderedParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${useSiteConfig().url}${path}?${queryString}\` : \`${useSiteConfig().url}${path}\` }] } } ``` h3. [Navigation with Ordered Parameters](#navigation-with-ordered-parameters) Force parameter order when updating query strings: ``` const router = useRouter() function updateFilters(filters: Record) { const paramOrder = ['category', 'sort', 'filter', 'page'] const ordered = Object.fromEntries( Object.entries(filters) .sort(([a], [b]) => { const indexA = paramOrder.indexOf(a) const indexB = paramOrder.indexOf(b) return indexA - indexB }) ) router.push({ query: ordered }) } ``` h2. [Search Parameters](#search-parameters) Search queries create unique URLs for each search term. Treatment depends on result quality: h3. [Block Thin Search Results](#block-thin-search-results) pages/search.vue ``` ``` h3. [robots.txt for Search](#robotstxt-for-search) Block crawlers from search entirely: public/robots.txt ``` User-agent: * Disallow: /search? h1. Or use URL patterns Disallow: /*?q= Disallow: /*?query= ``` h2. [Testing Parameter Handling](#testing-parameter-handling) h3. [Google Search Console](#google-search-console) 1. URL Inspection tool 2. Enter URL with parameters 3. Check "User-declared canonical" vs "Google-selected canonical" 4. Verify they match your preference h3. [Manual Verification](#manual-verification) ``` h1. Check canonical in HTML curl https://mysite.com/products?sort=price | grep canonical h1. Should return: h1. ``` h3. [Test Parameter Variations](#test-parameter-variations) Create a test matrix: | **URL** | **Expected Canonical** | **Expected Robots** | | --- | --- | --- | | `/products` | Self | index, follow | | `/products?sort=price` | Self or base | Depends on strategy | | `/products?filter=red` | Base URL | noindex, follow | | `/products?utm_source=twitter` | Base URL | index, follow | | `/products?page=2` | Self | index, follow | h2. [robots.txt Parameter Blocking](#robotstxt-parameter-blocking) Block specific parameters from crawling entirely: public/robots.txt ``` User-agent: * h1. Block all URLs with these params Disallow: /*?sessionid= Disallow: /*?sid= Disallow: /*&sessionid= Disallow: /*&sid= h1. Block tracking params Disallow: /*?utm_source= Disallow: /*?fbclid= Disallow: /*?gclid= h1. Block filter combinations Disallow: /*?filter= Disallow: /*&filter= ``` [**Google deprecated parameter handling in Search Console**](https://developers.google.com/search/blog/2022/06/retiring-url-parameters-tool) in 2022. Use robots.txt or meta robots instead. h2. [Common Mistakes](#common-mistakes) **Using client-side canonicals:** Nuxt renders canonical tags server-side by default, ensuring search engines see them immediately. **Indexing every parameter variation:** Creates thin content and wastes crawl budget. Pick one canonical version. **Inconsistent parameter handling:** Some pages canonical to base, others to self. Be consistent site-wide. **Ignoring tracking parameters:** Analytics params create duplicate URLs. Strip them from canonicals. **Not validating parameter values:** Allows `?sort=anything` creating infinite URLs. Whitelist valid values. --- [**Trailing Slashes** Trailing slashes can cause duplicate content issues. Here's how to handle them in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/query-parameters/learn-seo/nuxt/routes-and-rendering/trailing-slashes) [**Hreflang & i18n** Set hreflang tags in Nuxt to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/query-parameters/learn-seo/nuxt/routes-and-rendering/i18n) **On this page** - [Quick Setup](#quick-setup) - [Common Parameter Types](#common-parameter-types) - [Filter Parameters](#filter-parameters) - [Sort Parameters](#sort-parameters) - [Pagination Parameters](#pagination-parameters) - [Tracking Parameters](#tracking-parameters) - [Parameter Order Consistency](#parameter-order-consistency) - [Search Parameters](#search-parameters) - [Testing Parameter Handling](#testing-parameter-handling) - [robots.txt Parameter Blocking](#robotstxt-parameter-blocking) - [Common Mistakes](#common-mistakes) --- ### Hreflang Tags in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/i18n Description: Set hreflang tags in Nuxt to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites. h1. **Hreflang Tags in Nuxt** Set hreflang tags in Nuxt to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** Hreflang tags tell search engines which language version of your page to show users. Without them, Google might show your French content to English speakers or rank the wrong regional version. ``` ``` [**Google recommends hreflang**](https://developers.google.com/search/docs/specialty/international/localized-versions) when you have: - Same content in different languages - Regional variations (en-US vs en-GB) - Partial translations mixed with original language Hreflang is a signal, not a directive. Search engines can ignore it if they think a different version better matches user intent. h2. [Quick Reference](#quick-reference) ``` useHead({ link: [ { rel: 'alternate', hreflang: 'en', href: 'https://example.com/en' }, { rel: 'alternate', hreflang: 'fr', href: 'https://example.com/fr' }, { rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/en' } ] }) ``` ``` useSeoMeta({ alternateLanguages: { 'en': 'https://example.com/en', 'fr': 'https://example.com/fr', 'x-default': 'https://example.com/en' } }) ``` h2. [Hreflang Format](#hreflang-format) Hreflang values use [**ISO 639-1 language codes**](https://www.linguise.com/blog/guide/list-of-the-hreflang-language-codes-how-to-implement-them/) and optional [**ISO 3166-1 Alpha-2 region codes**](https://hreflang.org/what-is-a-valid-hreflang/): | **Format** | **Example** | **Use Case** | | --- | --- | --- | | Language only | `en` | English content for all regions | | Language + region | `en-US` | English for United States | | Language + region | `en-GB` | English for United Kingdom | | Special fallback | `x-default` | Default for unmatched languages/regions | Language codes must be lowercase. Region codes must be uppercase. Separate with hyphen: `en-US`. h3. [Common Examples](#common-examples) ``` en English (all regions) fr French (all regions) es Spanish (all regions) en-US English for USA en-GB English for UK fr-CA French for Canada es-MX Spanish for Mexico zh-CN Simplified Chinese (China) zh-TW Traditional Chinese (Taiwan) ``` h3. [Region Codes Are Tricky](#region-codes-are-tricky) [**UK is Ukraine**](https://www.weglot.com/blog/hreflang-language-codes), not United Kingdom. Use `GB` for Great Britain. [**Chinese uses zh-CN/zh-TW or zh-Hans/zh-Hant**](https://www.seroundtable.com/google-iso-3166-1-for-hreflang-24663.html) for simplified/traditional variants since ISO 639-1 only defines one `zh` code. h2. [Automatic Implementation with @nuxtjs/i18n](#automatic-implementation-with-nuxtjsi18n) The [**@nuxtjs/i18n**](https://i18n.nuxtjs.org/) module automatically generates hreflang tags for all configured locales. Nuxt handles return links, self-referential tags, and x-default automatically. ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/i18n'], i18n: { locales: [ { code: 'en', iso: 'en-US', file: 'en.json' }, { code: 'fr', iso: 'fr-FR', file: 'fr.json' }, { code: 'de', iso: 'de-DE', file: 'de.json' } ], defaultLocale: 'en', strategy: 'prefix', // URLs like /en, /fr, /de // SEO features baseUrl: 'https://example.com', detectBrowserLanguage: { useCookie: true, cookieKey: 'i18n_redirected', redirectOn: 'root' } } }) ``` Nuxt automatically generates: ``` ``` h3. [URL Strategies](#url-strategies) The i18n module supports different URL patterns: | **Strategy** | **Example** | **Notes** | | --- | --- | --- | | `prefix` | `/en/about`, `/fr/about` | Default locale also gets prefix | | `prefix_except_default` | `/about`, `/fr/about` | Default locale has no prefix | | `prefix_and_default` | `/about`, `/en/about`, `/fr/about` | Both work for default | | `no_prefix` | `/about` | Locale in cookie/domain only | ``` // nuxt.config.ts export default defineNuxtConfig({ i18n: { strategy: 'prefix_except_default', // /about (en), /fr/about defaultLocale: 'en' } }) ``` h3. [Domain-Based Locales](#domain-based-locales) For sites with different domains per language: ``` // nuxt.config.ts export default defineNuxtConfig({ i18n: { locales: [ { code: 'en', iso: 'en-US', domain: 'example.com' }, { code: 'fr', iso: 'fr-FR', domain: 'example.fr' }, { code: 'de', iso: 'de-DE', domain: 'example.de' } ], strategy: 'no_prefix', differentDomains: true } }) ``` Generates: ``` ``` h2. [Manual Implementation](#manual-implementation) If you're not using @nuxtjs/i18n, set hreflang manually with `useHead()`: ``` ``` h3. [Creating a Composable](#creating-a-composable) Extract hreflang logic into `composables/useHreflang.ts`: ``` // composables/useHreflang.ts - auto-imported in Nuxt export function useHreflang(locales: string[]) { const route = useRoute() const links = computed(() => { const baseUrl = 'https://example.com' return [ ...locales.map(locale => ({ rel: 'alternate', hreflang: locale, href: \`${baseUrl}/${locale}${route.path}\` })), { rel: 'alternate', hreflang: 'x-default', href: \`${baseUrl}/${locales[0]}${route.path}\` } ] }) useHead({ link: links }) } ``` Use it in pages: ``` ``` h2. [X-Default Tag](#x-default-tag) The `x-default` hreflang provides a fallback URL when no language matches the user's preferences. [**Google recommends x-default**](https://www.weglot.com/guides/hreflang-tag) for all hreflang clusters. ``` ``` h3. [When to Use X-Default](#when-to-use-x-default) Set x-default to: - Your primary language version - A language selector page - The most universally understood language version ``` // Point x-default to language selector useHead({ link: [ { rel: 'alternate', hreflang: 'en-US', href: 'https://example.com/en-us' }, { rel: 'alternate', hreflang: 'en-GB', href: 'https://example.com/en-gb' }, { rel: 'alternate', hreflang: 'fr-FR', href: 'https://example.com/fr-fr' }, { rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/choose-language' } ] }) ``` [**According to Victorious**](https://victorious.com/blog/what-is-x-default/), x-default improves user experience by routing unmatched users to an appropriate fallback instead of a random language version. h2. [Hreflang Rules](#hreflang-rules) h3. [1. Bidirectional Links (Return Links)](#_1-bidirectional-links-return-links) Every page referenced in hreflang must link back. If page A links to page B, page B must link to page A. [**This is the most common hreflang error**](https://www.iloveseo.net/how-to-use-hreflang-correctly/). ``` ``` h3. [2. Self-Referential Links](#_2-self-referential-links) Each page must include a hreflang tag pointing to itself: ``` ``` h3. [3. Use Canonical URLs Only](#_3-use-canonical-urls-only) All hreflang URLs must: - Return HTTP 200 status - Be indexable (no `noindex`) - Not be blocked by robots.txt - Point to canonical URLs (not redirects) [**According to I Love SEO**](https://www.iloveseo.net/how-to-use-hreflang-correctly/), using non-200, non-indexable, or non-canonical URLs is the root cause of most hreflang mistakes. ``` ``` h3. [4. Canonical vs Hreflang](#_4-canonical-vs-hreflang) Canonical tags and hreflang serve different purposes. Don't use canonical to point between language versions. [**that signals duplicate content**](https://www.iloveseo.net/how-to-use-hreflang-correctly/), not translations. ``` ``` ``` ``` h2. [Implementation Methods](#implementation-methods) You can implement hreflang using HTML `` tags, HTTP headers, or XML sitemaps. [**Best practice is to choose one method**](https://backlinko.com/hreflang-tag) and stick to it. Mixing methods creates conflicting signals. h3. [HTML Link Tags (Recommended)](#html-link-tags-recommended) Most flexible approach. Set per-page using `useHead()`: ``` ``` h3. [HTTP Headers](#http-headers) Useful for non-HTML resources (PDFs, etc.). Configure in server routes: ``` // server/routes/document.pdf.get.ts export default defineEventHandler((event) => { setHeader(event, 'Link', [ '; rel="alternate"; hreflang="en"', '; rel="alternate"; hreflang="fr"', '; rel="alternate"; hreflang="x-default"' ].join(', ')) return sendStream(event, createReadStream('/path/to/document.pdf')) }) ``` h3. [XML Sitemap](#xml-sitemap) The [**@nuxtjs/sitemap**](https://nuxtseo.com/sitemap) module automatically generates hreflang annotations in sitemaps when used with @nuxtjs/i18n: ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/sitemap', '@nuxtjs/i18n'], sitemap: { // Sitemap automatically includes hreflang from i18n config } }) ``` Generates: ``` https://example.com/en https://example.com/fr ``` h2. [Common Mistakes](#common-mistakes) h3. [Missing Return Links](#missing-return-links) ``` ``` h3. [Non-Canonical URLs](#non-canonical-urls) ``` ``` h3. [Noindex Pages](#noindex-pages) ``` ``` All pages in hreflang cluster must be indexable. Remove `noindex` or remove the page from hreflang annotations. h3. [Blocked by Robots.txt](#blocked-by-robotstxt) If a URL is blocked in `robots.txt`, crawlers can't access it to validate hreflang links. Ensure all alternate URLs are crawlable. h3. [Mixing with Canonical](#mixing-with-canonical) ``` ``` h2. [Testing Hreflang](#testing-hreflang) h3. [Manual Inspection](#manual-inspection) View page source and verify: - All alternate links are present - Self-referential link exists - x-default is set - All URLs return 200 - All URLs are canonical (not redirects) h3. [Google Search Console](#google-search-console) After implementing hreflang, check [**Google Search Console**](https://search.google.com/search-console) > International Targeting for errors: - Missing return tags - Incorrect language codes - Invalid URLs h3. [Third-Party Tools](#third-party-tools) - [**Hreflang Tags Testing Tool**](https://hreflang.org) - [**Merkle Hreflang Checker**](https://technicalseo.com/tools/hreflang/) - Search Console reports h2. [Full Example: Multilingual Nuxt App](#full-example-multilingual-nuxt-app) Complete implementation with @nuxtjs/i18n: ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/i18n'], i18n: { locales: [ { code: 'en', iso: 'en-US', file: 'en.json' }, { code: 'fr', iso: 'fr-FR', file: 'fr.json' }, { code: 'de', iso: 'de-DE', file: 'de.json' }, { code: 'es', iso: 'es-ES', file: 'es.json' } ], defaultLocale: 'en', strategy: 'prefix', langDir: 'locales', baseUrl: 'https://example.com', detectBrowserLanguage: { useCookie: true, cookieKey: 'i18n_redirected', redirectOn: 'root' } } }) ``` ``` // locales/en.json { "home": { "title": "Welcome", "description": "Welcome to our site" } } ``` ``` // locales/fr.json { "home": { "title": "Bienvenue", "description": "Bienvenue sur notre site" } } ``` ``` ``` Nuxt automatically generates hreflang tags for `/en`, `/fr`, `/de`, `/es` versions of every page. --- [**Query Parameters** Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/i18n/learn-seo/nuxt/routes-and-rendering/query-parameters) [**404 Pages** 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Nuxt applications.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/i18n/learn-seo/nuxt/routes-and-rendering/404-pages) **On this page** - [Quick Reference](#quick-reference) - [Hreflang Format](#hreflang-format) - [Automatic Implementation with @nuxtjs/i18n](#automatic-implementation-with-nuxtjsi18n) - [Manual Implementation](#manual-implementation) - [X-Default Tag](#x-default-tag) - [Hreflang Rules](#hreflang-rules) - [Implementation Methods](#implementation-methods) - [Common Mistakes](#common-mistakes) - [Testing Hreflang](#testing-hreflang) - [Full Example: Multilingual Nuxt App](#full-example-multilingual-nuxt-app) --- ### 404 Pages and SEO in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages Description: 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Nuxt applications. h1. **404 Pages and SEO in Nuxt** 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Nuxt applications. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** 404 errors don't hurt SEO. They're expected. deleted products, outdated links, user typos all create legitimate 404s. Google ignores them. Soft 404s hurt SEO. A soft 404 returns `200 OK` status but shows "page not found" content. Google excludes these from search results and wastes your [**crawl budget**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages/learn-seo/nuxt/controlling-crawlers#crawler-budget) recrawling pages it thinks exist. Nuxt handles this automatically. Using `error.vue` and `createError()` ensures proper status codes for both SSR and SSG. h2. [Quick Setup](#quick-setup) Create `error.vue` in your project root: error.vue ``` ``` Throw 404 errors in pages when content doesn't exist: pages/products/[id].vue ``` ``` Nuxt returns proper `404` status code for SSR and static generation automatically. h2. [Soft 404 Errors Explained](#soft-404-errors-explained) Soft 404 detection happens when Google sees content that looks like an error page but receives `200 OK` status ([**Google Search Central**](https://developers.google.com/search/docs/crawling-indexing/http-network-errors#soft-404-errors)). Common triggers: - "Page not found" in title or heading - Minimal content (under ~200 words) - Redirecting all 404s to homepage - Empty page body with "coming soon" message - Generic error messages without meaningful content Google Search Console flags soft 404s in the "Page Indexing" report. Fix by returning proper `404` status code. h3. [Why Soft 404s Hurt SEO](#why-soft-404s-hurt-seo) 1. **Wasted crawl budget** - Google recrawls pages thinking they exist, leaving less budget for real pages 2. **Index bloat** - Search Console shows thousands of indexed URLs that don't exist 3. **Ranking signals confusion** - Google doesn't know if content moved or disappeared 4. **No link equity transfer** - Can't redirect or canonicalize non-existent pages properly h2. [Checking Routes and Throwing Errors](#checking-routes-and-throwing-errors) Nuxt's file-based routing handles most route validation automatically. For dynamic routes, check data existence: pages/blog/[slug].vue ``` ``` The `createError()` function triggers Nuxt's error handling, renders `error.vue`, and returns proper HTTP status code. h2. [Dynamic Routes Considerations](#dynamic-routes-considerations) Check data existence before rendering: pages/products/[id].vue ``` ``` For server routes, use `setResponseStatus`: server/api/products/[id].get.ts ``` export default defineEventHandler(async (event) => { const id = getRouterParam(event, 'id') const product = await findProduct(id) if (!product) { setResponseStatus(event, 404) return { error: 'Product not found' } } return product }) ``` h2. [404 vs 410 Status Codes](#_404-vs-410-status-codes) **404 Not Found** - Resource doesn't exist, might never have existed, might come back: - User typos - Outdated external links - Deleted products that might return to inventory - Seasonal content (holiday pages) **410 Gone** - Resource existed, now permanently removed: - Discontinued products - Deleted blog posts (no redirect target) - Expired promotions - Intentionally removed content Google treats both similarly for indexing. removes from search results. 410 signals faster removal but rarely needed. Use 404 for most cases. To return 410: pages/discontinued/[id].vue ``` ``` h2. [Custom 404 Page Design](#custom-404-page-design) Good 404 pages keep users on your site: error.vue ``` ``` **Don't:** - Redirect all 404s to homepage (soft 404 risk) - Auto-redirect after countdown (bad UX) - Show only "404" with no explanation - Display technical error messages **Do:** - Explain what happened clearly - Provide search functionality - Link to popular/relevant pages - Match site design (keeps users oriented) - Include contact option for reporting broken links h2. [Crawl Budget Impact](#crawl-budget-impact) 404 errors have minimal crawl budget impact ([**Google Search Central**](https://developers.google.com/search/docs/crawling-indexing/http-network-errors)). Google expects them. Soft 404s waste crawl budget because Google recrawls pages thinking content exists. Large sites (10,000+ pages) should: - Monitor 404 rates in Search Console - Fix internal links pointing to 404s - Remove 404 URLs from [**sitemap**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages/learn-seo/nuxt/controlling-crawlers/sitemaps) - Use 301 redirects for high-value deleted pages with relevant replacements Don't worry about occasional 404s from external links or user typos. h2. [Handling 404s for Deleted Content](#handling-404s-for-deleted-content) h3. [Content Moved](#content-moved) Use [**301 redirect**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages/learn-seo/nuxt/controlling-crawlers/redirects) in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/old-product': { redirect: { to: '/new-product', statusCode: 301 } }, '/old-category/**': { redirect: { to: '/new-category/**', statusCode: 301 } } } }) ``` Or in server middleware: server/middleware/redirects.ts ``` export default defineEventHandler((event) => { if (event.path === '/old-product') { return sendRedirect(event, '/new-product', 301) } }) ``` h3. [Content Permanently Removed](#content-permanently-removed) Return 404 or 410. If similar content exists, redirect to relevant category: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // ✅ Good - redirect to relevant category '/discontinued-product': { redirect: { to: '/products/similar-items', statusCode: 301 } }, // ✅ Also good - let Nuxt return 404 naturally for non-existent routes } }) ``` h2. [Testing 404 Responses](#testing-404-responses) Verify proper status codes before deploying: **Browser DevTools:** 1. Open Network tab 2. Navigate to non-existent URL 3. Check status code in response headers 4. Should show `404` not `200` **Command line:** ``` curl -I https://example.com/fake-page h1. Output should show: h1. HTTP/1.1 404 Not Found ``` **Google Search Console:** 1. Use URL Inspection tool 2. Enter 404 URL 3. "Request indexing" 4. Check if Google recognizes 404 status 5. Monitor "Page Indexing" report for soft 404 flags **Lighthouse:** Run Lighthouse audit, check "Crawling and Indexing" section for status code issues. h2. [Common Mistakes](#common-mistakes) h3. [Redirecting All 404s to Homepage](#redirecting-all-404s-to-homepage) Creates soft 404 risk. Google may ignore [**redirects**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages/learn-seo/nuxt/controlling-crawlers/redirects) to irrelevant pages. ``` // ❌ Bad - mass redirect to homepage export default defineNuxtConfig({ routeRules: { '/**': { redirect: '/' } // Don't do this } }) ``` Only redirect if replacement content is relevant. Otherwise let Nuxt return proper 404. h3. [Not Using createError()](#not-using-createerror) Rendering error content without throwing error returns `200 OK`: ``` ``` ``` ``` h3. [Forgetting noindex Meta Tag](#forgetting-noindex-meta-tag) If 404 page accidentally returns `200 OK`, `noindex` prevents indexing: error.vue ``` ``` Safety net, not primary solution. Use `createError()` for proper status codes. h3. [Not Monitoring 404 Patterns](#not-monitoring-404-patterns) Repeated 404s to same path indicate broken internal links or outdated external links. Check Search Console "Not Found" report monthly, fix internal links immediately. h2. [Catch-All Routes](#catch-all-routes) For custom 404 handling beyond `error.vue`, use catch-all routes: pages/[...slug].vue ``` ``` This pattern works for CMS-driven sites where routes come from database/content. --- [**Hreflang & i18n** Set hreflang tags in Nuxt to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages/learn-seo/nuxt/routes-and-rendering/i18n) [**Dynamic Routes** How to use Nuxt's file-based dynamic routes, set per-route meta tags, and avoid duplicate content issues with URL parameters.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/404-pages/learn-seo/nuxt/routes-and-rendering/dynamic-routes) **On this page** - [Quick Setup](#quick-setup) - [Soft 404 Errors Explained](#soft-404-errors-explained) - [Checking Routes and Throwing Errors](#checking-routes-and-throwing-errors) - [Dynamic Routes Considerations](#dynamic-routes-considerations) - [404 vs 410 Status Codes](#_404-vs-410-status-codes) - [Custom 404 Page Design](#custom-404-page-design) - [Crawl Budget Impact](#crawl-budget-impact) - [Handling 404s for Deleted Content](#handling-404s-for-deleted-content) - [Testing 404 Responses](#testing-404-responses) - [Common Mistakes](#common-mistakes) - [Catch-All Routes](#catch-all-routes) --- ### Dynamic Routes in Nuxt for SEO · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes Description: How to use Nuxt's file-based dynamic routes, set per-route meta tags, and avoid duplicate content issues with URL parameters. h1. **Dynamic Routes in Nuxt for SEO** How to use Nuxt's file-based dynamic routes, set per-route meta tags, and avoid duplicate content issues with URL parameters. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** Dynamic routes generate clean URLs from parameters. `/blog/vue-seo-guide` instead of `/blog?id=123`. Search engines prefer semantic paths over query parameters. Nuxt's file-based routing handles this automatically. Create `pages/blog/[slug].vue` and Nuxt generates the route. Set per-route meta tags with `useSeoMeta()` and Google indexes each page correctly. h2. [Route Params vs Query Parameters](#route-params-vs-query-parameters) Google treats these differently: ``` ✅ /products/electronics/laptop ❌ /products?category=electronics&item=laptop ``` Route params create semantic URLs. Query parameters generate duplicate content issues. Google sees infinite URL variations when you add filters, sorting, or tracking params. From [**Google's 2025 URL structure guidelines**](https://developers.google.com/search/docs/crawling-indexing/url-structure): use clean paths for important content, reserve query parameters for filters that shouldn't be indexed. h2. [Basic Dynamic Routes](#basic-dynamic-routes) Create files with `[param]` syntax in the `pages/` directory: ``` pages/ ├── blog/ │ └── [slug].vue → /blog/:slug ├── products/ │ └── [category]/ │ └── [id].vue → /products/:category/:id ``` This generates: - `/blog/vue-ssr-guide` - `/products/electronics/123` Access params in components via `route.params`: pages/blog/[slug].vue ``` ``` h2. [Per-Route Meta Tags](#per-route-meta-tags) Search engines need unique titles and descriptions for each dynamic route. Fetch data and set meta tags with Nuxt's data fetching composables. h3. [Dynamic Meta from Data](#dynamic-meta-from-data) Fetch data and set specific meta tags per page: pages/blog/[slug].vue ``` ``` Nuxt's `useFetch()` and `useAsyncData()` work automatically during SSR. search engines see complete HTML with correct meta tags. No additional setup required. h3. [Using Nuxt Content](#using-nuxt-content) For content-driven sites, integrate with `@nuxt/content`: pages/blog/[slug].vue ``` ``` h2. [Multiple Route Params](#multiple-route-params) Combine params with nested directories: ``` pages/ └── docs/ └── [category]/ └── [page].vue → /docs/:category/:page ``` Generates: `/docs/getting-started/installation` Access all params: pages/docs/[category]/[page].vue ``` ``` h2. [Optional Params](#optional-params) Nuxt supports optional params with multiple page files: ``` pages/ └── blog/ ├── [slug].vue → /blog/:slug └── [category]/ └── [slug].vue → /blog/:category/:slug ``` Matches both: - `/blog/vue-guide` (category undefined) - `/blog/tutorials/vue-guide` (category = "tutorials") Handle optional params in components: ``` ``` h2. [Catch-All Routes](#catch-all-routes) Use `[...slug].vue` for catch-all patterns: ``` pages/ └── [...slug].vue → Matches any path ``` Or nested catch-all: ``` pages/ └── files/ └── [...path].vue → /files/* matches all nested paths ``` Access the full path: pages/files/[...path].vue ``` ``` h3. [404 Pages](#_404-pages) Create a catch-all for 404 handling: pages/[...slug].vue ``` ``` Or use Nuxt's dedicated error page: error.vue ``` ``` h2. [Programmatic Navigation](#programmatic-navigation) Navigate to dynamic routes with `navigateTo()`: ``` ``` Or use `useRouter()`: ``` ``` h2. [Common SEO Issues](#common-seo-issues) h3. [Issue 1: Duplicate Content from Query Params](#issue-1-duplicate-content-from-query-params) Mixing route params and query params creates duplicates: ``` /blog/vue-guide /blog/vue-guide?ref=twitter /blog/vue-guide?utm_source=newsletter ``` Google indexes these as separate pages. Fix with canonical tags: ``` ``` Or use Nuxt SEO Utils which handles this automatically: [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes/docs/seo-utils/getting-started/introduction) h3. [Issue 2: Missing Titles on Dynamic Routes](#issue-2-missing-titles-on-dynamic-routes) Generic titles hurt SEO: ``` Blog | MySite ``` Always override with specific content: ``` ``` h3. [Issue 3: Infinite Parameter Variations](#issue-3-infinite-parameter-variations) Dynamic routes with filters create crawl budget waste: ``` // ❌ Generates thousands of URLs /products/:category?sort=price /products/:category?sort=name /products/:category?color=red&sort=price // ... infinite combinations ``` Use `noindex` on filtered pages with route middleware: middleware/filter-noindex.global.ts ``` export default defineNuxtRouteMiddleware((to) => { // Noindex pages with query params if (Object.keys(to.query).length > 0) { useHead({ meta: [ { name: 'robots', content: 'noindex, follow' } ] }) } }) ``` Or block in `robots.txt` with the Robots module: [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes/docs/robots/getting-started/introduction) nuxt.config.ts ``` export default defineNuxtConfig({ robots: { disallow: [ '/*?ref=*', '/*?utm_*' ] } }) ``` Read more about [**controlling crawlers**](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes/learn-seo/nuxt/controlling-crawlers). h3. [Issue 4: 404 Handling](#issue-4-404-handling) Return proper 404 status codes for missing content: ``` ``` Nuxt automatically sets the response status code to 404 during SSR, preventing Google from indexing non-existent pages. h2. [Route Params Table](#route-params-table) Common dynamic route patterns: | **File Pattern** | **Matches** | **Params** | **Use Case** | | --- | --- | --- | --- | | `pages/blog/[slug].vue` | `/blog/vue-guide` | `{ slug: 'vue-guide' }` | Blog posts, articles | | `pages/products/[id].vue` | `/products/123` | `{ id: '123' }` | Product pages | | `pages/docs/[category]/[page].vue` | `/docs/api/methods` | `{ category: 'api', page: 'methods' }` | Documentation | | `pages/user/[id].vue` | `/user/42` | `{ id: '42' }` | User profiles | | `pages/[lang]/about.vue` | `/en/about` | `{ lang: 'en' }` | Internationalized routes | | `pages/files/[...path].vue` | `/files/docs/api.md` | `{ path: ['docs', 'api.md'] }` | Nested file paths | h2. [Route Validation](#route-validation) Validate params with `definePageMeta()`: pages/user/[id].vue ``` ``` Invalid params result in 404, preventing indexing of malformed URLs. h2. [TypeScript Support](#typescript-support) Type route params: ``` // Type-safe route params interface BlogParams { slug: string } const route = useRoute() const slug: string = route.params.slug // Typed ``` Or use Nuxt's generated types: ``` ``` h2. [Verification](#verification) Check what Google indexes: 1. **View Page Source** (not Inspect Element): Right-click → View Page Source. Should show complete HTML with title and meta tags. 2. **Google Search Console URL Inspection**: Test Live URL → View HTML. If you see `Loading...`, your SSR isn't working. 3. **Curl test**: ``` curl https://yoursite.com/blog/vue-guide | grep "" ``` Should return full title tag, not empty or "Loading". Nuxt's SSR works by default. you should always see complete HTML in the source. --- [**404 Pages** 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Nuxt applications.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes/learn-seo/nuxt/routes-and-rendering/404-pages) [**Rendering Modes** How SSR, SSG, and hybrid rendering affect Google indexing. Which mode to use and when in Nuxt.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/dynamic-routes/learn-seo/nuxt/routes-and-rendering/rendering) **On this page** - [Route Params vs Query Parameters](#route-params-vs-query-parameters) - [Basic Dynamic Routes](#basic-dynamic-routes) - [Per-Route Meta Tags](#per-route-meta-tags) - [Multiple Route Params](#multiple-route-params) - [Optional Params](#optional-params) - [Catch-All Routes](#catch-all-routes) - [Programmatic Navigation](#programmatic-navigation) - [Common SEO Issues](#common-seo-issues) - [Route Params Table](#route-params-table) - [Route Validation](#route-validation) - [TypeScript Support](#typescript-support) - [Verification](#verification) --- ### Getting Your Nuxt Site Indexed · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live Description: How to get your Nuxt site crawled and indexed for the first time by Google. h1. **Getting Your Nuxt Site Indexed** How to get your Nuxt site crawled and indexed for the first time by Google. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Oct 25, 2024** Updated **Dec 17, 2025** Deployed your Nuxt site to production? Two steps remain: get Google to crawl it, then get Google to index it. h2. [SSR by Default](#ssr-by-default) Nuxt renders pages on the server by default, which means Google sees fully-rendered HTML immediately. No JavaScript execution delays. This gives you faster, more reliable indexing compared to client-side SPAs. Your meta tags, content, and structured data are all present in the initial HTML response. **If you need client-side rendering for specific routes:** - Use `ssr: false` in [**route rules**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/routes-and-rendering/rendering) - Pre-render pages at build time with `prerender: true` - Set meta tags during SSR to ensure they're always present For authenticated pages or dashboards where SSR isn't needed, configure it per-route: ``` export default defineNuxtConfig({ routeRules: { '/dashboard/**': { ssr: false }, '/blog/**': { prerender: true } } }) ``` h2. [Canonical URL Configuration](#canonical-url-configuration) Multiple domains or subdomains pointing to your site? Only one version should be indexed. Example: `www.example.com` and `example.com` both serve your app, but only `example.com` should appear in Google. **Solutions:** 1. **Server-level redirect** (preferred): 301 redirect all non-canonical URLs 2. **Canonical tags**: Tell Google which version is authoritative ``` const route = useRoute() useHead({ link: [ { rel: 'canonical', href: \`https://example.com${route.path}\` } ] }) ``` If you're using the [**Nuxt SEO Module**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/docs/nuxt-seo/getting-started/introduction) you can use the `redirectToCanonicalSiteUrl` option to automatically redirect non-canonical URLs. See [**Canonical URLs guide**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/controlling-crawlers/canonical-urls) for implementation details. h2. [Set Up Google Search Console](#set-up-google-search-console) [**Google Search Console**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/search-console) is required for monitoring indexing. Set it up before launch: 1. Visit [**search.google.com/search-console**](https://search.google.com/search-console) 2. Add your property (Domain property recommended) 3. Verify ownership via DNS, HTML file, or meta tag 4. Submit your [**sitemap**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/controlling-crawlers/sitemaps) at **Indexing > Sitemaps** For Nuxt apps, use the Sitemap module for automatic sitemap generation: [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/docs/sitemap/getting-started/introduction) Or use the full Nuxt SEO module which includes sitemaps plus robots.txt, OG images, and schema.org: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/docs/nuxt-seo/getting-started/introduction) h2. [Request Indexing](#request-indexing) After sitemap submission, request indexing for important pages: **Manual method:** 1. Open [**URL Inspection**](https://support.google.com/webmasters/answer/9012289) in Search Console 2. Enter your URL 3. Click **Request Indexing** You get [**~10 requests per day**](https://support.google.com/webmasters/answer/9012289). Use them for homepage and critical pages. **Bulk method:** Use [**RequestIndexing**](https://requestindexing.com/) by @harlan_zw to submit multiple URLs automatically. **Instant notification (Bing/Yandex):**[**IndexNow**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/indexnow) notifies search engines immediately when content changes. Google doesn't support it, but Bing and Yandex do. h2. [Core Web Vitals Check](#core-web-vitals-check) [**Google uses Core Web Vitals**](https://developers.google.com/search/docs/appearance/core-web-vitals) as a ranking signal. Check your scores before launch: | **Metric** | **Good** | **Poor** | | --- | --- | --- | | LCP (Largest Contentful Paint) | ≤2.5s | >4s | | INP (Interaction to Next Paint) | ≤200ms | >500ms | | CLS (Cumulative Layout Shift) | ≤0.1 | >0.25 | Use [**PageSpeed Insights**](https://pagespeed.web.dev/) or Lighthouse to test. Don't chase perfect scores. Fix red flags and move on. See [**Core Web Vitals for Nuxt**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/core-web-vitals) for optimization techniques. h2. [Lighthouse SEO Audit](#lighthouse-seo-audit) Run Lighthouse on your key pages. Focus on the **SEO** and **Accessibility** categories. They catch issues Google cares about: - Missing meta descriptions - Images without alt text - Missing lang attribute - Low contrast text - Non-crawlable links Use [**Unlighthouse**](https://unlighthouse.dev/) to audit your entire site in bulk. h2. [Build Initial Backlinks](#build-initial-backlinks) New sites have zero authority. Google is skeptical of them. Signal legitimacy with a few quality backlinks: - Share on Twitter/X, LinkedIn, Reddit (relevant subreddits) - Submit to industry directories - Write guest posts on established sites - Build open-source tools that get linked Quality over quantity. One link from a respected site beats 100 from spam directories. h2. [Common Nuxt-Specific Issues](#common-nuxt-specific-issues) **Meta tags not updating on navigation:** - Use reactive values in `useSeoMeta()` or `useHead()` - Verify tags appear in **View Page Source** (not DevTools) - Check [**Mastering Meta**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/mastering-meta) guides **Slow Time to First Byte (TTFB):** - Optimize server response time - Use a CDN for static assets - Enable Nuxt's built-in caching with `routeRules` - Check [**Core Web Vitals**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/core-web-vitals) for LCP fixes **Pages "Crawled - currently not indexed":** - Content may be too thin or duplicate - Site may lack authority (needs backlinks) - See [**Debugging Indexing Issues**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/indexing-issues) h2. [After Launch](#after-launch) 1. Check [**Search Console**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/search-console) weekly for errors 2. Monitor [**Core Web Vitals**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/core-web-vitals) in field data 3. Track organic traffic with [**SEO Monitoring tools**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/seo-monitoring) 4. Keep publishing content and building backlinks SEO is a long game. Most sites take 3-6 months to see meaningful organic traffic. Don't panic if rankings don't appear immediately. --- [**Launch & Listen** Submit sitemap to Search Console, verify indexing, monitor rankings. Complete post-launch SEO workflow for Nuxt.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen) [**Google Search Console** Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/search-console) **On this page** - [SSR by Default](#ssr-by-default) - [Canonical URL Configuration](#canonical-url-configuration) - [Set Up Google Search Console](#set-up-google-search-console) - [Request Indexing](#request-indexing) - [Core Web Vitals Check](#core-web-vitals-check) - [Lighthouse SEO Audit](#lighthouse-seo-audit) - [Build Initial Backlinks](#build-initial-backlinks) - [Common Nuxt-Specific Issues](#common-nuxt-specific-issues) - [After Launch](#after-launch) --- ### Rendering Modes in Nuxt: SSR, SSG, and Hybrid · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering Description: How SSR, SSG, and hybrid rendering affect Google indexing. Which mode to use and when in Nuxt. h1. **Rendering Modes in Nuxt: SSR, SSG, and Hybrid** How SSR, SSG, and hybrid rendering affect Google indexing. Which mode to use and when in Nuxt. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw) Published **Oct 25, 2024** Updated **Dec 17, 2024** **What you'll learn** - SSR (default) sends full HTML to Google instantly. SSG prerenders at build time for CDN speed - Use `routeRules` to mix modes: prerender blogs, SSR dashboards, SPA for admin - ISR with `swr: 3600` gives SSG speed with hourly freshness Google can render JavaScript. But it's slower and less reliable than HTML. Choose the wrong rendering mode and your content might not get indexed. h2. [Rendering Modes in Nuxt](#rendering-modes-in-nuxt) Nuxt defaults to **SSR (Server-Side Rendering)**. Every page generates HTML on the server by default. You can override this per route using `routeRules`. **SSR (Server-Side Rendering)** - Server sends HTML, JavaScript hydrates (default) **SSG (Static Site Generation)** - Pre-render HTML at build time **SPA (Single Page Application)** - JavaScript renders everything client-side **Hybrid** - Mix modes per route using `routeRules`**ISR (Incremental Static Regeneration)** - Serve cached HTML, rebuild in background ![SSR vs SSG vs SPA Comparison](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering/images/learn-seo/vue/ssr-vs-ssg-vs-spa.svg) h2. [SSR: Server-Side Rendering](#ssr-server-side-rendering) Default behavior in Nuxt. Server generates HTML on each request. Google gets immediate content. nuxt.config.ts ``` export default defineNuxtConfig({ ssr: true // This is already the default }) ``` ✅ **Good for:** - Dynamic content (user profiles, dashboards) - Personalized pages - Real-time data - Content updated frequently ❌ **Don't use for:** - Static blogs (SSG is faster) - Documentation (SSG is cheaper) - Landing pages (SSG serves from CDN) **Performance note:** SSR requires server compute on every request. SSG serves pre-built files from CDN. h2. [SSG: Static Site Generation](#ssg-static-site-generation) Pre-render routes at build time. Run `nuxt generate` or configure prerendering in `nuxt.config.ts`: nuxt.config.ts ``` export default defineNuxtConfig({ nitro: { prerender: { routes: ['/blog', '/docs', '/about'] } } }) ``` Or use `routeRules` for wildcard patterns: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/blog/**': { prerender: true }, '/docs/**': { prerender: true } } }) ``` ✅ **Good for:** - Blogs - Documentation - Marketing pages - Product catalogs - Content updated daily or less ❌ **Don't use for:** - User-specific content - Real-time dashboards - Content changing hourly - Sites with 10,000+ dynamic pages **Build consideration:** A site with 50,000 routes might take 30+ minutes to build. Use SSR or ISR instead. h2. [SPA: Client-Side Rendering](#spa-client-side-rendering) Disable SSR for client-only rendering: nuxt.config.ts ``` export default defineNuxtConfig({ ssr: false }) ``` Or per route: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/admin/**': { ssr: false } } }) ``` Google's crawler downloads JavaScript, waits for execution, then indexes. This adds 5-10 seconds to indexing time. **Use it for:** Internal dashboards, apps behind authentication, content that shouldn't be indexed. **Avoid it for:** Marketing pages, blog posts, product catalogs. anything you want Google to index quickly. **Common mistake:** Launching a SPA site expecting Google to index it immediately. You'll wait weeks longer than SSR. h2. [Hybrid: Per-Route Configuration](#hybrid-per-route-configuration) Mix rendering modes based on route needs using `routeRules`: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // Prerender static content at build time '/': { prerender: true }, '/blog/**': { prerender: true }, '/docs/**': { prerender: true }, // SSR for dynamic content '/user/**': { ssr: true }, '/dashboard/**': { ssr: true }, // SPA for auth-only sections '/admin/**': { ssr: false }, // ISR: Cache with revalidation '/products/**': { swr: 3600 // Revalidate every hour }, // ISR with longer cache '/news/**': { swr: 86400 // Revalidate daily } } }) ``` This is Nuxt's most powerful feature for SEO. You get SSG speed for static content, SSR flexibility for dynamic content, and SPA efficiency for authenticated sections. all in one app. h2. [ISR: Incremental Static Regeneration](#isr-incremental-static-regeneration) Serve cached HTML, rebuild in background. Best of SSG speed + SSR freshness: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/blog/**': { swr: 3600 // Cache for 1 hour, rebuild in background } } }) ``` Google sees instant HTML (like SSG), but content stays fresh (like SSR). **Use it for:** - Product pages that change occasionally - Blog posts with view counts - News articles with updated timestamps - Any content where 1-hour staleness is acceptable **How it works:** 1. First request: Server renders HTML, caches for 1 hour 2. Next requests: Serve cached HTML instantly 3. After 1 hour: Next request triggers background rebuild while serving stale cache 4. Cache updates when rebuild completes h2. [Verifying What Google Sees](#verifying-what-google-sees) Don't guess. Check what Googlebot actually received: **1. Google Search Console URL Inspection** - Go to URL Inspection tool - Enter your URL - Click "Test Live URL" - View "Screenshot" and "HTML" tabs If the HTML tab shows `<div id="__nuxt"></div>` with no content, Google isn't seeing your page. **2. View Page Source** Right-click page > "View Page Source" (not Inspect Element) ✅ **Good:** Full content in HTML ❌ **Bad:** Empty div with JavaScript **3. Fetch as Google** ``` curl -A "Mozilla/5.0 (compatible; Googlebot/2.1)" https://yoursite.com ``` Should return complete HTML with content. **4. Check Build Output** When running `nuxt build`, look for prerendered routes: ``` ℹ Prerendering 42 initial routes with crawler ├── / ├── /blog/post-1 ├── /blog/post-2 └── ... ``` If routes you expect to be prerendered aren't listed, check your `routeRules` configuration. h2. [Common Mistakes](#common-mistakes) **Mistake 1: Using SPA for a blog** You're making Google work 10x harder. SSG renders in milliseconds. **Mistake 2: SSR for static documentation** Why run server compute for content that never changes? SSG is free to serve. **Mistake 3: SSG for 100,000 product pages** Your builds will time out. Use SSR or ISR instead. **Mistake 4: Forgetting route rules** Nuxt defaults to SSR. Without explicit `routeRules`, builds won't prerender anything. you'll need a server for all requests. **Mistake 5: Not testing the output** Always verify with Search Console. Your local dev server lies. **Mistake 6: Mixing conflicting route rules** More specific patterns override general ones. Order matters: ``` // ❌ Wrong: Specific rule comes after general routeRules: { '/blog/**': { prerender: true }, '/blog/draft': { ssr: false } // Won't work, already matched above } // ✅ Right: Specific before general routeRules: { '/blog/draft': { ssr: false }, '/blog/**': { prerender: true } } ``` h2. [Default Recommendation](#default-recommendation) Start with this configuration for content-heavy sites: nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // Static marketing pages '/': { prerender: true }, '/about': { prerender: true }, '/contact': { prerender: true }, // Blog and docs '/blog/**': { prerender: true }, '/docs/**': { prerender: true }, // Products with ISR '/products/**': { swr: 3600 // Fresh within 1 hour }, // User-specific content '/dashboard/**': { ssr: true }, '/account/**': { ssr: true }, // Admin panel (no SEO needed) '/admin/**': { ssr: false } } }) ``` Adjust based on your content update frequency. h2. [Nuxt Content Integration](#nuxt-content-integration) When using `@nuxt/content`, enable crawler to auto-discover routes: nuxt.config.ts ``` export default defineNuxtConfig({ nitro: { prerender: { crawlLinks: true, routes: ['/'] } } }) ``` Nuxt will crawl your site starting from `/`, discovering all `<NuxtLink>` references and prerendering them automatically. **For dynamic routes:** nuxt.config.ts ``` export default defineNuxtConfig({ nitro: { prerender: { routes: async () => { // Generate routes from content const { $content } = useNuxtApp() const posts = await $content('blog').find() return posts.map(post => \`/blog/${post.slug}\`) } } } }) ``` h2. [Deployment Considerations](#deployment-considerations) Different hosts support different rendering modes: | **Platform** | **SSR** | **SSG** | **ISR/SWR** | | --- | --- | --- | --- | | Netlify | ✅ | ✅ | ✅ (via functions) | | Vercel | ✅ | ✅ | ✅ (ISR built-in) | | Cloudflare Pages | ✅ | ✅ | ✅ (via KV) | | Static hosts (S3, GitHub Pages) | ❌ | ✅ | ❌ | | Node.js server | ✅ | ✅ | ⚠️ (needs cache layer) | Choose your rendering strategy based on your hosting platform. --- [**Dynamic Routes** How to use Nuxt's file-based dynamic routes, set per-route meta tags, and avoid duplicate content issues with URL parameters.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering/learn-seo/nuxt/routes-and-rendering/dynamic-routes) [**Security** Robots.txt is a polite suggestion. Malicious crawlers ignore it. Here's how to actually protect your Nuxt app.](https://nuxtseo.com/learn-seo/nuxt/routes-and-rendering/rendering/learn-seo/nuxt/routes-and-rendering/security) **On this page** - [Rendering Modes in Nuxt](#rendering-modes-in-nuxt) - [SSR: Server-Side Rendering](#ssr-server-side-rendering) - [SSG: Static Site Generation](#ssg-static-site-generation) - [SPA: Client-Side Rendering](#spa-client-side-rendering) - [Hybrid: Per-Route Configuration](#hybrid-per-route-configuration) - [ISR: Incremental Static Regeneration](#isr-incremental-static-regeneration) - [Verifying What Google Sees](#verifying-what-google-sees) - [Common Mistakes](#common-mistakes) - [Default Recommendation](#default-recommendation) - [Nuxt Content Integration](#nuxt-content-integration) - [Deployment Considerations](#deployment-considerations) --- ### Set Up Google Search Console for Your Nuxt Site · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/search-console Description: Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console. h1. **Set Up Google Search Console for Your Nuxt Site** Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** **What you'll learn** - Use Domain property for full coverage. Includes all subdomains, HTTP/HTTPS, requires DNS verification only - Submit sitemap at Indexing → Sitemaps, then use URL Inspection to request indexing for critical pages - Check Page Indexing report weekly. "Crawled not indexed" means content quality issues, not technical problems [**Google Search Console**](https://search.google.com/search-console) shows which pages Google indexed, what queries bring traffic, and what's broken. Every site needs this. Without it you're blind to [**80% of indexing issues**](https://developers.google.com/search/docs/monitor-debug/search-console-start). h2. [Create a Search Console Property](#create-a-search-console-property) Visit [**search.google.com/search-console**](https://search.google.com/search-console) and add a property. You get two types: **Domain property** (`example.com`): - Includes all subdomains (`www`, `m`, `blog`) - Covers both HTTP and HTTPS - Requires DNS verification only **URL-prefix property** (`https://example.com`): - Single protocol and subdomain - Multiple verification methods - Better for subsections or testing [**Use Domain property**](https://support.google.com/webmasters/answer/9008080) unless you only control a subdomain. It captures all traffic variations without managing separate properties. h2. [Verification Methods](#verification-methods) Google needs proof you own the site. Pick one method and don't remove it. Google [**checks verification periodically**](https://www.bluehost.com/blog/verify-website-ownership-google-search-console/). h3. [DNS Verification (Recommended)](#dns-verification-recommended) Add a TXT record to your domain's DNS settings. This is the only method for Domain properties and the most reliable overall. **Steps:** 1. Google provides a TXT record like `google-site-verification=abc123xyz` 2. Add it to your DNS at your registrar (GoDaddy, Namecheap, Cloudflare, etc.) 3. Click **Verify** in Search Console DNS changes take up to 48 hours but verification works immediately once the record propagates. This method [**eliminates re-verification**](https://kinsta.com/blog/google-site-verification/) if you switch between `www` and non-`www` URLs. ``` Type: TXT Name: @ Content: google-site-verification=abc123xyz TTL: Auto ``` ``` Record type: TXT Name: example.com Value: "google-site-verification=abc123xyz" TTL: 300 ``` h3. [HTML Meta Tag](#html-meta-tag) Add a `<meta>` tag to your site's homepage `<head>`. Works for URL-prefix properties. app.vue ``` useHead({ meta: [ { name: 'google-site-verification', content: 'abc123xyz' } ] }) ``` The tag must appear in the HTML source. Check with **View Page Source** (not DevTools). Nuxt's SSR ensures the tag is present in the initial HTML response. Common issue: If you use a layout component for the tag, make sure it renders on every page including the homepage. h3. [HTML File Upload](#html-file-upload) Upload `google-site-verification.html` to your site's root directory. Works for URL-prefix properties. **Steps:** 1. Download the verification file from Search Console 2. Place it in your `public/` directory 3. Verify it loads at `https://example.com/google-site-verification.html` 4. Click **Verify** in Search Console Don't delete this file after verification. Google re-checks it. h3. [Google Analytics](#google-analytics) If you already use Google Analytics with the GA4 tracking code on your homepage, Search Console can verify via that tag. **Requirements:** - GA4 property set up - Tracking code in `<head>` section - You have "Edit" permission on the GA4 property [**This is the fastest method**](https://medium.com/@makarenko.roman121/how-do-you-verify-a-site-in-google-search-console-e0d179eb4ce9) if you already have analytics. No code changes needed. h3. [Google Tag Manager](#google-tag-manager) Same concept as Google Analytics. If you have GTM installed and publishing, Search Console auto-detects it for verification. **Requirements:** - GTM container installed on homepage - You have "Publish" permission on the container h2. [Submit Your Sitemap](#submit-your-sitemap) After verification, tell Google where to find your pages: 1. Navigate to **Sitemaps** under **Indexing** 2. Enter your sitemap URL: `https://example.com/sitemap.xml` 3. Click **Submit** Status meanings: - **Success**: sitemap parsed, URLs discovered - **Couldn't fetch**: URL wrong or [**blocked by robots.txt**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) - **Pending**: processing in queue Check back in 24-48 hours for discovered page counts. If Google found 0 URLs but your sitemap has URLs, your XML syntax is broken or the URLs return non-200 status codes. You can [**reference your sitemap in robots.txt**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/search-console/learn-seo/nuxt/controlling-crawlers/robots-txt) as an alternative submission method. Google auto-discovers it when crawling. h2. [Key Reports Explained](#key-reports-explained) h3. [Performance Report](#performance-report) Shows the last 16 months of search data: **Metrics:** - **Impressions**: how many times your pages appeared in search results - **Clicks**: how many users clicked through - **CTR (Click-Through Rate)**: clicks ÷ impressions - **Position**: average ranking position (lower is better) Filter by: - **Queries**: what terms triggered your pages - **Pages**: which URLs get the most traffic - **Countries**: geographic breakdown - **Devices**: mobile vs desktop vs tablet Use this to find [**low-CTR pages with high impressions**](https://www.taylorscherseo.com/blog/google-search-console-audit). They rank well but have poor titles or descriptions. Fix the meta tags and watch CTR climb. h3. [Page Indexing Report](#page-indexing-report) Replaced the old Coverage report in 2025. Shows indexing status for all discovered URLs: **Categories:** - **Indexed**: pages in Google's index - **Not indexed**: pages excluded or rejected - **Crawled - currently not indexed**: pages Google saw but chose not to index Click any category to see specific reasons like "Duplicate content", "Soft 404", or "Crawled but not indexed". The [**June 2025 core update**](https://www.getpassionfruit.com/blog/why-website-pages-are-getting-de-indexed-after-june-2025-google-core-update-complete-recovery-guide) aggressively deindexed low-quality pages. This report shows the damage. Common issues: - **Page not found (404)**: sitemap lists URLs that don't exist - **Soft 404**: page returns 200 but contains "not found" content - **Redirect**: sitemap lists old URLs that redirect to new ones - **Blocked by robots.txt**: you accidentally blocked indexable pages h3. [URL Inspection Tool](#url-inspection-tool) Enter any URL to see: - Whether it's indexed - When Google last crawled it - Rendered HTML (what Googlebot saw after executing JavaScript) - Screenshot of the rendered page - Coverage errors or warnings - Mobile usability issues - Structured data found Click **Request Indexing** to ask Google to crawl the URL immediately. You get [**~10 requests per day**](https://support.google.com/webmasters/answer/9012289). Use them for new or updated critical pages. The rendered screenshot is gold for debugging. If it's blank or shows loading spinners, Google couldn't execute your JavaScript properly. h2. [Common Actions](#common-actions) h3. [Request Indexing](#request-indexing) After publishing new content: 1. Open **URL Inspection** tool 2. Enter the new URL 3. Click **Request Indexing** [**Google typically crawls within hours**](https://developers.google.com/search/docs/monitor-debug/search-console-start) but sometimes takes days. Requesting indexing doesn't guarantee it. Google still evaluates quality. h3. [Remove URLs Temporarily](#remove-urls-temporarily) Need to hide a URL from search results quickly? 1. Navigate to **Removals** under **Indexing** 2. Click **New Request** 3. Enter the URL 4. Choose removal type Removals last [**approximately 6 months**](https://support.google.com/webmasters/answer/9689846). For permanent removal, return 404/410 status or add `noindex` meta tag, then request removal. h3. [Security Issues](#security-issues) If Google detects malware or hacking, you'll see alerts in Search Console. Fix the issue first (remove malware, update plugins, patch vulnerabilities), then request a security review under **Security Issues**. Ignore these warnings and Google may deindex your entire site to protect users. h2. [Common Search Console Issues](#common-search-console-issues) **"Index Coverage report delayed since Nov 2025":** [**This is a known bug**](https://www.webpronews.com/google-search-console-index-coverage-report-delayed-since-nov-2025/). Google confirmed it's a reporting issue only. Actual crawling and indexing still work. Use the URL Inspection tool for real-time checks on individual pages. **Sitemap shows "Discovered - currently not indexed":** Google found the URLs but chose not to index them. Reasons include: - Low quality content - Duplicate content - Thin content (under 300 words) - E-E-A-T signals too weak ([**June 2025 update issue**](https://www.getpassionfruit.com/blog/why-website-pages-are-getting-de-indexed-after-june-2025-google-core-update-complete-recovery-guide)) Fix content quality issues before re-submitting. Don't spam "Request Indexing". It doesn't override quality filters. **Verification suddenly fails:** You accidentally removed the verification method. Common causes: - Deleted DNS record during domain migration - Removed meta tag when refactoring `<head>` tags - Deleted HTML verification file during deploy - Lost GTM/GA permissions Re-verify using the original method. --- [**Getting Indexed** How to get your Nuxt site crawled and indexed for the first time by Google.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/search-console/learn-seo/nuxt/launch-and-listen/going-live) [**Core Web Vitals** Measure and optimize LCP, INP, and CLS in Nuxt apps to improve user experience and search rankings.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/search-console/learn-seo/nuxt/launch-and-listen/core-web-vitals) **On this page** - [Create a Search Console Property](#create-a-search-console-property) - [Verification Methods](#verification-methods) - [Submit Your Sitemap](#submit-your-sitemap) - [Key Reports Explained](#key-reports-explained) - [Common Actions](#common-actions) - [Common Search Console Issues](#common-search-console-issues) --- ### Core Web Vitals in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/core-web-vitals Description: Measure and optimize LCP, INP, and CLS in Nuxt apps to improve user experience and search rankings. h1. **Core Web Vitals in Nuxt** Measure and optimize LCP, INP, and CLS in Nuxt apps to improve user experience and search rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** **What you'll learn** - LCP ≤2.5s, INP ≤200ms, CLS ≤0.1 are the thresholds. 75% of visits must pass for "Good" status - Nuxt's SSR and automatic code splitting give you a head start on LCP - Use `@nuxt/image` with `priority` prop for above-fold images, `loading="lazy"` for below-fold [**Core Web Vitals**](https://developers.google.com/search/docs/appearance/core-web-vitals) became a Google ranking factor in June 2021. The metrics measure loading performance, interactivity, and visual stability. Aspects that directly impact user experience and search rankings. h2. [The Three Metrics](#the-three-metrics) h3. [LCP (Largest Contentful Paint)](#lcp-largest-contentful-paint) LCP measures how long it takes for the largest visible element to render. This is typically a hero image, heading, or large text block. **Thresholds:** - Good: ≤ 2.5 seconds - Needs improvement: 2.5–4.0 seconds - Poor: > 4.0 seconds [**Google's research**](https://web.dev/blog/inp-cwv) shows sites with good LCP see 24% lower bounce rates. h3. [INP (Interaction to Next Paint)](#inp-interaction-to-next-paint) INP replaced First Input Delay (FID) in [**March 2024**](https://web.dev/blog/inp-cwv-march-12). FID only measured the first interaction. INP measures all interactions throughout the page lifecycle: clicks, taps, keyboard presses. **Thresholds:** - Good: ≤ 200 milliseconds - Needs improvement: 200–500 milliseconds - Poor: > 500 milliseconds [**Chrome usage data**](https://web.dev/blog/inp-cwv) shows users spend 90% of their time on a page after it loads, making responsiveness throughout the session more important than first-interaction metrics. h3. [CLS (Cumulative Layout Shift)](#cls-cumulative-layout-shift) CLS measures visual stability. Every unexpected layout shift gets scored: images loading without dimensions, fonts causing text reflow, dynamic content insertion. **Thresholds:** - Good: ≤ 0.1 - Needs improvement: 0.1–0.25 - Poor: > 0.25 h2. [Nuxt LCP Optimizations](#nuxt-lcp-optimizations) h3. [SSR by Default](#ssr-by-default) Nuxt renders pages on the server by default, which significantly improves LCP compared to client-side rendering. The browser receives fully rendered HTML, eliminating the wait for JavaScript execution before content appears. No configuration needed. Nuxt handles this automatically. h3. [Image Optimization with @nuxt/image](#image-optimization-with-nuxtimage) Install the Nuxt Image module for automatic image optimization: ``` npx nuxi@latest module add image ``` Use `<NuxtImg>` and `<NuxtPicture>` for optimized delivery: ``` <template> <div> <!-- Above-fold: prioritize LCP image --> <NuxtImg src="/hero.jpg" alt="Hero" width="1200" height="600" priority /> <!-- Below-fold: lazy loading --> <NuxtImg src="/product-1.jpg" alt="Product" width="800" height="600" loading="lazy" /> </div> </template> ``` The `priority` prop preloads the LCP image and disables lazy loading. `@nuxt/image` automatically generates responsive sizes, modern formats (WebP/AVIF), and optimizes delivery. h3. [Preload Critical Assets](#preload-critical-assets) Add preload hints in your page or layout: ``` <script setup> useHead({ link: [ { rel: 'preload', href: '/fonts/inter-var.woff2', as: 'font', type: 'font/woff2', crossorigin: '' } ] }) </script> ``` h3. [Optimize Font Loading](#optimize-font-loading) Use `font-display: swap` to show system fonts immediately while web fonts load: ``` @font-face { font-family: 'Inter'; src: url('/fonts/inter-var.woff2') format('woff2'); font-display: swap; } ``` Configure global font preloading in `nuxt.config.ts`: ``` export default defineNuxtConfig({ app: { head: { link: [ { rel: 'preload', href: '/fonts/inter-var.woff2', as: 'font', type: 'font/woff2', crossorigin: '' } ] } } }) ``` h3. [Automatic Code Splitting](#automatic-code-splitting) Nuxt automatically splits code at the route level. Each page in `/pages/` becomes a separate chunk, loading only what's needed: ``` pages/ index.vue → Chunk: index.vue dashboard.vue → Chunk: dashboard.vue blog/ [slug].vue → Chunk: blog-[slug].vue ``` No configuration required. Nuxt handles this automatically during build. For component-level splitting, use lazy loading: ``` <script setup> // Lazy load heavy components const LazyChart = defineAsyncComponent(() => import('~/components/Chart.vue')) </script> <template> <div> <LazyChart v-if="showChart" /> </div> </template> ``` h2. [Nuxt INP Optimizations](#nuxt-inp-optimizations) h3. [Debounce Event Handlers](#debounce-event-handlers) Prevent performance issues from rapid-fire events: ``` <script setup> import { ref } from 'vue' const searchQuery = ref('') let timeout = null function handleSearch(event) { clearTimeout(timeout) timeout = setTimeout(() => { searchQuery.value = event.target.value // Perform search }, 300) } </script> <template> <input placeholder="Search..." @input="handleSearch"> </template> ``` h3. [Use v-memo for Large Lists](#use-v-memo-for-large-lists) [**v-memo**](https://www.bairesdev.com/blog/vue-performance-optimization/) memoizes component output to skip re-renders: ``` <template> <div v-for="item in items" :key="item.id" v-memo="[item.id, item.selected]"> {{ item.name }} </div> </template> ``` Re-renders only happen when `item.id` or `item.selected` changes. h3. [Offload Heavy Computation to Web Workers](#offload-heavy-computation-to-web-workers) Keep the main thread responsive: ``` // worker.ts self.addEventListener('message', (e) => { const result = heavyComputation(e.data) self.postMessage(result) }) // component.vue const worker = new Worker(new URL('./worker.ts', import.meta.url)) worker.postMessage(data) worker.addEventListener('message', (e) => { console.log('Result:', e.data) }) ``` h3. [Optimize Computed Properties](#optimize-computed-properties) Computed properties cache results. Use them instead of methods for expensive operations: ``` <script setup> import { computed, ref } from 'vue' const items = ref([...largeArray]) // Cached, recalculates only when items changes const filteredItems = computed(() => items.value.filter(item => item.active) ) </script> ``` [**Breaking long tasks**](https://rankture.com/blog/core-web-vitals-optimization-guide) can reduce INP from 350ms to 120ms. h2. [Nuxt CLS Fixes](#nuxt-cls-fixes) h3. [Set Image Dimensions](#set-image-dimensions) Prevent layout shifts by reserving space: ``` <template> <NuxtImg src="/product.jpg" alt="Product" width="800" height="600" /> </template> ``` Or use aspect ratio with CSS: ``` <template> <div class="image-container"> <NuxtImg src="/product.jpg" alt="Product" /> </div> </template> <style scoped> .image-container { aspect-ratio: 16 / 9; overflow: hidden; } .image-container img { width: 100%; height: 100%; object-fit: cover; } </style> ``` h3. [Reserve Space for Dynamic Content](#reserve-space-for-dynamic-content) Skeleton screens or placeholders prevent shifts: ``` <script setup> const { data, pending } = await useFetch('/api/data') </script> <template> <div class="content"> <div v-if="pending" class="skeleton"> <!-- Same dimensions as actual content --> </div> <div v-else> {{ data }} </div> </div> </template> <style scoped> .skeleton { height: 200px; /* Match real content height */ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); } </style> ``` h3. [Avoid Flash of Unstyled Content (FOUC)](#avoid-flash-of-unstyled-content-fouc) Preload fonts and use `font-display: swap`: ``` // nuxt.config.ts export default defineNuxtConfig({ app: { head: { link: [ { rel: 'preload', href: '/fonts/inter-var.woff2', as: 'font', type: 'font/woff2', crossorigin: '' } ] } } }) ``` ``` @font-face { font-family: 'Inter'; src: url('/fonts/inter-var.woff2') format('woff2'); font-display: swap; size-adjust: 100%; /* Adjust fallback font metrics */ } ``` [**Adjusting local system fonts to match web fonts**](https://www.ateamsoftsolutions.com/core-web-vitals-optimization-guide-2025-showing-lcp-inp-cls-metrics-and-performance-improvement-strategies-for-web-applications-2/) using fallback metrics reduces CLS. h3. [Handle Ads and Embeds](#handle-ads-and-embeds) Reserve space for third-party content: ``` <template> <div class="ad-container" style="min-height: 250px;"> <!-- Ad loads here --> </div> </template> ``` h2. [Measuring Core Web Vitals](#measuring-core-web-vitals) h3. [PageSpeed Insights](#pagespeed-insights) [**PageSpeed Insights**](https://pagespeed.web.dev/) shows field data (real user metrics from Chrome UX Report) and lab data (simulated tests). Field data is what Google uses for rankings. Lab data helps debug specific issues. h3. [Lighthouse](#lighthouse) Run Lighthouse in Chrome DevTools (Performance tab) for local testing. It measures LCP, CLS, and provides optimization suggestions. h3. [Chrome DevTools Performance Panel](#chrome-devtools-performance-panel) Record a session to see frame-by-frame rendering: 1. Open DevTools → Performance 2. Click Record 3. Interact with your page 4. Stop recording 5. View LCP, CLS, and long tasks [**Chrome DevTools**](https://www.clapcreative.com/web-vitals-in-2025-whats-new-and-how-to-stay-compliant/) shows local LCP and CLS scores instantly. Interact with the page to capture INP. h3. [web-vitals Library](#web-vitals-library) [**Google's web-vitals library**](https://github.com/GoogleChrome/web-vitals) sends metrics to your analytics: ``` import { onCLS, onINP, onLCP } from 'web-vitals' onLCP((metric) => { console.log('LCP:', metric.value) // Send to analytics sendToAnalytics({ metric: 'LCP', value: metric.value }) }) onINP((metric) => { console.log('INP:', metric.value) sendToAnalytics({ metric: 'INP', value: metric.value }) }) onCLS((metric) => { console.log('CLS:', metric.value) sendToAnalytics({ metric: 'CLS', value: metric.value }) }) function sendToAnalytics({ metric, value }) { // Send to Google Analytics, Plausible, etc. if (window.gtag) { window.gtag('event', metric, { value: Math.round(value) }) } } ``` [**Capture p75 and p95 metrics**](https://madewithvuejs.com/blog/advanced-vue-performance-monitoring) for all critical flows, sliced by device and region. h2. [Core Web Vitals Impact on Rankings](#core-web-vitals-impact-on-rankings) Google treats Core Web Vitals as a [**tiebreaker ranking factor**](https://developers.google.com/search/docs/appearance/core-web-vitals). Content relevance is still the primary signal. For queries with multiple relevant results, good page experience can be the differentiator. [**Industry research shows**](https://rankinggenerals.com/2025-core-web-vitals/) Core Web Vitals account for 10–15% of ranking signals. Only [**47% of websites pass all three metrics in 2025**](https://www.brightvessel.com/core-web-vitals-in-2025-how-they-affect-google-rankings-and-user-experience/). Sites passing all three vitals see: - 24% lower bounce rates - 25% higher conversion rates moving from Poor to Good - 30% revenue improvements from higher engagement [**Mobile-first indexing**](https://systemsarchitect.net/core-web-vitals-2025/) now treats Core Web Vitals as indexing requirements rather than just ranking signals. Google evaluates mobile versions of sites primarily. To pass assessment, [**75% of page visits**](https://increv.co/academy/core-web-vitals/) must meet "Good" thresholds. h2. [Quick Check](#quick-check) **Quick Check** **What percentage of page visits must pass Core Web Vitals thresholds for a 'Good' rating?** - `50%`: That's the threshold for "Needs Improvement", not "Good" - `75%`: Correct! 75% of visits must have LCP ≤2.5s, INP ≤200ms, and CLS ≤0.1 - `100%`: Google uses the 75th percentile, not 100%, to account for edge cases and slow connections --- [**Google Search Console** Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/core-web-vitals/learn-seo/nuxt/launch-and-listen/search-console) [**Indexing Issues** Fix "crawled currently not indexed" and other GSC coverage errors affecting your Nuxt site.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/core-web-vitals/learn-seo/nuxt/launch-and-listen/indexing-issues) **On this page** - [The Three Metrics](#the-three-metrics) - [Nuxt LCP Optimizations](#nuxt-lcp-optimizations) - [Nuxt INP Optimizations](#nuxt-inp-optimizations) - [Nuxt CLS Fixes](#nuxt-cls-fixes) - [Measuring Core Web Vitals](#measuring-core-web-vitals) - [Core Web Vitals Impact on Rankings](#core-web-vitals-impact-on-rankings) - [Quick Check](#quick-check) --- ### Debug Indexing Issues in Google Search Console · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues Description: Fix "crawled currently not indexed" and other GSC coverage errors affecting your Nuxt site. h1. **Debug Indexing Issues in Google Search Console** Fix "crawled currently not indexed" and other GSC coverage errors affecting your Nuxt site. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** **What you'll learn** - "Crawled - currently not indexed" means Google saw your page but chose not to index it. Fix content quality, not technical issues - Verify SSR works by checking View Source for your content, not just DevTools - Use `useFetch()` for data. `onMounted()` fetches won't be in the initial HTML Google crawls Google crawled your page but won't index it. This happens to [**millions of pages daily**](https://www.onely.com/blog/how-to-fix-crawled-currently-not-indexed-in-google-search-console/). The Page Indexing report in Google Search Console shows exactly which pages have issues and why. h2. [Understanding Page Indexing Status](#understanding-page-indexing-status) Google Search Console's Page Indexing report shows six main statuses: ![Page Indexing Status Flowchart](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues/images/learn-seo/vue/indexing-status-flowchart.svg) h3. [Good Statuses](#good-statuses) **Indexed**: Page appears in Google's search index. This is what you want. h3. [Warning Statuses](#warning-statuses) **Discovered - currently not indexed**: Google found your page but hasn't crawled it yet. The page sits in Google's queue. This is normal for new sites and low-priority pages. **Crawled - currently not indexed**: Google crawled your page but chose not to index it. This means Google decided your content isn't worth showing in search results. h3. [Excluded Statuses](#excluded-statuses) **Excluded by robots.txt**: Your robots.txt file blocks Google from accessing the page. Check your robots.txt configuration. **Blocked by noindex tag**: Page has a `noindex` meta tag or HTTP header. Remove it if you want the page indexed. **Duplicate without canonical**: Google found multiple identical pages without proper canonical tags. Set canonical URLs. **Soft 404**: Page returns a 200 status code but looks like a 404 error to Google. Fix by returning proper 404 status codes for missing content. h2. [Fixing "Crawled - Currently Not Indexed"](#fixing-crawled-currently-not-indexed) This status means Google crawled your page but decided it wasn't worth indexing. [**Google doesn't explicitly state why**](https://support.google.com/webmasters/answer/7440203), but five main causes exist. h3. [Thin or Low-Quality Content](#thin-or-low-quality-content) Google skips pages with little unique value. Product pages with only titles and prices, blog posts under 300 words, and auto-generated content typically get excluded. Fix: Add substantial content. Write detailed product descriptions, expand short blog posts to 800+ words, include images and videos, answer user questions comprehensively. h3. [Duplicate Content](#duplicate-content) Multiple pages with identical or near-identical content waste Google's crawl budget. Common culprits: paginated URLs without proper canonicals, URL parameters creating duplicate versions, tag/category archives with the same posts. Fix: Implement canonical tags pointing to the primary version. Consolidate similar pages. Use `useSeoMeta()` in your Nuxt pages: ``` <script setup> useSeoMeta({ canonical: 'https://yoursite.com/primary-page' }) </script> ``` Or set site-wide canonical logic in `nuxt.config.ts`: ``` export default defineNuxtConfig({ site: { url: 'https://yoursite.com' }, modules: ['@nuxtjs/seo'] }) ``` The Nuxt SEO module automatically generates canonical URLs based on your site URL and current route. h3. [Too Many Similar Pages](#too-many-similar-pages) Sites with thousands of nearly-identical pages (e.g., faceted search, filter combinations) trigger quality filters. Google picks representative pages and excludes the rest. Fix: Use meta robots `noindex` on filter pages, parameter-based URLs, and search result pages. Consolidate similar content into fewer comprehensive pages. ``` <script setup> const route = useRoute() // Noindex pages with filter parameters const shouldNoIndex = computed(() => route.query.color || route.query.size || route.query.sort ) useSeoMeta({ robots: shouldNoIndex.value ? 'noindex,follow' : 'index,follow' }) </script> ``` h3. [Low Site Authority](#low-site-authority) New sites with few backlinks face stricter indexing thresholds. [**Google prioritizes crawling trusted sites**](https://www.onely.com/blog/how-to-fix-crawled-currently-not-indexed-in-google-search-console/). Fix: Build backlinks through guest posting, create linkable assets (tools, research, guides), get listed in industry directories, promote content on social media. This takes months. Be patient. h3. [Orphan Pages](#orphan-pages) Pages without internal links from other pages on your site signal low importance to Google. [**Orphan pages lack link equity**](https://mangools.com/blog/orphan-pages/) and often don't get indexed. Fix: Add internal links from relevant pages. Include new pages in your main navigation or sidebar. Link from high-authority pages on your site. ``` <!-- Link to important pages from your main layout --> <template> <nav> <NuxtLink to="/important-page"> Important Page </NuxtLink> </nav> </template> ``` h2. [Fixing "Discovered - Currently Not Indexed"](#fixing-discovered-currently-not-indexed) This status means Google knows your page exists but hasn't crawled it. [**Three main causes exist**](https://www.onely.com/blog/how-to-fix-discovered-currently-not-indexed-in-google-search-console/). h3. [Site Too New](#site-too-new) Google takes weeks to crawl new sites. For brand-new domains, expect 2-4 weeks before regular crawling starts. Fix: Wait. Submit your sitemap. Request indexing for critical pages via URL Inspection tool. Keep publishing content regularly. h3. [Crawl Budget Issues](#crawl-budget-issues) Large sites (10,000+ pages) run into Google's crawl budget limits. Google won't crawl everything if your site has [**slow server responses, too many low-quality pages, or complex URL structures**](https://support.google.com/webmasters/community-guide/278777978). Fix: Optimize server response times (target under 200ms). Remove or noindex low-value pages. Fix redirect chains. Reduce duplicate content. Block unnecessary URLs in robots.txt: ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/robots'], robots: { disallow: [ '/admin/', '/search?*', '/*?filter=*', '/print-version/' ] } }) ``` h3. [Slow Server Response](#slow-server-response) If your server takes over 500ms to respond, [**Google may crawl fewer pages**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget). Fix: Enable caching, use a CDN, optimize database queries, upgrade hosting. Monitor server response times in Search Console's Crawl Stats report. Nuxt's SSR caching helps with this: ``` // nuxt.config.ts export default defineNuxtConfig({ routeRules: { // Cache static pages for 1 hour '/blog/**': { swr: 3600 }, // Cache API responses '/api/**': { cache: { maxAge: 60 } } } }) ``` h3. [Pages Only in Sitemap](#pages-only-in-sitemap) If pages exist only in your sitemap without internal links, Google considers them low priority. Fix: Add internal links. Don't rely solely on sitemaps for discovery. [**Internal linking signals importance**](https://seotesting.com/google-search-console/discovered-currently-not-indexed/). h2. [Verifying SSR in Nuxt](#verifying-ssr-in-nuxt) Nuxt renders pages on the server by default, which helps indexing. Verify your SSR is working correctly: h3. [Check Server Response](#check-server-response) View your page source to confirm content is in the initial HTML: ``` h1. Check if content is in server-rendered HTML curl -s https://yoursite.com/page | grep "expected content" ``` All your content should be visible in the raw HTML response. If it's not, check that: 1. Your page doesn't have `ssr: false` in `definePageMeta()` 2. Data fetching uses `useAsyncData()` or `useFetch()` (not client-only methods) 3. Your `nuxt.config.ts` doesn't have `ssr: false` h3. [Verify Data Fetching](#verify-data-fetching) Ensure data loads during SSR, not just on client: ``` <script setup> // CORRECT: Data available during SSR const { data: products } = await useFetch('/api/products') // WRONG: Only loads on client // onMounted(async () => { // products.value = await $fetch('/api/products') // }) </script> <template> <div v-for="product in products" :key="product.id"> {{ product.name }} </div> </template> ``` h3. [Handle Client-Only Content](#handle-client-only-content) For content that must load client-side, provide fallback text that Google can index: ``` <template> <div> <h1>Product Catalog</h1> <ClientOnly> <LazyProductList /> <template #fallback> <p>Loading 500+ products from our catalog...</p> </template> </ClientOnly> </div> </template> ``` h3. [Test Rendering Modes](#test-rendering-modes) Nuxt supports hybrid rendering. Verify your route rules are configured correctly: ``` // nuxt.config.ts export default defineNuxtConfig({ routeRules: { // Static pages (prerendered) '/': { prerender: true }, '/about': { prerender: true }, // Dynamic pages (SSR) '/blog/**': { swr: 3600 }, // Client-only pages (if needed) '/dashboard/**': { ssr: false } } }) ``` Use Search Console's URL Inspection tool to verify Google sees server-rendered content: 1. Open Search Console → URL Inspection 2. Enter your page URL 3. Click "Test Live URL" 4. Click "View Tested Page" → "Screenshot" Compare the screenshot to your actual page. With proper SSR, they should be identical. h2. [Requesting Re-Indexing](#requesting-re-indexing) After fixing issues, request re-indexing via URL Inspection: 1. Search Console → URL Inspection 2. Enter fixed URL 3. Click "Request Indexing" Google prioritizes these requests but doesn't guarantee indexing. [**It still evaluates content quality**](https://support.google.com/webmasters/answer/7440203). For many URLs, request indexing programmatically using the [**Google Indexing API**](https://developers.google.com/search/apis/indexing-api/v3/quickstart): ``` // server/api/request-indexing.post.ts import { google } from 'googleapis' export default defineEventHandler(async (event) => { const { url } = await readBody(event) const auth = await google.auth.getClient({ scopes: ['https://www.googleapis.com/auth/indexing'] }) const indexing = google.indexing({ version: 'v3', auth }) await indexing.urlNotifications.publish({ requestBody: { url, type: 'URL_UPDATED' } }) return { success: true } }) ``` h2. [Monitoring Progress](#monitoring-progress) Track indexing status changes over time: 1. Search Console → Page Indexing 2. Check "Not indexed" count weekly 3. Look for status changes from "Crawled - not indexed" to "Indexed" Expect changes to take 1-4 weeks. [**Google doesn't index on demand**](https://www.onely.com/blog/how-to-fix-crawled-currently-not-indexed-in-google-search-console/). It re-evaluates pages on its schedule. --- [**Core Web Vitals** Measure and optimize LCP, INP, and CLS in Nuxt apps to improve user experience and search rankings.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues/learn-seo/nuxt/launch-and-listen/core-web-vitals) [**SEO Monitoring** Set up analytics, rank tracking, and alerts to monitor your Nuxt site's search performance.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexing-issues/learn-seo/nuxt/launch-and-listen/seo-monitoring) **On this page** - [Understanding Page Indexing Status](#understanding-page-indexing-status) - [Fixing "Crawled - Currently Not Indexed"](#fixing-crawled-currently-not-indexed) - [Fixing "Discovered - Currently Not Indexed"](#fixing-discovered-currently-not-indexed) - [Verifying SSR in Nuxt](#verifying-ssr-in-nuxt) - [Requesting Re-Indexing](#requesting-re-indexing) - [Monitoring Progress](#monitoring-progress) --- ### SEO Monitoring Tools for Nuxt Sites · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/seo-monitoring Description: Set up analytics, rank tracking, and alerts to monitor your Nuxt site's search performance. h1. **SEO Monitoring Tools for Nuxt Sites** Set up analytics, rank tracking, and alerts to monitor your Nuxt site's search performance. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** Once your Nuxt site is live, you need data. Traffic numbers, keyword rankings, and technical issues don't surface themselves. You track them with the right tools. h2. [Start with Free Tools](#start-with-free-tools) h3. [Google Search Console](#google-search-console) [**Search Console**](https://search.google.com/search-console) shows you what Google sees. Add your property, verify ownership, submit your sitemap, and check back in 48 hours. Track these metrics: - **Impressions** - How many times your pages appeared in search - **Clicks** - Actual traffic from Google - **Average position** - Where you rank for queries - **Coverage errors** - Pages Google can't index Check the Performance report weekly. Sort by pages with high impressions but low clicks. These need better titles or descriptions. h3. [GA4 for Organic Traffic](#ga4-for-organic-traffic) Google Analytics 4 tracks user behavior and traffic sources. The advantage with Nuxt: SSR means initial pageviews work out of the box, but [**client-side navigation still needs tracking**](https://data-marketing-school.com/en/blog/google-analytics/track-single-page-applications/). h4. [Setup with Nuxt](#setup-with-nuxt) Add the GA4 script to your `app.head` in `nuxt.config.ts` with `send_page_view: false` to prevent double-tracking: ``` export default defineNuxtConfig({ app: { head: { script: [ { src: 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX', async: true }, { innerHTML: \` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-XXXXXXXXXX', { send_page_view: false }); \` } ] } } }) ``` Track route changes with a plugin: ``` // plugins/analytics.client.ts export default defineNuxtPlugin(() => { const router = useRouter() router.afterEach((to) => { gtag('config', 'GA_MEASUREMENT_ID', { page_path: to.fullPath }) }) }) ``` h4. [Track Organic Traffic](#track-organic-traffic) Go to **Reports > Acquisition > Traffic acquisition** in GA4. Filter by `sessionDefaultChannelGroup == Organic Search` to see your Google traffic. Link GA4 to Search Console for keyword data. Navigate to **Admin > Property Settings > Product links > Search Console links**. Data appears 48 hours after linking. h2. [Privacy-Focused Alternatives](#privacy-focused-alternatives) GA4 requires cookie banners in the EU and collects more data than most sites need. Three alternatives give you clean analytics without privacy headaches. h3. [Plausible](#plausible) [**Plausible**](https://plausible.io/) is lightweight (under 1KB script) and GDPR-compliant without cookies. No cookie banner needed. Add the script to your `nuxt.config.ts`: ``` export default defineNuxtConfig({ app: { head: { script: [ { 'defer': true, 'data-domain': 'yourdomain.com', 'src': 'https://plausible.io/js/script.js' } ] } } }) ``` Plausible automatically tracks [**single-page applications**](https://plausible.io/docs/hash-based-routing) using the History API. Works seamlessly with Nuxt's client-side navigation. Dashboard shows: - Real-time visitors - Top pages and sources - Referrers and countries - Bounce rate and time on page Pricing starts at $9/month for 10k monthly pageviews. Self-hosted version is free. h3. [Fathom](#fathom) [**Fathom**](https://usefathom.com/) focuses on simplicity. No cookies, GDPR-compliant, Canadian company with strong privacy stance. Install via `nuxt.config.ts`: ``` export default defineNuxtConfig({ app: { head: { script: [ { 'src': 'https://cdn.usefathom.com/script.js', 'data-site': 'ABCDEFG', 'defer': true } ] } } }) ``` Fathom tracks SPAs automatically. Dashboard is cleaner than GA4. Fewer metrics, faster insights. Starts at $15/month for 100k pageviews. No free tier, but 30-day free trial available. h3. [Umami](#umami) [**Umami**](https://umami.is/) is open-source and self-hosted. Free if you run it yourself, or $9/month for cloud hosting. Deploy on Vercel/Railway, add the tracking code: ``` export default defineNuxtConfig({ app: { head: { script: [ { 'defer': true, 'src': 'https://analytics.umami.is/script.js', 'data-website-id': 'your-id' } ] } } }) ``` SPA tracking works out of the box. [**Umami's Vue integration**](https://umami.is/docs/tracker-functions) lets you track custom events: ``` // Track button clicks umami.track('signup_button_clicked') ``` Best choice if you want full data ownership and don't mind managing infrastructure. h2. [Paid Tools (When You Need More)](#paid-tools-when-you-need-more) Free tools show _what's happening_. Paid tools show _why_ and _what to do about it_. h3. [When to Upgrade](#when-to-upgrade) Upgrade when you hit these signals: - Traffic plateaus after 6 months - Competitors outrank you for target keywords - You need backlink data to build authority - Manual SEO audits take too long [**Don't pay for tools on day one**](https://www.ibeamconsulting.com/blog/seo-tools-free-vs-paid-premium/). Start with Search Console and free analytics. Upgrade when revenue justifies it. A blog earning $500/month can justify $100/month tools. h3. [Ahrefs](#ahrefs) [**Ahrefs**](https://ahrefs.com/) excels at backlink analysis and keyword research. Use it to: - Find who links to competitors - Track keyword rankings daily - Discover content gaps - Run site audits for technical issues Rank Tracker shows click potential: how many actual clicks a keyword generates after SERP features take their share. More useful than search volume alone. [**Ahrefs doesn't provide daily ranking updates by default**](https://seranking.com/blog/ahrefs-vs-semrush/). Manual refreshes cost credits. Still worth it for backlink database, the largest in the industry. Starts at $129/month (Lite plan). Use the $7 7-day trial first. h3. [SEMrush](#semrush) [**SEMrush**](https://www.semrush.com/) is better for [**full campaign tracking**](https://trafficthinktank.com/semrush-vs-ahrefs/). It sends daily ranking updates via email and includes: - Position tracking for 500 keywords (Pro plan) - PPC competitor analysis - Local rank tracking with heatmaps - Content marketing toolkit SEMrush's Map Rank Tracker beats Ahrefs for local SEO. See how you rank across cities, not just countries. Better value than Ahrefs if you need PPC data or manage multiple locations. Starts at $139.95/month. h3. [Screaming Frog](#screaming-frog) [**Screaming Frog**](https://www.screamingfrogseoseo.com/) crawls your site like Googlebot. Find broken links, duplicate content, missing meta tags, and redirect chains. Free up to 500 URLs. Paid version ($259/year) crawls unlimited URLs and integrates with GA4 and Search Console. Run audits monthly. Fix issues before Google does. h2. [What to Track Weekly](#what-to-track-weekly) Monitor these metrics every Monday: **From Search Console:** - Total clicks (is traffic growing?) - Coverage errors (new indexing issues?) - Core Web Vitals (any pages failing?) **From GA4 or Plausible:** - Organic sessions (week-over-week change) - Top landing pages (which content performs?) - Bounce rate (are users finding what they need?) **From Ahrefs/SEMrush (if paid):** - Keyword rankings (any big drops?) - New backlinks (who linked to you?) - Competitor rankings (are they gaining ground?) Don't obsess over daily fluctuations. Rankings shift constantly. Weekly trends matter more. h2. [Setting Up Alerts](#setting-up-alerts) Alerts catch problems before you lose traffic. h3. [Search Console Alerts](#search-console-alerts) Search Console emails you automatically for: - Manual actions (penalties) - Security issues (hacked site) - Critical coverage errors Add team members in **Settings > Users and permissions** so they get alerts too. h3. [GA4 Custom Alerts](#ga4-custom-alerts) Create alerts for traffic drops. Go to **Admin > Custom insights** and set conditions: - If `Sessions from Organic Search` decreases by **30% week-over-week**, send email - If `Conversions` drops by **50%**, alert immediately Catch issues the day they happen, not two weeks later. h3. [Uptime Monitoring](#uptime-monitoring) If your site goes down, Search Console won't tell you. Use [**UptimeRobot**](https://uptimerobot.com/) (free) or [**Pingdom**](https://www.pingdom.com/) to check every 5 minutes. Get SMS alerts when your site breaks. --- [**Indexing Issues** Fix "crawled currently not indexed" and other GSC coverage errors affecting your Nuxt site.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/seo-monitoring/learn-seo/nuxt/launch-and-listen/indexing-issues) [**Site Migration** Migrate domains, redesign URLs, or switch frameworks without losing search rankings.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/seo-monitoring/learn-seo/nuxt/launch-and-listen/site-migration) **On this page** - [Start with Free Tools](#start-with-free-tools) - [Privacy-Focused Alternatives](#privacy-focused-alternatives) - [Paid Tools (When You Need More)](#paid-tools-when-you-need-more) - [What to Track Weekly](#what-to-track-weekly) - [Setting Up Alerts](#setting-up-alerts) --- ### Site Migration SEO for Nuxt Apps · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/site-migration Description: Migrate domains, redesign URLs, or switch frameworks without losing search rankings. h1. **Site Migration SEO for Nuxt Apps** Migrate domains, redesign URLs, or switch frameworks without losing search rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** **What you'll learn** - Map every old URL to exactly one new URL. 1:1 redirects via `routeRules` pass PageRank properly - Keep redirects for at least 1 year; Google recommends never removing redirects that still get traffic - Update canonical tags alongside redirects. Canonicals pointing to old URLs create loops Poor site migrations destroy search rankings. [**Well-executed migrations recover 90-95% of traffic within 30 days**](https://www.matthewedgar.net/how-long-keep-redirects/). Poor migrations can take 6-12 months to recover or never reach previous levels. The difference is planning, proper redirects, and post-migration monitoring. h2. [Types of Migrations](#types-of-migrations) Each migration type affects SEO differently: ![Migration Decision Tree](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/site-migration/images/learn-seo/vue/migration-decision-tree.svg) **Domain change** (old.com → new.com): Requires [**Change of Address tool in Google Search Console**](https://support.google.com/webmasters/answer/9370220) and strict 1:1 URL mapping. Keep redirects for [**at least one year**](https://www.searchenginejournal.com/google-keep-301-redirects-in-place-for-a-year/428998/). **Protocol change** (HTTP → HTTPS): Update all canonical tags to HTTPS versions. Forgetting this creates redirect loops. Redirects send Google to HTTPS, but canonicals point back to HTTP. **URL structure change** (/blog/post → /posts/post): Most prone to redirect chains. Map every old URL to exactly one new URL. No intermediates. **Platform/framework change**: Moving from WordPress to Nuxt, or Vue to Nuxt. URL structure usually changes. Full redirect mapping required. **Site redesign with same URLs**: Lowest risk if URLs stay identical. Still validate canonical tags and internal links. h2. [Pre-Migration Checklist](#pre-migration-checklist) Start here before touching production: 1. **Crawl old site completely**: Use Screaming Frog or similar to export all URLs. You need a full inventory. 2. **Export indexed URLs from Search Console**: Go to Coverage report → Valid → Export. These are URLs Google knows about. 3. **Document current rankings**: Screenshot Search Console Performance for top pages. You'll compare against this. 4. **Create redirect mapping spreadsheet**: Three columns: Old URL | New URL | Status (200, 301, 410). Map every single URL. No exceptions. For large sites (10,000+ pages), consider [**migrating in sections**](https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes). Test with a small section first, verify traffic holds, then migrate the rest. h2. [Redirect Mapping Strategy](#redirect-mapping-strategy) Mapping is where migrations succeed or fail. **1:1 mapping** (preferred): Each old URL redirects to exactly one new URL with identical or similar content. ``` /blog/vue-seo-guide → /guides/vue-seo /products/item-123 → /products/item-123 (unchanged) ``` **Pattern-based redirects**: For systematic URL changes. Use regex or route patterns to redirect entire sections. ``` /blog/:slug → /articles/:slug /category/:cat/page/:num → /c/:cat?page=:num ``` **Deleted pages**: Don't 404 pages that had traffic or backlinks. Redirect to the most relevant alternative. If truly no alternative exists, return 410 (Gone) instead of 404. This signals permanent removal. h2. [Implementing Redirects in Nuxt](#implementing-redirects-in-nuxt) Nuxt handles redirects through `routeRules` in `nuxt.config.ts`. These are server-side redirects that properly pass PageRank. h3. [Single Redirects](#single-redirects) ``` // nuxt.config.ts export default defineNuxtConfig({ routeRules: { '/old-url': { redirect: '/new-url' }, '/company/about': { redirect: '/about' } } }) ``` h3. [Pattern-Based Redirects](#pattern-based-redirects) ``` // nuxt.config.ts export default defineNuxtConfig({ routeRules: { '/blog/**': { redirect: '/articles/**' }, '/old-section/**': { redirect: '/new-section/**' } } }) ``` h3. [Bulk Redirects with Server Middleware](#bulk-redirects-with-server-middleware) For large redirect maps (1000+ URLs), use server middleware: ``` // server/middleware/redirects.ts const redirects: Record<string, string> = { '/old-page-1': '/new-page-1', '/old-page-2': '/new-page-2', '/products/legacy-item': '/products/new-item' } export default defineEventHandler((event) => { const path = event.path const redirect = redirects[path] if (redirect) { return sendRedirect(event, redirect, 301) } }) ``` For even larger redirect maps, load from JSON: ``` // server/middleware/redirects.ts import redirectData from '~/redirects.json' export default defineEventHandler((event) => { const redirect = redirectData[event.path] if (redirect) { return sendRedirect(event, redirect, 301) } }) ``` h3. [Dynamic Pattern Redirects](#dynamic-pattern-redirects) ``` // server/middleware/redirects.ts export default defineEventHandler((event) => { const path = event.path // Redirect old blog structure to new if (path.startsWith('/blog/')) { const slug = path.replace('/blog/', '') return sendRedirect(event, \`/articles/${slug}\`, 301) } // Redirect category pages with pagination const categoryMatch = path.match(/^\/category\/([^/]+)\/page\/(\d+)$/) if (categoryMatch) { const [, category, page] = categoryMatch return sendRedirect(event, \`/c/${category}?page=${page}\`, 301) } }) ``` h2. [Avoid Redirect Chains](#avoid-redirect-chains) [**Redirect chains**](https://netpeaksoftware.com/blog/top-10-site-migration-mistakes) happen when URL A redirects to B, which redirects to C. This dilutes PageRank and slows crawling. Common scenario: You have old redirects (A → B) and add new migration redirects (B → C). Result: chain A → B → C. Fix: Update all redirects to point directly to final destination. Consolidate old and new redirects into single-hop redirects. ``` // Bad: creates chain // Old redirect: /page-v1 → /page-v2 // New redirect: /page-v2 → /page-v3 // Result: /page-v1 → /page-v2 → /page-v3 // Good: consolidate in routeRules export default defineNuxtConfig({ routeRules: { '/page-v1': { redirect: '/page-v3' }, // direct to final '/page-v2': { redirect: '/page-v3' } } }) ``` Test redirects before launch: Each should return 301 status and resolve in one hop to 200 (OK). h2. [Update Canonical Tags](#update-canonical-tags) When URLs change, canonical tags must point to new URLs. [**Canonical pointing to redirect**](https://sitechecker.pro/site-audit-issues/canonical-points-redirect/) is a common migration mistake. After migration, canonical tags should reference new URL structure: ``` <!-- Bad: canonical points to old URL --> <link rel="canonical" href="https://example.com/old-url"> <!-- Good: canonical points to new URL --> <link rel="canonical" href="https://example.com/new-url"> ``` In Nuxt with `useHead`: ``` <script setup> useHead({ link: [ { rel: 'canonical', href: 'https://example.com/new-url' } ] }) </script> ``` Or with `useSeoMeta`: ``` <script setup> useSeoMeta({ canonicalUrl: 'https://example.com/new-url' }) </script> ``` Scan staging site before launch to verify all canonicals point to new URLs, not old ones. h2. [Post-Migration Steps](#post-migration-steps) The hour after migration is critical. 1. **Update Search Console property**: For domain changes, use [**Change of Address tool**](https://support.google.com/webmasters/answer/9370220). This tells Google you've moved. 2. **Submit new sitemap**: Generate sitemap with new URLs. Submit in Search Console. Remove old sitemap from robots.txt. 3. **Request indexing of key pages**: In Search Console, request indexing for homepage and top 10-20 pages. Speeds up discovery. 4. **Monitor coverage report**: Check daily for errors. Look for 404s, soft 404s, redirect errors, or pages not followed. 5. **Watch server capacity**: [**Google crawls more after migrations**](https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes). Redirects from old URLs trigger crawls of new URLs. Monitor server load. 6. **Check Analytics segmentation**: Set up date comparison in Google Analytics. Before migration vs after. Track organic traffic specifically. h2. [Recovery Timeline](#recovery-timeline) ![Migration Recovery Timeline](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/site-migration/images/learn-seo/vue/migration-recovery-gantt.svg) Expect these phases: **Week 1-2**: Traffic dip of [**10-25% is normal**](https://www.numentechnology.co.uk/blog/website-migration-seo-strategy). Google discovers redirects and starts reindexing. **Week 3-4**: [**Small sites recover**](https://socialander.com/how-long-to-do-website-migration/) 90%+ of traffic if migration was clean. Large sites still reindexing. **Month 2-3**: Most sites reach full recovery. Rankings stabilize. [**Typical range is 30-60 days**](https://www.localseoguide.com/how-long-does-it-take-to-recover-from-a-domain-migration/). **Month 4-6**: Large sites (100,000+ pages) continue recovery. [**Average across 892 migrations was 523 days**](https://www.rapidfireweb.com/post/domain-migration-its-impact-on-organic-traffic), but median is much lower. **Keep redirects for 1+ year**: [**Google recommends at least 12 months**](https://www.searchenginejournal.com/google-keep-301-redirects-in-place-for-a-year/428998/). Longer if you still see traffic to old URLs. Never remove redirects that still get hits. If traffic hasn't recovered after 3 months, audit for: - Missing redirects (404s in coverage report) - Redirect chains - Canonical tags still pointing to old URLs - Internal links still pointing to old URLs h2. [Common Migration Mistakes](#common-migration-mistakes) **Redirect chains**: As mentioned, these dilute authority. [**Consolidate old and new redirects**](https://www.tomcreweseo.co.uk/blog/6-mistakes-ive-seen-cause-a-website-migration-to-go-wrong/) into single-hop redirects. **Forgetting internal links**: Your site's internal links still point to old URLs, triggering unnecessary redirects. Update internal links to point directly to new URLs. **Not updating canonical URLs**: Canonical tags create loops when they point to redirected URLs. [**Update canonicals alongside redirects**](https://proranktracker.com/blog/seo-common-mistakes-canonical-tag-and-301-redirect/). **Removing redirects too early**: Traffic sources outside your control (old backlinks, bookmarks, third-party sites) use old URLs indefinitely. Keep redirects for years, not months. **Client-side redirects**: JavaScript redirects (`window.location`) don't pass PageRank. Google may not follow them. Server-side 301s are required. **No redirect testing**: Test redirects on staging before launch. Verify each returns 301 and resolves to 200 in one hop. **Forgetting mobile/AMP URLs**: If you had separate mobile URLs (m.example.com) or AMP versions, redirect those too. --- [**SEO Monitoring** Set up analytics, rank tracking, and alerts to monitor your Nuxt site's search performance.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/site-migration/learn-seo/nuxt/launch-and-listen/seo-monitoring) [**IndexNow** Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/site-migration/learn-seo/nuxt/launch-and-listen/indexnow) **On this page** - [Types of Migrations](#types-of-migrations) - [Pre-Migration Checklist](#pre-migration-checklist) - [Redirect Mapping Strategy](#redirect-mapping-strategy) - [Implementing Redirects in Nuxt](#implementing-redirects-in-nuxt) - [Avoid Redirect Chains](#avoid-redirect-chains) - [Update Canonical Tags](#update-canonical-tags) - [Post-Migration Steps](#post-migration-steps) - [Recovery Timeline](#recovery-timeline) - [Common Migration Mistakes](#common-migration-mistakes) --- ### Debug Nuxt SEO Issues · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/debugging Description: Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide. h1. **Debug Nuxt SEO Issues** Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** **What you'll learn** - Use View Source (not DevTools) to see what Google sees. If meta tags are missing there, SSR isn't working - `useFetch()` and `useAsyncData()` work during SSR; `onMounted()` fetches are client-only - URL Inspection in Search Console shows exactly how Google renders your page including JavaScript If you can't see meta tags in View Source, neither can Google. Most Nuxt SEO bugs come from hydration mismatches, configuration mistakes, or disabling SSR where you shouldn't. h2. [View Source vs Inspect Element](#view-source-vs-inspect-element) Two different ways to view HTML show different content: **View Source** (Right-click → View Page Source) shows the initial HTML your server sends. This is what Google's crawler sees first. **Inspect Element** (F12 → Elements tab) shows the live DOM after JavaScript executes. Meta tags added by JavaScript appear here but might not be indexed. Test: Open your site, right-click, select "View Page Source." Search for `<title>` or `<meta name="description"`. If you can't find your content in View Source, Google can't either. ``` <!-- ❌ BAD: Empty in View Source (SPA mode) --> <!DOCTYPE html> <html> <head> <title>Loading...
How to Debug Nuxt SEO | MySite

How to Debug Nuxt SEO

Fix meta tags not rendering...

``` Google [**renders JavaScript**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) but it's a two-wave process: crawl the HTML first, then render JavaScript days later. If critical content only appears after JavaScript executes, indexing gets delayed by weeks. h2. [Meta Tags Not Updating on Navigation](#meta-tags-not-updating-on-navigation) Single Page Applications change routes without full page reloads. Meta tags set during initial render might not update when users navigate. **Common mistake:** Using `document.title` instead of Nuxt's composables. ``` ``` Fix: Use `useHead()` or `useSeoMeta()`. Nuxt tracks navigation and updates meta tags correctly. ``` ``` Nuxt's Unhead integration synchronizes meta tags across SSR and client-side navigation. The [**Unhead documentation**](https://unhead.unjs.io/usage/composables/use-head) explains reactivity patterns. h3. [Debugging Navigation Updates with Nuxt DevTools](#debugging-navigation-updates-with-nuxt-devtools) Nuxt DevTools provides a dedicated view for monitoring meta tags: 1. Open app in browser 2. Click Nuxt DevTools icon (bottom right) 3. Select "Head" tab 4. Navigate between pages 5. Watch meta tags update in real-time If meta tags don't change, you're not using reactive values correctly or have SSR disabled. h2. [Hydration Mismatches Breaking Meta Tags](#hydration-mismatches-breaking-meta-tags) Hydration is when Vue takes server-rendered HTML and "activates" it with reactivity and event listeners. A hydration mismatch occurs when the HTML rendered on the client differs from the server-rendered HTML. Nuxt displays warnings like "Hydration completed but contains mismatches" or "Text content does not match server-rendered HTML." These warnings appear in browser console and indicate server/client HTML differences ([**Vue SSR Guide**](https://vuejs.org/guide/scaling-up/ssr.html)). h3. [Common Causes](#common-causes) **Invalid HTML nesting** triggers browser auto-correction, causing mismatches: ``` ``` **Browser-only APIs** don't exist during SSR: ``` ``` **Random values and timestamps** differ between server and client: ``` ``` **Third-party libraries** without SSR support cause mismatches ([**How to Fix Vue Hydration Mismatch**](https://dev.to/jacobandrewsky/how-to-fix-vue-hydration-mismatch-47eg)): ``` ``` h3. [Suppressing Mismatches in Vue 3.5+](#suppressing-mismatches-in-vue-35) For inevitable mismatches (like timestamps), use `data-allow-mismatch`: ``` ``` This tells Vue to expect differences and skip warnings. h3. [External Factors](#external-factors) **HTML minification** causes hydration mismatches. Most HTML minifiers must be disabled ([**Harlan Wilton - Nuxt 3 Hydration Mismatch**](https://harlanzw.com/blog/nuxt3-hydration-node-mismatch)). Cloudflare users: Disable Cloudflare's automatic HTML minifier in dashboard → Speed → Optimization → Auto Minify. **Browser cache** loads outdated JavaScript. Clear cache when debugging mismatches. h2. [Nuxt Configuration Mistakes](#nuxt-configuration-mistakes) Most Nuxt SEO bugs come from configuration errors or disabling features that shouldn't be disabled. h3. [Disabling SSR Globally](#disabling-ssr-globally) Setting `ssr: false` in `nuxt.config.ts` breaks SEO for the entire site: ``` // ❌ Disables SSR globally (bad for SEO) export default defineNuxtConfig({ ssr: false }) ``` Fix: Remove global SSR disable or use route-level rules: ``` // ✅ Disable SSR only where needed export default defineNuxtConfig({ routeRules: { '/dashboard/**': { ssr: false }, // Client-only for authenticated routes '/blog/**': { prerender: true } // SSG for blog pages } }) ``` h3. [Reactive Values Not Updating](#reactive-values-not-updating) Passing `.value` instead of the ref breaks reactivity: ``` ``` For computed values, use getter functions: ``` ``` h3. [Duplicate Meta Tags](#duplicate-meta-tags) Multiple `useHead()` calls with same meta tags create duplicates: ``` ``` Unhead deduplicates by default but multiple calls can override unexpectedly. Consolidate meta tags into single `useHead()` or `useSeoMeta()` call per component. h3. [Missing Data During SSR](#missing-data-during-ssr) Data fetched in `onMounted()` won't be available during SSR: ``` ``` Nuxt's `useFetch()` and `useAsyncData()` work during both SSR and client-side navigation. h2. [Chrome DevTools SEO Workflow](#chrome-devtools-seo-workflow) DevTools helps debug meta tags, rendering issues, and JavaScript errors. h3. [1. Check Initial HTML Response](#_1-check-initial-html-response) Network panel shows server-sent HTML before JavaScript executes: 1. Open DevTools (F12) 2. Network tab → Clear (trash icon) 3. Reload page 4. Click first request (usually document) 5. Preview or Response tab shows initial HTML Look for meta tags in this response. If missing, your SSR isn't working. h3. [2. Search for Meta Tags](#_2-search-for-meta-tags) Elements panel lets you find all meta tags quickly: 1. Elements tab 2. Ctrl+F (Cmd+F on Mac) to search 3. Search for `` element - Document has meta description - Links have descriptive text - `` has `lang` attribute Lighthouse doesn't verify correctness. Just presence. A title "undefined" passes but isn't useful. h2. [Google Search Console URL Inspection](#google-search-console-url-inspection) URL Inspection tool shows exactly what Google sees when crawling your page. h3. [How to Use URL Inspection](#how-to-use-url-inspection) 1. Open [**Google Search Console**](https://search.google.com/search-console) 2. Enter URL in search bar at top 3. Click "Test Live URL" for current version 4. View results Tool shows ([**Google URL Inspection documentation**](https://support.google.com/webmasters/answer/9012289)): - Whether page is indexed - How it was crawled - What resources were blocked - Structured data Google found - Rendered page screenshot h3. [Index Status](#index-status) "URL is on Google" means the URL is eligible to appear in search results (not guaranteed). "URL is not on Google" means the URL can't appear. Check "Page indexing" section for why. h3. [View Crawled Page](#view-crawled-page) See how Google renders your page: 1. Click "View tested page" 2. Screenshot tab shows visual render 3. More info → HTML shows rendered HTML 4. More info → Console log shows JavaScript errors Compare screenshot to your actual page. Missing content indicates rendering problems. h3. [JavaScript Rendering Test](#javascript-rendering-test) Google renders JavaScript but [**processes happen in two waves**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics): 1. **First wave:** Crawl initial HTML 2. **Second wave:** Render JavaScript (days later) If content only appears after JavaScript, it gets delayed. URL Inspection shows both: - "Live test" → Current rendered version - "Indexed version" → What's in Google's index h3. [Request Indexing](#request-indexing) After fixing issues: 1. URL Inspection → "Request Indexing" 2. Google queues URL for recrawl 3. Re-indexing takes 1-4 weeks [**Google doesn't guarantee indexing**](https://support.google.com/webmasters/answer/7440203). It still evaluates content quality. Daily quota limits requests. For bulk operations, use [**Google Indexing API**](https://developers.google.com/search/apis/indexing-api/v3/quickstart) (up to 2,000 URLs/day). h2. [Common Nuxt SEO Debugging Scenarios](#common-nuxt-seo-debugging-scenarios) h3. [Meta Tags Appear in DevTools But Not View Source](#meta-tags-appear-in-devtools-but-not-view-source) **Cause:** SSR disabled globally or for specific routes. **Test:** ``` curl -s https://yoursite.com | grep "" ``` If curl shows empty title, check `nuxt.config.ts` for `ssr: false` or route rules disabling SSR. **Fix:** Re-enable SSR: ``` // nuxt.config.ts export default defineNuxtConfig({ // Remove ssr: false if present // Or use route-level control routeRules: { '/dashboard/**': { ssr: false }, // Only disable for client-only sections } }) ``` h3. [Meta Tags Don't Update on Navigation](#meta-tags-dont-update-on-navigation) **Cause:** Not using reactive values or using `document.title` directly. **Test:** Navigate between pages, watch `<title>` in Nuxt DevTools Head tab. **Fix:** ``` <script setup> const route = useRoute() useHead({ title: () => route.meta.title as string }) </script> ``` h3. [Google Shows Wrong Title/Description](#google-shows-wrong-titledescription) **Cause:** [**Google rewrites 60-70% of meta descriptions**](https://dev.to/hmzas/vuejs-seo-in-2025-why-you-still-need-server-side-rendering-ssr-12b9) and titles it deems low-quality. **Test:** Search for `site:yoursite.com` in Google. Compare displayed titles to your actual meta tags. **Fix:** Write better titles and descriptions: - Titles: 50-60 characters, unique per page, include target keyword - Descriptions: 150-160 characters, compelling copy, match search intent - Avoid duplicates across pages - Make them relevant to page content Google still might rewrite. That's normal. If it rewrites everything, your meta tags are probably too generic or off-topic. h3. [Hydration Mismatch on Meta Tags](#hydration-mismatch-on-meta-tags) **Cause:** Server and client render different meta tag values (timestamps, random IDs, browser APIs). **Test:** Look for console warnings: "Hydration completed but contains mismatches." **Fix:** Use `ClientOnly` wrapper or `data-allow-mismatch`: ``` <script setup> const timestamp = ref('') onMounted(() => { timestamp.value = new Date().toISOString() }) useHead({ meta: [ { property: 'og:updated_time', content: timestamp } ] }) </script> ``` h3. [Content Loads After SSR](#content-loads-after-ssr) **Cause:** Data fetched in `onMounted()` instead of during SSR. **Test:** View Source shows "Loading..." instead of actual content. **Fix:** Use `useFetch()` or `useAsyncData()`: ``` <!-- ❌ Client-only fetch --> <script setup> const post = ref({ title: 'Loading...' }) onMounted(async () => { post.value = await fetch('/api/post').then(r => r.json()) }) useHead({ title: () => post.value.title }) </script> <!-- ✅ SSR-compatible fetch --> <script setup> const { data: post } = await useFetch('/api/post') useHead({ title: () => post.value?.title || 'Loading...' }) </script> ``` h2. [Testing Before Deployment](#testing-before-deployment) Don't wait for Google to tell you something's broken. Test locally: h3. [1. View Source Test](#_1-view-source-test) Right-click → View Page Source. Search for critical meta tags. If they're missing, fix SSR before deploying. h3. [2. Curl Test](#_2-curl-test) ``` h1. Check initial HTML curl -s https://yoursite.com | grep -A 5 "<head>" h1. Check specific meta tag curl -s https://yoursite.com | grep 'name="description"' ``` Should show full meta tags in output. h3. [3. Search Console Test Environment](#_3-search-console-test-environment) Set up staging site in Search Console. Test URL Inspection on staging before pushing to production. h3. [4. Lighthouse CI](#_4-lighthouse-ci) Automate SEO checks in CI/CD: ``` npm install -g @lhci/cli h1. Run Lighthouse lhci autorun --collect.url=http://localhost:3000 ``` Configure assertion for minimum SEO score. h3. [5. Nuxt DevTools in Production](#_5-nuxt-devtools-in-production) Enable DevTools in production for debugging (temporarily): ``` // nuxt.config.ts export default defineNuxtConfig({ devtools: { enabled: true // Normally auto-disabled in production } }) ``` Remember to disable after debugging. DevTools add overhead. --- [**IndexNow** Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/debugging/learn-seo/nuxt/launch-and-listen/indexnow) [**AI Search Optimization** Make your Nuxt site visible in AI Overviews, ChatGPT, and other generative search engines with structured data and content strategies.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/debugging/learn-seo/nuxt/launch-and-listen/ai-optimized-content) **On this page** - [View Source vs Inspect Element](#view-source-vs-inspect-element) - [Meta Tags Not Updating on Navigation](#meta-tags-not-updating-on-navigation) - [Hydration Mismatches Breaking Meta Tags](#hydration-mismatches-breaking-meta-tags) - [Nuxt Configuration Mistakes](#nuxt-configuration-mistakes) - [Chrome DevTools SEO Workflow](#chrome-devtools-seo-workflow) - [Google Search Console URL Inspection](#google-search-console-url-inspection) - [Common Nuxt SEO Debugging Scenarios](#common-nuxt-seo-debugging-scenarios) - [Testing Before Deployment](#testing-before-deployment) --- ### IndexNow and Indexing APIs in Nuxt · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexnow Description: Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing. h1. **IndexNow and Indexing APIs in Nuxt** Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** Search engines discover content changes through regular crawling, which can take days or weeks. Indexing APIs let you notify search engines immediately when you publish, update, or delete content. h2. [IndexNow Protocol](#indexnow-protocol) IndexNow is an [**open-source protocol**](https://www.indexnow.org/) that notifies search engines instantly when URLs change. Instead of waiting for crawlers to discover updates, you push notifications directly to participating engines. Supported search engines: - **Microsoft Bing** - Primary supporter and developer - **Yandex** - Russian search engine - **Seznam.cz** - Czech search engine - **Naver** - Korean search engine - **Yep** - Privacy-focused search engine **Important:** Google does not support IndexNow. Use Google Search Console's manual "Request Indexing" feature or third-party tools (covered below). h3. [When to Use IndexNow](#when-to-use-indexnow) Use IndexNow when: - Publishing new content (blog posts, product pages) - Updating existing pages (price changes, corrections) - Deleting pages (discontinued products, removed content) - Fixing critical errors on important pages - Running time-sensitive content (news, events, flash sales) Don't use IndexNow for: - Minor text edits or typo fixes - Styling or layout changes - Pages already noindexed via robots meta tag - Private or staging content h3. [Setting Up IndexNow](#setting-up-indexnow) Generate an API key (8-128 alphanumeric characters): ``` openssl rand -hex 32 ``` Create a key file in your `public` directory: public/abc123def456.txt ``` abc123def456 ``` The key must be accessible at `https://yoursite.com/{key}.txt` for verification. h3. [Submitting URLs](#submitting-urls) Submit URLs after content changes. One notification reaches all IndexNow engines. server/api/indexnow.post.ts ``` export default defineEventHandler(async (event) => { const { urls } = await readBody(event) await $fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: { host: 'yoursite.com', key: useRuntimeConfig().indexnowKey, keyLocation: \`https://yoursite.com/${useRuntimeConfig().indexnowKey}.txt\`, urlList: urls } }) return { success: true, count: urls.length } }) ``` Call from your content management workflows: pages/admin/publish.vue ``` <script setup> async function publishPost(post) { // Save content await $fetch('/api/posts', { method: 'POST', body: post }) // Notify search engines await $fetch('/api/indexnow', { method: 'POST', body: { urls: [\`https://yoursite.com/blog/${post.slug}\`] } }) } </script> ``` Submit up to 10,000 URLs per request. For single URLs, use GET: ``` https://api.indexnow.org/IndexNow?url=https://yoursite.com/page&key=abc123def456 ``` h3. [Automated Integration](#automated-integration) Nuxt SEO provides zero-config IndexNow support: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexnow/docs/nuxt-seo/getting-started/introduction) With the module installed, IndexNow triggers automatically when you: - Prerender pages with `nuxt generate` - Update content in supported CMS integrations - Call the `useIndexNow()` composable manually See the [**IndexNow documentation**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexnow/docs/nuxt-seo/integrations/indexnow) for configuration options. h2. [Google Indexing API](#google-indexing-api) [**Google Indexing API**](https://developers.google.com/search/apis/indexing-api/v3/quickstart) works differently than IndexNow. It's limited to **JobPosting** and **BroadcastEvent** schema types only. Don't use Google Indexing API for: - Blog posts - Product pages - General website content - News articles - Landing pages Only use it if your site has: - Job listings with JobPosting schema - Livestream videos with BroadcastEvent schema Using the API for other content types violates Google's terms and gets ignored. [**Google confirmed**](https://www.seroundtable.com/google-indexing-api-other-content-types-32957.html) it won't help or hurt. It's simply ignored. h3. [2025 API Changes](#_2025-api-changes) Google now requires approval for Indexing API access. The [**default quota is 200 requests**](https://dstribute.io/job-boards/google-jobs-shake-up-2025-navigating-the-new-indexing-api-restrictions/) for testing. Production use requires partner authorization. Smaller job boards and recruitment sites lost direct access. If you run a job board, follow the [**official quickstart**](https://developers.google.com/search/apis/indexing-api/v3/quickstart) and request quota increases through Google Search Console. h2. [RequestIndexing Tool](#requestindexing-tool) [**RequestIndexing**](https://requestindexing.com/) by [**@harlan_zw**](https://x.com/harlan_zw) automates bulk URL submission to Google Search Console. It uses your Search Console credentials to programmatically trigger the manual "Request Indexing" button for multiple URLs. This isn't an official API. It automates the manual workflow. Google allows requesting indexing for individual URLs; RequestIndexing does this in bulk. Use cases: - Bulk indexing after site migration - Re-indexing updated content - Submitting new pages when sitemaps aren't crawled quickly - Emergency indexing for time-sensitive content Limitations: - Requires Google Search Console access - Subject to Google's rate limits - Not instant (still requires Googlebot crawling) - Manual process, not integrated into your build h2. [Choosing the Right Method](#choosing-the-right-method) Pick based on search engine priority and content type: | **Method** | **Best For** | **Limitations** | | --- | --- | --- | | **IndexNow** | Bing/Yandex users, instant updates | Doesn't work for Google | | **Google Indexing API** | Job boards, livestreams | JobPosting/BroadcastEvent only | | **RequestIndexing** | Bulk Google submissions | Manual process, rate limited | | **Search Console Manual** | One-off important pages | Slow, not scalable | | **XML Sitemap** | Routine crawling | Passive, no urgency signal | **Recommendation:** Implement IndexNow for all content changes. Your Bing and Yandex traffic gets instant updates. For Google, use XML sitemaps for routine discovery and RequestIndexing for bulk submissions after major updates. Don't waste time on Google Indexing API unless you're a job board or livestream platform. h2. [Implementation Checklist](#implementation-checklist) 1. Generate IndexNow API key 2. Add key file to `/public/{key}.txt` 3. Create server endpoint to call IndexNow API 4. Trigger notifications in publish/update workflows 5. Submit sitemap to Google Search Console 6. Use RequestIndexing for bulk Google submissions 7. Monitor Search Console for indexing status For automated setup, install Nuxt SEO and configure IndexNow in your `nuxt.config.ts`. --- [**Site Migration** Migrate domains, redesign URLs, or switch frameworks without losing search rankings.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexnow/learn-seo/nuxt/launch-and-listen/site-migration) [**Debugging** Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/indexnow/learn-seo/nuxt/launch-and-listen/debugging) **On this page** - [IndexNow Protocol](#indexnow-protocol) - [Google Indexing API](#google-indexing-api) - [RequestIndexing Tool](#requestindexing-tool) - [Choosing the Right Method](#choosing-the-right-method) - [Implementation Checklist](#implementation-checklist) --- ### Optimizing Nuxt Content for AI Search · Nuxt SEO Source: https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content Description: Make your Nuxt site visible in AI Overviews, ChatGPT, and other generative search engines with structured data and content strategies. h1. **Optimizing Nuxt Content for AI Search** Make your Nuxt site visible in AI Overviews, ChatGPT, and other generative search engines with structured data and content strategies. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** Updated **Jan 4, 2026** **What you'll learn** - 80% of AI Overview citations come from pages already ranking top 3. GEO extends SEO, doesn't replace it - Pages with Schema.org structured data are 40% more likely to be cited by AI engines - Lead with direct answers, use question-format headings, include code examples and sourced statistics AI search engines (ChatGPT, Google AI Overviews, Perplexity, Gemini) synthesize answers instead of listing links. Getting cited means appearing in their responses, not ranking on page one. Generative Engine Optimization (GEO) is the practice of making your content citable by AI. [**Princeton researchers coined the term**](https://arxiv.org/abs/2311.09735) in November 2023, and by 2025 it's standard practice alongside traditional SEO. Nuxt gives you a head start: SSR by default, automatic schema.org with [**nuxt-schema-org**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/docs/schema-org/getting-started/introduction), and llms.txt support via [**nuxt-llms**](https://github.com/harlan-zw/nuxt-llms). h2. [Why GEO Matters](#why-geo-matters) Traditional search shows 10 blue links. AI search shows one synthesized answer citing 2-7 sources ([**Backlinko research**](https://backlinko.com/generative-engine-optimization-geo)). If you're not in those citations, you're invisible. The numbers are compelling: - Over 1 billion prompts sent to ChatGPT daily - [**71% of Americans**](https://www.tryprofound.com/guides/generative-engine-optimization-geo-guide-2025) use AI search for purchase research - [**Semrush predicts**](https://www.semrush.com/blog/google-ai-overview/) LLM traffic will overtake traditional Google search by 2027 h2. [GEO vs SEO](#geo-vs-seo) GEO doesn't replace SEO. It extends it. AI engines like Google's Gemini (powering AI Overviews) still use ranking signals. [**80% of AI Overview citations**](https://www.theedigital.com/blog/how-to-rank-in-ai-overviews) come from pages already ranking in the top 3. | **Traditional SEO** | **Generative Engine Optimization** | | --- | --- | | Optimize for 10 blue links | Optimize for 2-7 citations | | Backlinks build authority | Third-party mentions build authority | | SERP snippets drive clicks | AI summaries drive (fewer) clicks | | Keyword targeting | Semantic topic coverage | | CTR matters | Citation rate matters | **Start with SEO fundamentals.** If your Nuxt site isn't crawlable and indexable, AI engines can't cite it either. h2. [What AI Engines Actually Cite](#what-ai-engines-actually-cite) Each AI platform favors different sources: | **Platform** | **Top Citation Sources** | | --- | --- | | ChatGPT | Wikipedia (47.9%), news sites, academic sources | | Gemini | Reddit, YouTube, official docs | | Perplexity | Reddit, community discussions, recent articles | | Google AI Overviews | Top-ranking organic results, structured data | [**Wikipedia dominates ChatGPT**](https://backlinko.com/generative-engine-optimization-geo) because it provides comprehensive, neutral, well-structured information. Reddit dominates Perplexity because it surfaces real user experiences. **Implications for Nuxt developers:** Create content that reads like documentation, not marketing. Include real code examples, acknowledge limitations, and cite your sources. h2. [Structured Data for AI](#structured-data-for-ai) Schema.org markup helps AI understand your content. [**Google confirmed at Search Central Live Madrid**](https://www.brightedge.com/blog/structured-data-ai-search-era) that Gemini (powering AI Overviews) uses structured data to understand content context. Pages with structured data are [**40% more likely**](https://greenbananaseo.com/structured-data-ai-ranking/) to appear in AI citations. h3. [Using nuxt-schema-org](#using-nuxt-schema-org) Nuxt Schema.org provides automatic and manual schema generation: [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/docs/schema-org/getting-started/introduction) ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['nuxt-schema-org'], schemaOrg: { identity: { type: 'Organization', name: 'My Company', url: 'https://example.com' } } }) ``` For articles and guides, add page-level schema: ``` <script setup lang="ts"> // No imports needed, auto-imported in Nuxt useSchemaOrg([ defineWebPage({ '@type': 'WebPage', 'name': 'How to Add Meta Tags in Nuxt' }), defineArticle({ headline: 'How to Add Meta Tags in Nuxt', description: 'Complete guide to managing meta tags in Nuxt with useSeoMeta.', datePublished: '2025-01-15', author: { '@type': 'Person', 'name': 'Your Name' } }) ]) </script> ``` h3. [FAQ Schema](#faq-schema) FAQ schema works well for question-based queries that trigger AI responses: ``` <script setup lang="ts"> useSchemaOrg([ defineFAQPage({ mainEntity: [ { '@type': 'Question', 'name': 'How do I add meta tags in Nuxt?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Use useSeoMeta() or useHead() in any component. Both are auto-imported.' } } ] }) ]) </script> ``` Note: Google [**deprecated FAQ rich results**](https://developers.google.com/search/blog/2023/08/howto-faq-changes) for most sites in August 2023, but the structured data still helps AI engines understand Q&A content. h2. [Content Structure for AI Extraction](#content-structure-for-ai-extraction) AI engines parse content differently than humans browse it. Structure your Nuxt documentation and articles for extraction: h3. [Lead with Summaries](#lead-with-summaries) Put the answer first. AI engines extract from the opening paragraph: ``` Bad: "In today's web development landscape, meta tags play a crucial role..." Good: "Use \`useSeoMeta()\` to set meta tags in Nuxt. It's auto-imported, handles SSR, and supports reactivity." ``` h3. [Use Clear Headings](#use-clear-headings) AI engines use H2s and H3s to understand content hierarchy: ``` h2. How to Set Meta Tags in Nuxt ← Clear question format h3. Using useSeoMeta() ← Specific method h3. Dynamic Meta Tags per Route ← Common use case h3. Troubleshooting SSR Issues ← Problem-solving ``` h3. [Include Code Examples](#include-code-examples) Technical AI queries expect code. ChatGPT and Perplexity cite pages with working examples: ``` <script setup lang="ts"> // This gets extracted by AI engines useSeoMeta({ title: 'Page Title', description: 'Page description for search engines' }) </script> ``` h3. [Add Quotable Statistics](#add-quotable-statistics) AI engines love citable facts. Include specific numbers with sources: ``` Bad: "Most sites have SEO issues." Good: "67.6% of websites have duplicate content issues (Semrush, 2024)." ``` h2. [Building External Citations](#building-external-citations) AI engines weight third-party mentions heavily. Wikipedia dominates ChatGPT because thousands of external sources cite it. h3. [Get Mentioned on Authoritative Sites](#get-mentioned-on-authoritative-sites) Target sites AI engines trust: - **Wikipedia**: Add citations to relevant articles (follow their guidelines) - **Reddit**: Answer questions in r/nuxt, r/vuejs, r/webdev - **Stack Overflow**: Provide detailed answers with links to your documentation - **GitHub**: README files, discussions, and issues get crawled h3. [Create Link-Worthy Content](#create-link-worthy-content) Original research gets cited. Publish: - Benchmarks (Nuxt 4 vs Nuxt 3 performance) - Surveys (What tools do Nuxt developers use?) - Case studies (How we improved LCP by 50%) [**Backlinko saw 800% YoY increase**](https://backlinko.com/generative-engine-optimization-geo) in LLM referrals by creating original research content. h2. [Implementing llms.txt](#implementing-llmstxt) The [**llms.txt standard**](https://llmstxt.org/) tells AI crawlers what content to prioritize. Nuxt makes this easy with [**nuxt-llms**](https://github.com/harlan-zw/nuxt-llms): ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['nuxt-llms'], llms: { domain: 'https://example.com', title: 'Nuxt SEO Guide', description: 'Complete guide to SEO for Nuxt applications', sections: [ { title: 'Documentation', links: [ { title: 'Quick Start', href: '/docs/getting-started' }, { title: 'Meta Tags', href: '/docs/meta-tags' } ] } ] } }) ``` The module generates `/llms.txt` and `/llms-full.txt` automatically. AI crawlers like GPTBot check for this file. h2. [Tracking AI Citations](#tracking-ai-citations) Traditional analytics miss AI traffic. Users get answers without clicking through. h3. [New Metrics to Track](#new-metrics-to-track) | **Metric** | **What It Measures** | **How to Track** | | --- | --- | --- | | AI citation rate | How often you're cited in AI responses | Manual sampling, brand monitoring | | Share of AI voice | % of AI answers mentioning your brand | Tools like Profound, Otterly | | Zero-click visibility | Impressions without clicks | Search Console impression data | h3. [Tools for GEO Monitoring](#tools-for-geo-monitoring) - **[**Profound**](https://www.tryprofound.com/)**: Tracks AI citations across ChatGPT, Perplexity, Gemini - **[**Otterly.ai**](https://www.otterly.ai/)**: Monitors brand mentions in AI responses - **Search Console**: High impressions with low clicks may indicate AI extraction h3. [Manual Citation Checking](#manual-citation-checking) Query AI engines directly with your target keywords: ``` ChatGPT: "How do I add meta tags in Nuxt?" Perplexity: "Best practices for Nuxt SEO" Google (AI Overview): "Nuxt meta tags tutorial" ``` Check if your content appears in citations. Screenshot and track monthly. h2. [Nuxt GEO Advantages](#nuxt-geo-advantages) Nuxt provides several built-in advantages for GEO: h3. [SSR by Default](#ssr-by-default) AI crawlers get fully-rendered HTML without JavaScript execution: ``` // nuxt.config.ts export default defineNuxtConfig({ // SSR is enabled by default // For specific routes, use routeRules: routeRules: { '/blog/**': { prerender: true }, // SSG for blog posts '/docs/**': { isr: 3600 } // ISR with 1hr revalidation } }) ``` h3. [Automatic Schema.org](#automatic-schemaorg) With [**Nuxt SEO**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/docs/nuxt-seo/getting-started/introduction), schema.org is generated automatically: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/docs/nuxt-seo/getting-started/introduction) ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/seo'], site: { url: 'https://example.com', name: 'My Site' } }) ``` h3. [robots.txt for AI Crawlers](#robotstxt-for-ai-crawlers) Control which AI engines can access your content: ``` // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/robots'], robots: { groups: [ { userAgent: ['Googlebot', 'GPTBot', 'PerplexityBot'], allow: ['/'] }, { userAgent: 'CCBot', disallow: ['/'] } ] } }) ``` See the [**robots.txt guide**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/learn-seo/nuxt/controlling-crawlers/robots-txt) for full configuration. h2. [Quick Wins Checklist](#quick-wins-checklist) Start with these high-impact changes: - Install [**nuxt-schema-org**](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/docs/schema-org/getting-started/introduction) and configure identity - Add Article schema to blog posts and guides - Write TL;DR summaries at the top of long content - Include code examples in technical articles - Add specific statistics with source citations - Install [**nuxt-llms**](https://github.com/harlan-zw/nuxt-llms) and configure sections - Use `prerender: true` in routeRules for content pages - Answer questions on Reddit/Stack Overflow linking to your content - Test queries in ChatGPT and Perplexity monthly h2. [Full Nuxt SEO Stack for GEO](#full-nuxt-seo-stack-for-geo) For comprehensive GEO optimization, use the full Nuxt SEO module: [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/docs/nuxt-seo/getting-started/introduction) This includes: - **Sitemap**: Ensures all pages are discoverable - **Robots**: Controls crawler access including AI crawlers - **Schema.org**: Automatic structured data - **OG Image**: Social previews that may appear in AI citations - **SEO Utils**: Title templates, canonical URLs, and more --- [**Debugging** Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/learn-seo/nuxt/launch-and-listen/debugging) [**SEO Checklist** Complete checklist for Nuxt SEO. Pre-launch setup, post-launch verification, and ongoing monitoring. Links to detailed guides for every step.](https://nuxtseo.com/learn-seo/nuxt/launch-and-listen/ai-optimized-content/learn-seo/nuxt/launch-and-listen/checklist) **On this page** - [Why GEO Matters](#why-geo-matters) - [GEO vs SEO](#geo-vs-seo) - [What AI Engines Actually Cite](#what-ai-engines-actually-cite) - [Structured Data for AI](#structured-data-for-ai) - [Content Structure for AI Extraction](#content-structure-for-ai-extraction) - [Building External Citations](#building-external-citations) - [Implementing llms.txt](#implementing-llmstxt) - [Tracking AI Citations](#tracking-ai-citations) - [Nuxt GEO Advantages](#nuxt-geo-advantages) - [Quick Wins Checklist](#quick-wins-checklist) - [Full Nuxt SEO Stack for GEO](#full-nuxt-seo-stack-for-geo) --- ### Vue SEO Checklist · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist Description: Complete checklist for Vue SEO. pre-launch setup, post-launch verification, and ongoing monitoring. Links to detailed guides for every step. h1. **Vue SEO Checklist** Complete checklist for Vue SEO. pre-launch setup, post-launch verification, and ongoing monitoring. Links to detailed guides for every step. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** Vue SPAs fail SEO by default. This checklist covers pre-launch setup, post-launch verification, and ongoing monitoring. with links to detailed guides for every step. h2. [Pre-Launch Checklist](#pre-launch-checklist) **SSR & Rendering** - [**SSR implemented**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/rendering) or [**prerendering configured**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/spa/prerendering) for client-only apps - [**Dynamic rendering**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/spa/dynamic-rendering) set up if SSR impossible (serve pre-rendered HTML to crawlers only) - [**Hydration errors**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/spa/hydration) resolved (check browser console for mismatches) - SSR framework chosen if needed ([**Nuxt vs Quasar vs Vite SSR vs VitePress**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/ssr-frameworks)) **Meta Tags** - [**Page titles**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/mastering-meta/titles) configured per route (under 60 chars, descriptive, unique) - [**Meta descriptions**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/mastering-meta/descriptions) set per page (under 160 chars, actionable, unique) - [**Social sharing tags**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/mastering-meta/social-sharing) configured (OG image, Twitter Card, title/description) - [**Schema.org structured data**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/mastering-meta/schema-org) added if applicable (products, recipes, FAQs, articles) - Meta tags tested in [**Google Rich Results Test**](https://search.google.com/test/rich-results) and [**Facebook Debugger**](https://developers.facebook.com/tools/debug/) **URL Structure** - [**URL structure**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/url-structure) uses hyphens (not underscores), lowercase paths, descriptive slugs - [**Trailing slashes**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/trailing-slashes) handled consistently (pick one style, redirect the other) - [**Dynamic routes**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/dynamic-routes) configured with unique meta per route param - [**Query parameters**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/query-parameters) excluded from indexing via canonicals (filter/sort/tracking params) - [**Pagination**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/pagination) uses self-referencing canonicals (not rel=prev/next. deprecated) - [**404 pages**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/404-pages) return proper 404 status code (not 200 soft 404) **Crawler Control** - [**robots.txt**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/controlling-crawlers/robots-txt) configured (allow all by default, block staging/admin if needed) - [**XML sitemap**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/controlling-crawlers/sitemaps) generated with all indexable pages (under 50,000 URLs, updated dynamically) - [**Canonical URLs**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/controlling-crawlers/canonical-urls) set on every page (self-referencing or pointing to preferred version) - [**Meta robots tags**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/controlling-crawlers/meta-tags) configured for noindex pages (staging, user profiles, duplicate content) - [**Redirects**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/controlling-crawlers/redirects) use 301 status codes (not 302), avoid redirect chains **Security** - HTTPS configured with valid SSL certificate - [**Security headers**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/security) set (CSP, X-Frame-Options, HSTS) without blocking crawlers - No sensitive data exposed in HTML source or robots.txt **Performance** - Images lazy-loaded with proper alt text - Critical CSS inlined, non-critical CSS deferred - JavaScript code-split by route - CDN configured for static assets - Fonts preloaded (WOFF2 format) h2. [Post-Launch Checklist](#post-launch-checklist) **Google Search Console** - [**GSC property created and verified**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/search-console) (DNS, meta tag, or HTML file method) - [**Sitemap submitted**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/controlling-crawlers/sitemaps) via GSC (check Sitemaps report for errors) - URL Inspection tool tested on key pages (check if indexable, view rendered HTML) - Page Indexing report reviewed (resolve "Crawled - currently not indexed" and "Discovered - currently not indexed") **Indexing Verification** - [**Indexing status checked**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/indexing-issues) for important pages (`site:yoursite.com` in Google) - Rendered HTML verified (use URL Inspection tool. check if meta tags appear in rendered output) - [**Soft 404 errors**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/routes-and-rendering/404-pages) resolved (404 pages must return 404 status, not 200) - No "Duplicate without user-selected canonical" errors in GSC - Orphan pages linked from sitemap or internal navigation **Performance & Core Web Vitals** - [**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/core-web-vitals) passing thresholds (LCP ≤2.5s, INP ≤200ms, CLS ≤0.1) - Mobile-first indexing verified (use Mobile-Friendly Test) - Page Speed Insights run on key pages (address blocking resources, unused CSS/JS) - Real user metrics monitored via web-vitals library or RUM tool **Social Sharing** - OG images tested in Facebook Debugger (clear cache if needed) - Twitter Cards validated (compose tweet without posting to preview card) - Slack link previews tested (OG tags control Slack unfurls) - LinkedIn share tested (uses OG tags, sometimes caches aggressively) **Analytics** - [**Analytics configured**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/seo-monitoring) (GA4, Plausible, Fathom, or Umami) - SPA tracking set up (router change events fire pageview events) - Search terms tracking enabled (Google Search Console > Performance report) - Goals/conversions tracked (signups, purchases, downloads) h2. [Ongoing Monitoring](#ongoing-monitoring) **Weekly Tasks** - GSC Performance report reviewed (track impressions, clicks, CTR, position) - Page Indexing report checked (resolve new indexing issues) - Core Web Vitals monitored (check for regressions after deploys) - Broken links scanned (use [**Link Checker**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/docs/link-checker) or Screaming Frog) **Monthly Tasks** - [**SEO monitoring tools**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/seo-monitoring) reviewed (Ahrefs, SEMrush, or free alternatives) - Competitor rankings tracked (identify keyword gaps and opportunities) - Search rankings monitored for target keywords (track position changes) - Backlink profile reviewed (identify new links, lost links, disavow spam if needed) - Content gaps identified (check GSC for queries where you rank #5-#20, optimize pages) **Quarterly Tasks** - Full technical SEO audit (crawl site with Screaming Frog or Sitebulb) - Sitemap reviewed for bloat (remove low-value pages, add new pages) - Redirect chains audited (flatten chains, remove unnecessary redirects after 1 year) - Structured data updated for deprecated types (Google removes schema types periodically) - Canonical URL configuration verified (GSC shows user-declared vs Google-selected canonical) **After Major Changes** - [**Site migration**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/site-migration) checklist followed if domain or URL structure changed - [**IndexNow**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/indexnow) or URL submission used for urgent updates (new pages, content changes) - Redirects tested after URL structure changes (301s in place, no 404s) - GSC URL Inspection re-run on changed pages (request indexing for critical updates) - Search rankings monitored weekly for 60 days post-migration (track recovery timeline) h2. [Framework-Specific Notes](#framework-specific-notes) h3. [Nuxt Users](#nuxt-users) [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/docs/nuxt-seo/getting-started/introduction) handles most of this automatically. [**See Nuxt SEO checklist →**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/nuxt/launch-and-listen/checklist) h3. [Client-Only Vue (No SSR)](#client-only-vue-no-ssr) - SPAs struggle with SEO. [**prerendering**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/spa/prerendering) or [**dynamic rendering**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/spa/dynamic-rendering) required - [**SSR frameworks**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/ssr-frameworks) (Nuxt, Quasar, Vite SSR) solve this at the cost of complexity - [**VitePress**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/ssr-frameworks/vitepress) works for docs/blogs with static content h3. [Migrating from vue-meta?](#migrating-from-vue-meta) [**Migration guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/mastering-meta/migrating-vue-meta) covers switching to Unhead for Vue 3 compatibility. h2. [Common Mistakes](#common-mistakes) **Skipping SSR verification** - "View Source" on deployed site. If meta tags missing, crawlers see empty `<head>`. **Generic meta descriptions** - "Welcome to page" template descriptions hurt CTR. Write unique descriptions. **Ignoring GSC warnings** - Page Indexing report shows why pages excluded. 67.6% of sites have duplicate content issues. **Soft 404s** - 404 pages returning 200 status waste crawl budget and confuse Google. **Redirect chains** - A→B→C dilutes authority. Flatten to A→C after fixing the root cause. **Forgetting mobile** - Google uses mobile-first indexing. Desktop-only testing misses most issues. **Not monitoring CWV** - 53% of sites fail Core Web Vitals. Poor metrics tank rankings in competitive queries. h2. [Tools Referenced](#tools-referenced) - [**Google Search Console**](https://search.google.com/search-console) - Free indexing, performance, and technical SEO monitoring - [**Google Rich Results Test**](https://search.google.com/test/rich-results) - Structured data validator - [**Facebook Sharing Debugger**](https://developers.facebook.com/tools/debug/) - OG tags validator (works for most platforms) - [**PageSpeed Insights**](https://pagespeed.web.dev/) - Core Web Vitals and performance analysis - [**Mobile-Friendly Test**](https://search.google.com/test/mobile-friendly) - Mobile usability checker - [**Screaming Frog**](https://www.screamingfrogseo.com/) - Desktop SEO spider (free up to 500 URLs) - [**Ahrefs**](https://ahrefs.com/) - Paid SEO suite (backlinks, keywords, rankings) - [**SEMrush**](https://www.semrush.com/) - Paid SEO suite (keyword research, competitor analysis) h2. [Next Steps](#next-steps) Start with [**Mastering Meta**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/mastering-meta) if you haven't configured meta tags yet. Already launched? Check [**Launch and Listen**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen) for post-launch monitoring. Need framework-level SEO? Compare [**SSR Frameworks**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/ssr-frameworks) or use [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/docs/nuxt-seo/getting-started/introduction). --- [**AI Search Optimization** Make your Vue site visible in AI Overviews, ChatGPT, and other generative search engines with structured data and content strategies.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/checklist/learn-seo/vue/launch-and-listen/ai-optimized-content) **On this page** - [Pre-Launch Checklist](#pre-launch-checklist) - [Post-Launch Checklist](#post-launch-checklist) - [Ongoing Monitoring](#ongoing-monitoring) - [Framework-Specific Notes](#framework-specific-notes) - [Common Mistakes](#common-mistakes) - [Tools Referenced](#tools-referenced) - [Next Steps](#next-steps) --- ### Vue Routing and SEO · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering Description: URL structure and rendering mode determine whether search engines can crawl, index, and rank your Vue application. h1. **Vue Routing and SEO** URL structure and rendering mode determine whether search engines can crawl, index, and rank your Vue application. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - Use path segments over query parameters: `/products/shoes` beats `/products?item=shoes` - SSR/SSG delivers HTML immediately; SPA requires JavaScript execution first - Inconsistent trailing slashes create duplicate content. pick one and redirect the other Search engines read URLs before content. `/products/shoes` ranks better than `/products?category=shoes`. `/about` and `/about/` are different pages unless you configure otherwise. Rendering mode determines whether crawlers see your content immediately (SSR/SSG) or must execute JavaScript first (SPA). Google can render JavaScript, but it's slower and less reliable than serving HTML directly. h2. [Section Overview](#section-overview) | **Topic** | **What You'll Learn** | | --- | --- | | [**URL Structure**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/url-structure) | Slugs, hyphens vs underscores, URL length, keyword placement | | [**Pagination**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/pagination) | Self-referencing canonicals, infinite scroll vs pagination, crawlable links | | [**Trailing Slashes**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/trailing-slashes) | Consistent URL formats, redirect configuration | | [**Query Parameters**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/query-parameters) | Filters, tracking params, canonical handling | | [**Hreflang & i18n**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/i18n) | Multilingual sites, x-default, return links | | [**404 Pages**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/404-pages) | Soft 404s, proper status codes, crawl budget | | [**Dynamic Routes**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/dynamic-routes) | Route params, per-route meta tags, SSR patterns | | [**Rendering Modes**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/rendering) | SSR vs SSG vs SPA, when to use each | h2. [Rendering Mode Quick Reference](#rendering-mode-quick-reference) | **Mode** | **Crawler Sees HTML** | **Best For** | | --- | --- | --- | | **SSR** | Immediately | Dynamic content, personalization | | **SSG** | Immediately | Blogs, docs, marketing pages | | **SPA** | After JavaScript | Admin panels, authenticated apps | SPAs require [**prerendering**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/spa/prerendering) or SSR for SEO. Google's JavaScript rendering has a [**second wave of indexing**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) that delays content discovery. h2. [URL Structure Quick Reference](#url-structure-quick-reference) **Path segments over query parameters:** ``` ✅ /products/electronics/laptop ❌ /products?category=electronics&item=laptop ``` **Hyphens over underscores:** ``` ✅ /vue-router-guide ❌ /vue_router_guide ``` **Lowercase, short URLs:** ``` ✅ /blog/vue-seo ❌ /Blog/The-Complete-Guide-To-Vue-SEO-Optimization-2025 ``` Read [**URL Structure**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/url-structure) for implementation details. h2. [Crawl Budget](#crawl-budget) Google allocates limited time to crawl your site. Poor URL structure wastes budget on duplicate or low-value pages. **Common crawl budget problems:** | **Problem** | **Solution** | | --- | --- | | Inconsistent trailing slashes | [**Configure redirects**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/trailing-slashes) | | Pagination without canonicals | [**Self-referencing canonicals**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/pagination) | | Infinite filter combinations | [**Noindex or block in robots.txt**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/query-parameters) | | Soft 404s returning 200 | [**Proper status codes**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/404-pages) | Sites under 10,000 pages rarely need to worry about crawl budget. Large sites should monitor [**Google Search Console**](https://search.google.com/search-console) crawl stats. h2. [Dynamic Routes in Vue Router](#dynamic-routes-in-vue-router) Vue Router's dynamic segments create clean URLs: ``` const routes = [ { path: '/blog/:slug', component: BlogPost }, { path: '/products/:category/:id', component: Product } ] ``` Generates `/blog/vue-seo-guide` and `/products/electronics/123`. Each dynamic route needs unique meta tags. Set them with [**Unhead**](https://unhead.unjs.io/): ``` <script setup lang="ts"> const route = useRoute() const post = await fetchPost(route.params.slug) useSeoMeta({ title: post.title, description: post.excerpt }) </script> ``` Read [**Dynamic Routes**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/dynamic-routes) for SSR patterns, optional params, and common SEO issues. h2. [Multilingual Sites](#multilingual-sites) Use hreflang tags to tell search engines which language version to show users: ``` useHead({ link: [ { rel: 'alternate', hreflang: 'en', href: 'https://example.com/en' }, { rel: 'alternate', hreflang: 'fr', href: 'https://example.com/fr' }, { rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/en' } ] }) ``` Read [**Hreflang & i18n**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/i18n) for vue-i18n integration, bidirectional links, and common mistakes. h2. [Using Nuxt?](#using-nuxt) Nuxt handles most of this automatically. file-based routing, SSR by default, automatic canonical URLs. The [**Nuxt SEO module**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/docs/nuxt-seo/getting-started/introduction) adds sitemaps, OG images, and structured data. [**Learn more in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/nuxt/routes-and-rendering) --- [**Hydration & SEO** How hydration failures cause Google to index broken versions of your site. Debug mismatches, fix common causes, optimize with partial hydration.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/spa/hydration) [**URL Structure** Create search-optimized URLs using Vue Router. Learn slug formatting, parameter handling, and route patterns that improve rankings.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/learn-seo/vue/routes-and-rendering/url-structure) **On this page** - [Section Overview](#section-overview) - [Rendering Mode Quick Reference](#rendering-mode-quick-reference) - [URL Structure Quick Reference](#url-structure-quick-reference) - [Crawl Budget](#crawl-budget) - [Dynamic Routes in Vue Router](#dynamic-routes-in-vue-router) - [Multilingual Sites](#multilingual-sites) - [Using Nuxt?](#using-nuxt) --- ### Launching and Monitoring Your Vue Site · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen Description: Deploy your Vue site to production and monitor its indexing status in search engines. h1. **Launching and Monitoring Your Vue Site** Deploy your Vue site to production and monitor its indexing status in search engines. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - Set up Google Search Console and submit your sitemap before expecting indexing - Indexing takes days to weeks. Google prioritizes sites with backlinks and fresh content - Track weekly metrics: impressions, clicks, CTR, and Core Web Vitals Your Vue site is built. Now get it indexed and track its performance. h2. [Pre-Launch Checklist](#pre-launch-checklist) Before deploying to production: - Production domain configured with SSL certificate - [**robots.txt**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/controlling-crawlers/robots-txt) allows search engine crawlers - No `noindex` [**meta tags**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/controlling-crawlers/meta-tags) on pages you want indexed - [**Sitemap**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/controlling-crawlers/sitemaps) generated at `/sitemap.xml` - [**Canonical URLs**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/controlling-crawlers/canonical-urls) point to production domain - Social preview images working - Mobile-friendly design - [**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/core-web-vitals) passing - [**404 pages**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/routes-and-rendering/404-pages) return proper status codes - [**Redirects**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/controlling-crawlers/redirects) from old URLs configured (if redesigning) Don't delay your launch chasing perfection. Ship with working fundamentals, then iterate. h2. [Getting Indexed](#getting-indexed) Your site needs two things to appear in search results: be crawlable and be indexed. **Crawlability** means search engines can access your pages. **Indexing** means they've added your pages to their database. Read the [**Going Live guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/going-live) for deployment strategies and common pitfalls. h3. [Submit to Search Engines](#submit-to-search-engines) Google and Bing won't automatically know your site exists. [**Set up Google Search Console**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/search-console) and submit your sitemap: 1. Add your property (domain or URL prefix) 2. Verify ownership (DNS record, HTML file, or meta tag) 3. Submit sitemap at **Indexing > Sitemaps** For Bing, submit through [**Bing Webmaster Tools**](https://www.bing.com/webmasters) or use [**IndexNow**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/indexnow) for instant notification. Indexing takes days to weeks. [**Google prioritizes sites**](https://developers.google.com/search/docs/fundamentals/how-search-works) with backlinks and regular content updates. h3. [Check Indexing Status](#check-indexing-status) ``` h1. Search for your domain site:yourdomain.com h1. Check specific page site:yourdomain.com/specific-page ``` Google Search Console shows detailed indexing data under Page Indexing reports: - **Indexed** . in Google's index - **Discovered - currently not indexed** . found but not crawled yet - **Crawled - currently not indexed** . crawled but Google chose not to index See [**Debugging Indexing Issues**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/indexing-issues) for fixes when pages won't index. h2. [Monitoring Performance](#monitoring-performance) Set up these tools after launch: | **Tool** | **What It Tracks** | **Cost** | | --- | --- | --- | | [**Google Search Console**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/search-console) | Impressions, clicks, rankings, indexing | Free | | GA4 / Plausible / Fathom | Organic traffic, user behavior | Free / Paid | | Ahrefs / SEMrush | Backlinks, keyword rankings, competitors | Paid | See [**SEO Monitoring Tools**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/seo-monitoring) for setup guides and recommendations. h3. [Weekly Metrics](#weekly-metrics) Track these metrics every week: **Search Console:** - Total impressions and clicks - Click-through rate (CTR) - Average position for target keywords - Coverage errors **Analytics:** - Organic traffic trends - Top landing pages - Bounce rate **[**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/core-web-vitals):** - LCP under 2.5s - INP under 200ms - CLS under 0.1 Don't obsess over daily fluctuations. Look for trends over weeks and months. h2. [Common Post-Launch Issues](#common-post-launch-issues) **Site not indexing after weeks:** Check [**robots.txt**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/controlling-crawlers/robots-txt), verify no `noindex` tags, confirm [**sitemap**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/controlling-crawlers/sitemaps) submitted correctly. See [**Indexing Issues**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/indexing-issues). **Pages indexed but not ranking:** Normal for new sites. Improve [**title tags**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/mastering-meta/titles) and [**meta descriptions**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/mastering-meta/descriptions). Add internal links. **High impressions, low clicks:** Your titles and descriptions need work. Check [**Mastering Meta**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/mastering-meta) guides. **Migrating to a new domain?** See [**Site Migration SEO**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/site-migration) for redirect mapping and recovery timelines. h2. [Deep Dive Guides](#deep-dive-guides) | **Guide** | **What You'll Learn** | | --- | --- | | [**Going Live**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/going-live) | First-time indexing, SSR vs SPA, common Vue issues | | [**Google Search Console**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/search-console) | Verification, reports, URL inspection | | [**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/core-web-vitals) | LCP, INP, CLS optimization for Vue | | [**Indexing Issues**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/indexing-issues) | Fix "crawled not indexed" errors | | [**SEO Monitoring**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/seo-monitoring) | Analytics setup, rank tracking, alerts | | [**Site Migration**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/site-migration) | Domain changes, redirects, recovery | | [**IndexNow**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/indexnow) | Instant indexing for Bing/Yandex | | [**AI Search Optimization**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/ai-optimized-content) | GEO, AI Overviews, ChatGPT citations | h2. [Using Nuxt?](#using-nuxt) Nuxt SEO handles sitemap generation, robots.txt, and OG images automatically. [**Learn more about launching in Nuxt →**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/nuxt/launch-and-listen) --- [**VitePress SEO** VitePress offers built-in SEO features like sitemap generation, meta tags, and fast static site generation for documentation and blogs.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/ssr-frameworks/vitepress) [**Getting Indexed** How to get your Vue site crawled and indexed for the first time by Google.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/learn-seo/vue/launch-and-listen/going-live) **On this page** - [Pre-Launch Checklist](#pre-launch-checklist) - [Getting Indexed](#getting-indexed) - [Monitoring Performance](#monitoring-performance) - [Common Post-Launch Issues](#common-post-launch-issues) - [Deep Dive Guides](#deep-dive-guides) - [Using Nuxt?](#using-nuxt) --- ### Dynamic Rendering for SEO · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/spa/dynamic-rendering Description: Dynamic rendering serves pre-rendered HTML to crawlers while users see JavaScript. Google no longer recommends this. use SSR or SSG instead. h1. **Dynamic Rendering for SEO** Dynamic rendering serves pre-rendered HTML to crawlers while users see JavaScript. Google no longer recommends this. use SSR or SSG instead. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** **What you'll learn** - Google explicitly marked dynamic rendering as a "workaround". not recommended for new projects - Serves pre-rendered HTML to crawlers while users get client-side rendering - Use SSR or SSG instead. modern frameworks make server-side rendering straightforward Dynamic rendering detects crawler requests and serves them pre-rendered HTML, while users receive client-side rendered content. Google explicitly calls this a "workaround" and [**no longer recommends it**](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) as of 2025. use SSR or SSG instead. Use only when SSR/SSG prove impractical due to legacy constraints. Modern frameworks make server-side rendering straightforward, eliminating the need for this complexity. h2. [Why Google Deprecated It](#why-google-deprecated-it) Google explicitly calls dynamic rendering a "workaround" and no longer recommends it. Use SSR or SSG for new projects. dynamic rendering adds complexity without SEO benefit. Google [**positions dynamic rendering**](https://cristalcode.net/articles/why-dynamic-rendering-is-outdated-in-2025-and-what-to-use-instead) as error-prone, increasing server load and maintenance burden. Serving different content to crawlers versus users risks cloaking violations if implementations diverge. User-agent detection is fragile. new crawlers break assumptions, AI crawlers go unrecognized, and maintenance never ends. [**John Mueller confirms**](https://www.seroundtable.com/google-seo-dynamic-rendering-or-server-side-rendering-33947.html) no ranking benefit exists between dynamic rendering and SSR. they're infrastructure choices, not SEO advantages. Googlebot handles JavaScript well in 2025. [**Server-side rendering ensures**](https://stakque.com/javascript-seo-guide/) immediate content visibility without relying on crawler execution behavior. h2. [How It Works](#how-it-works) ``` import express from 'express' import { chromium } from 'playwright' import { createServer } from 'vite' const app = express() const crawlers = /googlebot|bingbot|slurp|duckduckbot/i app.use(async (req, res, next) => { const userAgent = req.get('user-agent') if (!crawlers.test(userAgent)) { return next() } const browser = await chromium.launch() const page = await browser.newPage() await page.goto(\`http://localhost:${PORT}${req.path}\`) await page.waitForLoadState('networkidle') const html = await page.content() await browser.close() res.send(html) }) ``` ``` import express from 'express' import { chromium } from 'playwright' const app = express() const crawlers = /googlebot|bingbot|slurp|duckduckbot/i app.use(async (req, res, next) => { if (!crawlers.test(req.get('user-agent'))) { return next() } const browser = await chromium.launch() const page = await browser.newPage() await page.goto(\`http://localhost:3000${req.path}\`) await page.waitForLoadState('networkidle') const html = await page.content() await browser.close() res.send(html) }) ``` ``` import { defineEventHandler, getHeader } from 'h3' import { chromium } from 'playwright' const crawlers = /googlebot|bingbot|slurp|duckduckbot/i export default defineEventHandler(async (event) => { const userAgent = getHeader(event, 'user-agent') if (!crawlers.test(userAgent)) { return } const browser = await chromium.launch() const page = await browser.newPage() await page.goto(\`http://localhost:3000${event.path}\`) await page.waitForLoadState('networkidle') const html = await page.content() await browser.close() return html }) ``` This requires headless browser infrastructure. Chrome binary (~280MB), system dependencies, memory management, error handling. [**Full production setup**](https://prerender.io/blog/puppeteer-vs-prerender-for-javascript-rendering/) takes 4-8 weeks engineering effort. h2. [Tools Comparison](#tools-comparison) h3. [Rendertron](#rendertron) [**Google's open-source solution**](https://github.com/GoogleChrome/rendertron) built on Puppeteer. Deploy your own server, customize rendering logic, maintain Chrome updates manually. [**Popularity declining**](https://npmtrends.com/prerender-vs-puppeteer-vs-rendertron). 138 weekly npm downloads vs Puppeteer's 6.2M. No Docker file included; refer to Puppeteer docs for deployment. Free but requires infrastructure. Memory leaks, Chrome crashes, and crawler detection updates fall on you. h3. [Prerender.io](#prerenderio) Commercial service handling rendering, caching, and crawler detection automatically. [**Zero maintenance burden**](https://prerender.io/blog/alternatives-to-rendertron-for-dynamic-rendering/). they manage Chrome updates, resource allocation, error recovery. Cache expiration: 6 hours to 30 days depending on plan. Submit sitemap for automatic refresh. [**Works with all frameworks**](https://www.ohmycrawl.com/rendertron-prerender-io/) without code changes. Pricing starts $50/month for 10K pages. Worth it if avoiding weeks of engineering work. h3. [Puppeteer (DIY)](#puppeteer-diy) [**Open-source Node library**](https://prerender.io/blog/puppeteer-vs-prerender-for-javascript-rendering/) controlling headless Chrome. Full control, zero licensing costs, maximum complexity. Production deployment requires 30+ system packages on Linux. Must handle caching, scaling, failure recovery. Maintenance never stops. Chrome updates monthly, memory leaks accumulate, edge cases multiply. Choose if you have spare engineering time and need custom rendering logic SSR can't provide. h2. [Implementation Example](#implementation-example) Minimal Rendertron setup: ``` import express from 'express' import rendertron from 'rendertron-middleware' const app = express() app.use(rendertron.makeMiddleware({ proxyUrl: 'https://render-tron.appspot.com/render', userAgentPattern: /googlebot|bingbot|slurp|duckduckbot|whatsapp|facebookexternalhit|twitterbot/i })) ``` ``` import express from 'express' import rendertron from 'rendertron-middleware' import { createServer } from 'vite' const app = express() const vite = await createServer({ server: { middlewareMode: true } }) app.use(rendertron.makeMiddleware({ proxyUrl: 'https://render-tron.appspot.com/render' })) app.use(vite.middlewares) ``` ``` import { defineEventHandler, getHeader, proxyRequest } from 'h3' const crawlers = /googlebot|bingbot|slurp|duckduckbot|whatsapp|facebookexternalhit|twitterbot/i export default defineEventHandler(async (event) => { const userAgent = getHeader(event, 'user-agent') if (crawlers.test(userAgent)) { return proxyRequest(event, \`https://render-tron.appspot.com/render/${event.path}\`) } }) ``` Google's public Rendertron instance is for testing only. Run your own for production. h2. [When to Use (Rarely)](#when-to-use-rarely) **Valid use cases:** - Legacy SPA you can't rewrite to SSR - Third-party JavaScript breaking SSR (ads, analytics, chat widgets) - Content behind authentication crawlers need to see **Don't use for:** - New projects. use SSR from day one - Sites with infrequent content updates. use SSG - Anything where you control the codebase. refactor to SSR [**Modern frameworks**](https://digitalthriveai.com/en-us/resources/guides/javascript-seo-rendering/) make SSR straightforward. Nuxt handles it automatically. Custom Vite SSR takes a weekend. Dynamic rendering adds complexity that rarely pays off. h2. [Cloaking Risk](#cloaking-risk) Serving crawlers different content than users violates [**Google's webmaster guidelines**](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) if the versions diverge. JavaScript errors in user version while crawler gets perfect HTML triggers manual penalties. Keep rendered output identical: - Test both versions regularly - Monitor JavaScript errors in production - Use same data sources for both renders - Log differences and alert on mismatches Safer to fix SSR hydration issues than maintain two code paths. h2. [Testing Dynamic Rendering](#testing-dynamic-rendering) Verify crawlers receive correct HTML: ``` h1. Test with Googlebot user agent curl -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \ https://your-site.com h1. Should return fully rendered HTML with meta tags ``` Check for: 1. Meta tags present in source (not injected by JavaScript) 2. Full content visible without running JavaScript 3. Schema.org markup in HTML 4. Internal links crawlable 5. Response time under 3 seconds Use [**Google Search Console URL Inspection**](https://gracker.ai/seo-101/javascript-rendering-seo) to see what Googlebot receives. If it differs from user version, you risk penalties. h2. [Migration Path](#migration-path) Moving from dynamic rendering to SSR: 1. **Audit dependencies**. identify libraries breaking SSR 2. **Start with one route**. prove SSR works for a single page 3. **Handle client-only code**. wrap in `onMounted` or check `typeof window` 4. **Test incrementally**. keep dynamic rendering as fallback 5. **Monitor crawl errors**. watch Search Console during transition 6. **Remove dynamic rendering**. once SSR covers all routes Don't attempt big-bang rewrites. Migrate route by route, keep dynamic rendering for uncovered paths. h2. [Using Nuxt?](#using-nuxt) Nuxt handles server-side rendering automatically. No need for dynamic rendering workarounds. [**Learn more about Nuxt SEO →**](https://nuxtseo.com/learn-seo/vue/spa/dynamic-rendering/learn-seo/nuxt/routes-and-rendering/rendering) h2. [Sources](#sources) - [**Dynamic Rendering as a workaround - Google Search Central**](https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering) - [**Why Dynamic Rendering Is Outdated in 2025 - Cristal Code**](https://cristalcode.net/articles/why-dynamic-rendering-is-outdated-in-2025-and-what-to-use-instead) - [**Google Says No SEO Benefit for Dynamic Rendering - Search Engine Roundtable**](https://www.seroundtable.com/google-seo-dynamic-rendering-or-server-side-rendering-33947.html) - [**JavaScript SEO Guide 2025 - Stakque**](https://stakque.com/javascript-seo-guide/) - [**Alternatives to Rendertron - Prerender.io**](https://prerender.io/blog/alternatives-to-rendertron-for-dynamic-rendering/) - [**Rendertron vs Prerender.io - OhMyCrawl**](https://www.ohmycrawl.com/rendertron-prerender-io/) - [**Puppeteer vs Prerender.io - Prerender.io**](https://prerender.io/blog/puppeteer-vs-prerender-for-javascript-rendering/) - [**JavaScript Rendering and SEO - Gracker AI**](https://gracker.ai/seo-101/javascript-rendering-seo) - [**JavaScript SEO Rendering Guide - Digital Thrive**](https://digitalthriveai.com/en-us/resources/guides/javascript-seo-rendering/) - [**npm trends - prerender vs puppeteer vs rendertron**](https://npmtrends.com/prerender-vs-puppeteer-vs-rendertron) --- [**Prerendering** Build-time and on-demand prerendering for client-side Vue apps. How to get SPA performance with SSR indexing.](https://nuxtseo.com/learn-seo/vue/spa/dynamic-rendering/learn-seo/vue/spa/prerendering) [**Hydration & SEO** How hydration failures cause Google to index broken versions of your site. Debug mismatches, fix common causes, optimize with partial hydration.](https://nuxtseo.com/learn-seo/vue/spa/dynamic-rendering/learn-seo/vue/spa/hydration) **On this page** - [Why Google Deprecated It](#why-google-deprecated-it) - [How It Works](#how-it-works) - [Tools Comparison](#tools-comparison) - [Implementation Example](#implementation-example) - [When to Use (Rarely)](#when-to-use-rarely) - [Cloaking Risk](#cloaking-risk) - [Testing Dynamic Rendering](#testing-dynamic-rendering) - [Migration Path](#migration-path) - [Using Nuxt?](#using-nuxt) - [Sources](#sources) --- ### Add JSON-LD Structured Data in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/mastering-meta/schema-org Description: Learn how to implement Schema.org structured data in Vue using Unhead. Get rich results in Google search with type-safe JSON-LD markup. h1. **Add JSON-LD Structured Data in Vue** Learn how to implement Schema.org structured data in Vue using Unhead. Get rich results in Google search with type-safe JSON-LD markup. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 5, 2024** Updated **Dec 17, 2025** **What you'll learn** - JSON-LD is Google's recommended format for structured data - Schema.org helps search engines and AI understand your content - Use `useSchemaOrg()` for type-safe markup with automatic graph linking Schema.org structured data helps Google display [**Rich Results**](https://developers.google.com/search/docs/appearance/structured-data/search-gallery). stars, FAQs, recipes, product prices. Rotten Tomatoes saw a [**25% higher click-through rate**](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) on pages with structured data compared to pages without. ``` <!-- JSON-LD structured data in the head --> <script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Article", "headline": "Add JSON-LD Structured Data in Vue", "author": { "@type": "Person", "name": "Your Name" } } </script> ``` Google [**recommends JSON-LD**](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) as the easiest format to implement and maintain. Common rich result types: - [**Article**](https://developers.google.com/search/docs/data-types/article) - news, blog posts - [**Breadcrumb**](https://developers.google.com/search/docs/data-types/breadcrumb) - navigation trail - [**FAQ**](https://developers.google.com/search/docs/data-types/faqpage) - question/answer pairs - [**Product**](https://developers.google.com/search/docs/appearance/structured-data/product) - pricing, availability Rich results aren't guaranteed. Content must match the markup and follow [**Google's structured data guidelines**](https://developers.google.com/search/docs/appearance/structured-data/sd-policies). h2. [Setup with Unhead](#setup-with-unhead) Vue uses [**Unhead**](https://unhead.unjs.io/) for head management. You can add JSON-LD via `useHead()`, but `useSchemaOrg()` from `@unhead/schema-org` provides type safety and automatic [**graph linking**](https://schema.org/docs/data-and-datasets.html). ``` pnpm add -D @unhead/schema-org ``` See [**Unhead Schema.org setup**](https://unhead.unjs.io/schema-org/getting-started/setup) for full install instructions. ``` import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue' useSchemaOrg([ defineArticle({ headline: 'Add JSON-LD Structured Data in Vue', author: { name: 'Your Name' }, datePublished: new Date(2024, 0, 15), }) ]) ``` ``` useHead({ script: [ { type: 'application/ld+json', innerHTML: JSON.stringify({ '@context': 'https://schema.org', '@type': 'Article', 'headline': 'Add JSON-LD Structured Data in Vue', 'author': { '@type': 'Person', 'name': 'Your Name' } }) } ], }) ``` The `defineX()` helpers align with [**Google's Structured Data Guidelines**](https://developers.google.com/search/docs/guides/sd-policies) and handle boilerplate: | **Helper** | **Rich Result Type** | | --- | --- | | [`defineArticle()`](https://unhead.unjs.io/schema-org/schema/article) | Article, NewsArticle, BlogPosting | | [`defineBreadcrumb()`](https://unhead.unjs.io/schema-org/schema/breadcrumb) | Breadcrumb navigation | | [`defineQuestion()`](https://unhead.unjs.io/schema-org/schema/question) | FAQ pages | | [`defineProduct()`](https://unhead.unjs.io/schema-org/schema/product) | Product listings | h2. [Reactive Data](#reactive-data) `useSchemaOrg()` accepts refs and computed getters: ``` const article = ref({ title: 'My Article', description: 'Article description' }) useSchemaOrg([ defineArticle({ headline: () => article.value.title, description: () => article.value.description, }) ]) ``` h2. [Site-Wide Setup](#site-wide-setup) Set up base schema in your root component. Child components can add specific types that link to this graph automatically. app.vue ``` <script lang="ts" setup> import { defineOrganization, defineWebPage, defineWebSite, useSchemaOrg } from '@unhead/schema-org/vue' const route = useRoute() useHead({ templateParams: { schemaOrg: { host: 'https://mysite.com', path: route.path, inLanguage: 'en', } } }) useSchemaOrg([ defineWebPage(), defineWebSite({ name: 'My Site', description: 'What my site does.', }), // Use defineOrganization for businesses, definePerson for personal sites defineOrganization({ name: 'My Company', logo: '/logo.png', }) ]) </script> ``` h2. [Blog Article Example](#blog-article-example) Vue's hierarchical head system means you don't need to repeat `WebSite` and `WebPage` if they're in the layout: blog/[slug].vue ``` <script lang="ts" setup> import { defineArticle, useSchemaOrg } from '@unhead/schema-org/vue' const article = await fetchArticle() useSchemaOrg([ defineArticle({ headline: article.title, image: article.image, datePublished: article.publishedAt, dateModified: article.updatedAt, author: { name: article.author.name, url: article.author.url, } }) ]) </script> ``` h2. [Testing Your Markup](#testing-your-markup) Use [**Google's Rich Results Test**](https://search.google.com/test/rich-results) to validate your structured data. Enter your URL or paste the HTML directly. The tool shows which rich results are eligible and flags any errors. For local development, inspect the `<head>` to verify the JSON-LD script tag is present and valid JSON. h2. [Using Nuxt?](#using-nuxt) Nuxt SEO handles Schema.org [**automatically**](https://nuxtseo.com/learn-seo/vue/mastering-meta/schema-org/docs/schema-org/getting-started/introduction) with zero config. See the [**Nuxt Schema.org guide**](https://nuxtseo.com/learn-seo/vue/mastering-meta/schema-org/learn-seo/nuxt/mastering-meta/schema-org) for the Nuxt-specific approach. **Quick Check** **Which format does Google recommend for structured data?** - `Microdata` - Older format, harder to maintain and debug - `JSON-LD` - Correct! Google recommends JSON-LD as easiest to implement and maintain - `RDFa` - Supported but not recommended by Google --- [**Social Sharing** Configure Open Graph and Twitter Card meta tags for rich link previews on Facebook, X/Twitter, LinkedIn, Slack, and Discord.](https://nuxtseo.com/learn-seo/vue/mastering-meta/schema-org/learn-seo/vue/mastering-meta/social-sharing) [**Migrating vue-meta** Step-by-step guide to migrate your Vue app from vue-meta to Unhead for Vue 3 compatibility and modern head management.](https://nuxtseo.com/learn-seo/vue/mastering-meta/schema-org/learn-seo/vue/mastering-meta/migrating-vue-meta) **On this page** - [Setup with Unhead](#setup-with-unhead) - [Reactive Data](#reactive-data) - [Site-Wide Setup](#site-wide-setup) - [Blog Article Example](#blog-article-example) - [Testing Your Markup](#testing-your-markup) - [Using Nuxt?](#using-nuxt) --- ### Rich Results in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results Description: Get rich results in Google search with Schema.org structured data. Learn which types still work after Google's 2023 FAQ/HowTo removals. h1. **Rich Results in Vue** Get rich results in Google search with Schema.org structured data. Learn which types still work after Google's 2023 FAQ/HowTo removals. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** **What you'll learn** - Google removed FAQ/HowTo rich results for most sites in August 2023 - Product, Recipe, Article, Event types still show rich results - Test with Rich Results Test before deploying. valid markup doesn't guarantee display Rich results are enhanced search listings that display more than the standard blue link. stars, prices, cooking times, FAQs. Google shows them when your page has valid [**Schema.org structured data**](https://schema.org/). Not all rich results survived. Google removed [**FAQ and HowTo rich results**](https://developers.google.com/search/blog/2023/08/howto-faq-changes) in August 2023 for most sites. h2. [What Changed in 2023](#what-changed-in-2023) Google made two major changes to rich results: **August 8, 2023:** FAQ rich results limited to authoritative government and health sites only. All other sites lost FAQ rich results. **September 13, 2023:** HowTo rich results completely removed from desktop and mobile. No exceptions. Google said the changes provide "a cleaner and more consistent search experience." The reality: these result types were overused and spammy. John Mueller [**referenced "the tragedy of the commons"**](https://www.searchenginejournal.com/google-downgrades-visibility-of-howto-and-faq-rich-results/493522/) when explaining the decision. Sites that relied on FAQ/HowTo rich results saw CTR drops. No way around it. those rich snippets are gone. FAQ rich results are now limited to authoritative government and health sites only. Adding FAQ schema to a regular business or blog site will have no visible effect in search results. h2. [Rich Results That Still Work](#rich-results-that-still-work) These rich result types remain active as of December 2025: | **Type** | **Shows** | **Example** | | --- | --- | --- | | [**Article**](https://developers.google.com/search/docs/appearance/structured-data/article) | Headline, image, date | News, blog posts | | [**Breadcrumb**](https://developers.google.com/search/docs/appearance/structured-data/breadcrumb) | Navigation path | Home > Category > Page | | [**Event**](https://developers.google.com/search/docs/appearance/structured-data/event) | Date, location, ticket info | Concerts, conferences | | [**LocalBusiness**](https://developers.google.com/search/docs/appearance/structured-data/local-business) | Hours, location, reviews | Stores, restaurants | | [**Product**](https://developers.google.com/search/docs/appearance/structured-data/product) | Price, availability, reviews | E-commerce | | [**Recipe**](https://developers.google.com/search/docs/appearance/structured-data/recipe) | Cook time, calories, ratings | Food sites | | [**Review**](https://developers.google.com/search/docs/appearance/structured-data/review-snippet) | Star rating | Product/service reviews | | [**Video**](https://developers.google.com/search/docs/appearance/structured-data/video) | Thumbnail, duration, upload date | YouTube, Vimeo embeds | [**See the full list in Google's Search Gallery**](https://developers.google.com/search/docs/appearance/structured-data/search-gallery). Product, Recipe, and Event rich results drive measurable CTR increases. Rotten Tomatoes saw a [**25% higher click-through rate**](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) on pages with structured data. h2. [Testing Your Markup](#testing-your-markup) ![Rich Results Testing Workflow](https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results/images/learn-seo/vue/rich-results-workflow.svg) Use [**Rich Results Test**](https://search.google.com/test/rich-results) to validate structured data. Enter your URL or paste HTML directly. The tool shows which rich results are eligible and flags errors. **Two validation tools exist. use both:** 1. **Rich Results Test** . Tests Google-specific eligibility. Use this to see if your markup qualifies for rich results in search. 2. **Schema Markup Validator** . Tests schema.org syntax compliance. Use this for types that don't qualify for rich results but you want on the page anyway (like Organization, WebSite). The [**Schema Markup Validator**](https://validator.schema.org/) validates all schema.org types, not just those eligible for Google rich results. If you're implementing Organization or WebSite schema (which don't show rich results), use this tool. h2. [Monitoring in Search Console](#monitoring-in-search-console) Google Search Console shows a separate report for each rich result type found on your site: 1. Open Search Console 2. Go to **Enhancements** in the sidebar 3. Click the rich result type (Product, Recipe, Article, etc.) Each report shows valid items (can display as rich results) and invalid items (have errors blocking display). Fix errors immediately. they block rich results entirely. Address warnings when possible. they may reduce effectiveness. The reports show a sample of detected items, not every instance. Google drops reporting for types that no longer appear in search. [**September 2025 removed reporting**](https://jobnimbusmarketing.com/blog/september-2025-google-search-console-reports-update/) for Course Info, Estimated Salary, Learning Video, Special Announcement, Vehicle Listing, Claim Review. h2. [Implementation in Vue](#implementation-in-vue) See [**Schema.org in Vue**](https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results/learn-seo/vue/mastering-meta/schema-org) for implementation details using Unhead. Quick example for Product rich results: ``` <script setup lang="ts"> import { defineProduct, useSchemaOrg } from '@unhead/schema-org/vue' const product = await fetchProduct() useSchemaOrg([ defineProduct({ name: product.name, image: product.image, offers: { price: product.price, priceCurrency: 'USD', availability: 'https://schema.org/InStock', }, aggregateRating: { ratingValue: product.rating, reviewCount: product.reviews, } }) ]) </script> ``` The `defineProduct()` helper ensures your markup meets [**Google's Product structured data guidelines**](https://developers.google.com/search/docs/appearance/structured-data/product). h2. [Should You Remove FAQ/HowTo Markup?](#should-you-remove-faqhowto-markup) No. Google says structured data that's not used "does not cause problems for Search, but also has no visible effects." Leave it. it doesn't hurt, and Google could reverse course. If you're adding new markup, skip FAQ and HowTo unless you're a government/health site. Focus on Product, Recipe, Article, Event types that still work. h2. [Common Rich Result Mistakes](#common-rich-result-mistakes) **Missing required fields** . Each rich result type has required properties. Product needs `name`, `image`, `offers`. Recipe needs `name`, `image`, `totalTime`. Check Google's docs for each type. **Invalid image URLs** . Images must be absolute HTTPS URLs, not relative paths. Minimum 1200x630px for most types. **Blocking images in robots.txt** . Google must be able to crawl your images. Check your robots.txt doesn't block `/images/` or similar paths. **No visible content** . The structured data must match visible content on the page. Markup for a recipe that's not actually on the page violates [**Google's spam policies**](https://developers.google.com/search/docs/appearance/structured-data/sd-policies). **Syntax errors** . Invalid JSON-LD breaks all structured data on the page. Validate with the Schema Markup Validator before deploying. h2. [Rich Results Aren't Guaranteed](#rich-results-arent-guaranteed) Valid markup doesn't guarantee rich results will show. Google decides based on: - Search query relevance - User intent - Competition for that result type - Page authority You can have perfect structured data and still not see rich results. That's normal. Focus on adding markup for the user benefit (better organized data) not purely for search appearance. h2. [Using Nuxt?](#using-nuxt) Nuxt SEO handles Schema.org [**automatically**](https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results/docs/schema-org/getting-started/introduction) with zero config. [**Learn more about Rich Results in Nuxt →**](https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results/learn-seo/nuxt/mastering-meta/schema-org) --- [**Migrating vue-meta** Step-by-step guide to migrate your Vue app from vue-meta to Unhead for Vue 3 compatibility and modern head management.](https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results/learn-seo/vue/mastering-meta/migrating-vue-meta) [**Controlling Crawlers** Manage how search engines crawl and index your Vue app. Configure robots.txt, sitemaps, canonical URLs, and redirects for better SEO.](https://nuxtseo.com/learn-seo/vue/mastering-meta/rich-results/learn-seo/vue/controlling-crawlers) **On this page** - [What Changed in 2023](#what-changed-in-2023) - [Rich Results That Still Work](#rich-results-that-still-work) - [Testing Your Markup](#testing-your-markup) - [Monitoring in Search Console](#monitoring-in-search-console) - [Implementation in Vue](#implementation-in-vue) - [Should You Remove FAQ/HowTo Markup?](#should-you-remove-faqhowto-markup) - [Common Rich Result Mistakes](#common-rich-result-mistakes) - [Rich Results Aren't Guaranteed](#rich-results-arent-guaranteed) - [Using Nuxt?](#using-nuxt) --- ### Canonical URLs in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls Description: Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Vue. h1. **Canonical URLs in Vue** Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Vue. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - Canonical tags are hints, not directives. Google may choose a different canonical - Always use absolute URLs ([**https://mysite.com/page**](https://mysite.com/page), not /page) - Self-referencing canonicals are recommended even for unique pages Canonical URLs tell search engines which version of a page is the primary copy when duplicate content exists at multiple URLs. [**67.6% of websites have duplicate content issues**](https://www.womenintechseo.com/knowledge/dealing-with-duplicate-content-canonicalization-in-detail/) due to poor canonicalization. Use canonicals for URLs with query parameters (filters, sorting), same content on multiple paths, paginated sequences, and cross-domain syndication. For redirecting users, use [**HTTP redirects**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/redirects) instead. For blocking pages from search, use [**meta robots**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/meta-tags) with noindex. h2. [Quick Setup](#quick-setup) Add canonical URLs to your Vue pages using Unhead composables: Basic Usage ``` useHead({ link: [ { rel: 'canonical', href: 'https://mysite.com/products/phone' } ] }) ``` With Query Params ``` // Keep sort parameter in canonical useHead({ link: [ { rel: 'canonical', href: \`https://mysite.com/products?sort=${sort}\` } ] }) ``` Cross Domain ``` useHead({ link: [ { rel: 'canonical', href: 'https://otherdomain.com/original-article' } ] }) ``` For Vue applications, you'll need to [**install Unhead manually**](https://unhead.unjs.io/guide/getting-started/installation). h2. [Understanding Canonical URLs](#understanding-canonical-urls) A canonical URL is implemented as a link tag in your page's head: ``` <link rel="canonical" href="https://mysite.com/page"> ``` h3. [Canonical Tags Are Hints, Not Directives](#canonical-tags-are-hints-not-directives) Google treats canonicals as [**strong signals, not mandatory rules**](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls). Google's Joachim Kupke: "It's a hint that we honor strongly. We'll take your preference into account, in conjunction with other signals, when calculating the most relevant page to display in search results." Google may choose a different canonical than you specify when: - Content differs significantly between URLs - Multiple conflicting canonical declarations exist - Google believes a different page is more authoritative [**Google uses the canonical page**](https://developers.google.com/search/docs/crawling-indexing/canonicalization) as the main source to evaluate content and quality. Non-canonical URLs may still be crawled but usually won't appear in search results. h3. [Self-Referencing Canonicals](#self-referencing-canonicals) [**Self-referencing canonicals are recommended**](https://searchengineland.com/canonicalization-seo-448161) even for unique pages. They establish a clear preferred URL and prevent search engines from guessing when tracking parameters or alternate URL formats appear. Google's John Mueller: "I recommend using a self-referential canonical because it really makes it clear to us which page you want to have indexed, or what the URL should be when it is indexed." h3. [Important Notes](#important-notes) - Must use absolute URLs ([**Google documentation**](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)) - Only one canonical per page (multiple declarations cause Google to ignore all hints) - Must be server-side rendered (crawlers don't run JavaScript) - Include canonical pages in [**sitemaps**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/sitemaps), exclude non-canonical pages - Don't combine canonical with noindex [**meta robots**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/meta-tags). sends conflicting signals Google may ignore your canonical if the target page's content differs significantly from the source. Canonicals should point to pages with substantially similar content. not a completely different page or your homepage. h2. [Common Patterns](#common-patterns) h3. [Filter and Sort Parameters](#filter-and-sort-parameters) pages/products/[category].vue ``` const { sort, filter, page } = useRoute().query useHead({ link: [{ rel: 'canonical', // Only include sort in canonical, remove filter and pagination href: sort ? \`https://mysite.com/products/${category}?sort=${sort}\` : \`https://mysite.com/products/${category}\` }] }) ``` h3. [Pagination](#pagination) Use self-referencing canonicals on paginated pages. [**don't point them all to page 1**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). Each page in the sequence has unique content and should be indexed separately. pages/blog/[page].vue ``` const route = useRoute() const page = route.params.page || '1' useHead({ link: [{ rel: 'canonical', // Each page references itself href: \`https://mysite.com/blog${page === '1' ? '' : \`?page=${page}\`}\` }] }) ``` Google [**deprecated rel=prev/next in 2019**](https://searchengineland.com/pagination-seo-what-you-need-to-know-453707) and now automatically recognizes pagination patterns. h3. [Mobile/Desktop Versions](#mobiledesktop-versions) If you have separate mobile URLs (m.domain.com), [**keep the desktop URL as canonical**](https://www.lumar.io/office-hours/separate-mobile/) even with mobile-first indexing. Google uses canonicals to understand which pages belong together, then internally selects the mobile version for indexing. pages/products/[id].vue ``` const route = useRoute() const id = route.params.id useHead({ link: [{ rel: 'canonical', // Mobile site (m.mysite.com) points to desktop href: \`https://mysite.com/products/${id}\` }] }) ``` [**Don't switch canonicals from desktop to mobile URLs**](https://www.seroundtable.com/google-m-dot-urls-the-canonical-37809.html). Google advises against this. Use responsive design with a single URL instead. h3. [Cross-Domain Syndication](#cross-domain-syndication) [**Google no longer recommends cross-domain canonicals**](https://searchengineland.com/google-no-longer-recommends-canonical-tags-for-syndicated-content-406491) for syndicated content. Instead, syndication partners should use noindex meta robots to block indexing. pages/article/[slug].vue ``` // If syndicating TO other sites, have them use noindex useSeoMeta({ robots: 'noindex, follow' }) ``` pages/original-article.vue ``` // If this IS the original, use self-referencing canonical useHead({ link: [{ rel: 'canonical', href: 'https://mysite.com/articles/original-slug' }] }) ``` Exception: News publishers syndicating to Google News should still use cross-domain canonicals per [**Google's news publisher guidance**](https://developers.google.com/search/blog/2009/12/handling-legitimate-cross-domain). h2. [Testing](#testing) h3. [Using Google Search Console](#using-google-search-console) Use the [**URL Inspection tool**](https://support.google.com/webmasters/answer/9012289) to verify canonical implementation: 1. Enter the URL you want to inspect 2. Check the "Page indexing" section 3. Compare "User-declared canonical" vs "Google-selected canonical" 4. If they don't match, [**Google found conflicting signals**](https://developers.google.com/search/blog/2019/03/how-to-discover-suggest-google-selected) Note: [**Live tests won't show Google-selected canonical**](https://www.conductor.com/academy/url-inspection-tool/). you'll only see this for already indexed pages. h3. [When Google Selects a Different Canonical](#when-google-selects-a-different-canonical) Common reasons Google ignores your canonical preference: - Content differs significantly between URLs - Canonical URL returns non-200 status code - Canonical URL uses HTTP instead of HTTPS - Multiple conflicting canonical declarations on page - Canonical chain detected (A → B → C) [**Fix canonicalization issues**](https://developers.google.com/search/docs/crawling-indexing/canonicalization-troubleshooting) by checking internal links, sitemap inclusion, and removing conflicting signals. h3. [Important Checks](#important-checks) - Validate absolute URL format - Check for canonical chains (A → B → C) - Verify SSR implementation (view page source, not inspected HTML) - Test with and without parameters - Don't combine canonical with noindex [**meta robots**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/meta-tags) h2. [Handling Edge Cases](#handling-edge-cases) h3. [Multiple Language Versions](#multiple-language-versions) For multilingual sites, combine canonicals with hreflang: ``` useHead({ link: [ { rel: 'canonical', href: 'https://mysite.com/en/page' }, { rel: 'alternate', hreflang: 'fr', href: 'https://mysite.com/fr/page' } ] }) ``` h3. [Protocol/WWW Variations](#protocolwww-variations) Handle through [**server redirects**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/redirects) rather than canonicals: ``` import express from 'express' const app = express() app.use((req, res, next) => { const host = req.get('host') if (!host.startsWith('www.')) { return res.redirect(301, \`https://www.${host}${req.path}\`) } next() }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { const host = req.get('host') if (!host.startsWith('www.')) { return res.redirect(301, \`https://www.${host}${req.path}\`) } next() }) ``` ``` import { defineEventHandler, getRequestHost, sendRedirect } from 'h3' export default defineEventHandler((event) => { const host = getRequestHost(event) if (!host.startsWith('www.')) { return sendRedirect(event, \`https://www.${host}${event.path}\`, 301) } }) ``` h3. [Dynamic Canonicals](#dynamic-canonicals) For dynamic routes, ensure canonical URLs are consistent: composables/useCanonical.ts ``` export function useCanonical(path: string) { const siteUrl = import.meta.env.VITE_SITE_URL return { link: [{ rel: 'canonical', href: \`${siteUrl}${path}\` }] } } ``` h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about canonical URLs in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/nuxt/controlling-crawlers/canonical-urls) --- [**Robot Meta Tag** Control page-level indexing with meta robots tags. Block search results pages, manage pagination, and prevent duplicate content in Vue apps.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/meta-tags) [**HTTP Redirects** 301 redirects preserve SEO value when content moves. Implement server-side redirects in Vue SSR to pass link equity and maintain rankings.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls/learn-seo/vue/controlling-crawlers/redirects) **On this page** - [Quick Setup](#quick-setup) - [Understanding Canonical URLs](#understanding-canonical-urls) - [Common Patterns](#common-patterns) - [Testing](#testing) - [Handling Edge Cases](#handling-edge-cases) - [Using Nuxt?](#using-nuxt) --- ### HTTP Redirects for SEO in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects Description: 301 redirects preserve SEO value when content moves. Implement server-side redirects in Vue SSR to pass link equity and maintain rankings. h1. **HTTP Redirects for SEO in Vue** 301 redirects preserve SEO value when content moves. Implement server-side redirects in Vue SSR to pass link equity and maintain rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)9 mins read Published **Nov 3, 2024** Updated **Dec 17, 2025** **What you'll learn** - 301 redirects pass ~100% link equity. use for permanent content moves - Server-side redirects required for SEO (JavaScript redirects don't pass link equity) - Avoid redirect chains (A→B→C). redirect directly to the final destination 301 redirects pass nearly 100% of link equity to the new URL ([**Google confirms**](https://www.searchenginejournal.com/301-redirect-pagerank/275503/)), preserving your SEO value when content moves. Server-side implementation required for search engines to recognize them. Use for permanent moves: site migrations, URL restructuring, domain changes, deleted pages with replacements. For duplicate content use [**canonical tags**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects/learn-seo/vue/controlling-crawlers/canonical-urls). For temporary moves use 302 redirects. h2. [Quick Setup](#quick-setup) In Vue applications, implement redirects at the server level: ``` import express from 'express' const app = express() app.get('/old-page', (req, res) => { res.redirect(301, '/new-page') }) app.get('/blog/:slug', (req, res) => { res.redirect(301, \`/articles/${req.params.slug}\`) }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { if (req.path === '/old-page') { return res.redirect(301, '/new-page') } if (req.path.startsWith('/blog/')) { const slug = req.path.replace('/blog/', '') return res.redirect(301, \`/articles/${slug}\`) } next() }) ``` ``` import { defineEventHandler, sendRedirect } from 'h3' export default defineEventHandler((event) => { if (event.path === '/old-page') { return sendRedirect(event, '/new-page', 301) } if (event.path.startsWith('/blog/')) { const slug = event.path.replace('/blog/', '') return sendRedirect(event, \`/articles/${slug}\`, 301) } }) ``` h2. [301 vs 302 Redirects](#_301-vs-302-redirects) h3. [When to Use Each](#when-to-use-each) **301 (Permanent)** - Transfers ~100% of link equity to new URL ([**Google**](https://www.searchenginejournal.com/301-redirect-pagerank/275503/)) - Permanent content moves - Domain migrations - URL structure changes - Deleted pages with direct replacements - HTTP to HTTPS upgrades **302 (Temporary)** - Keeps SEO value on original URL - A/B testing - Temporary promotions - Maintenance pages - Out-of-stock product redirects If a 302 stays active for months with no plans to revert, switch to 301 ([**SEO Clarity**](https://www.seoclarity.net/resources/knowledgebase/use-301-redirect-vs-302-redirect-15683/)). Search engines may eventually treat long-term 302s as permanent anyway. **307/308** - Like 302/301 but preserve HTTP method (POST remains POST). Rarely needed for typical SEO work. h3. [How Long to Keep Redirects Active](#how-long-to-keep-redirects-active) Google recommends keeping 301 redirects active for at least one year ([**Search Central**](https://www.searchenginejournal.com/google-keep-301-redirects-in-place-for-a-year/428998/)). This ensures Google transfers all ranking signals and recrawls links pointing to old URLs. Keep redirects longer if: - External sites still link to old URLs - Old URLs receive referral traffic - High-value pages with many backlinks Removing redirects before Google processes them loses the transferred SEO value permanently. h3. [Avoid Redirect Chains](#avoid-redirect-chains) Redirect chains (A → B → C) waste [**crawl budget**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects/learn-seo/vue/controlling-crawlers#crawler-budget) and slow page speed ([**Gotch SEO**](https://www.gotchseo.com/redirect-chains/)). Each hop degrades Core Web Vitals, particularly LCP and TTFB. Google follows up to 5 redirect hops, then aborts ([**Hike SEO**](https://www.hikeseo.co/learn/technical/redirect-chains)). Redirect directly to final destination: Bad: ``` /old → /interim → /final ``` Good: ``` /old → /final /interim → /final ``` h2. [Common Patterns](#common-patterns) h3. [Domain Migration](#domain-migration) ``` import express from 'express' const app = express() app.use((req, res, next) => { const host = req.get('host') if (host === 'old-domain.com') { return res.redirect(301, \`https://new-domain.com${req.path}\`) } next() }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { const host = req.get('host') if (host === 'old-domain.com') { return res.redirect(301, \`https://new-domain.com${req.path}\`) } next() }) ``` ``` import { defineEventHandler, getRequestHost, sendRedirect } from 'h3' export default defineEventHandler((event) => { const host = getRequestHost(event) if (host === 'old-domain.com') { return sendRedirect(event, \`https://new-domain.com${event.path}\`, 301) } }) ``` h3. [URL Structure Changes](#url-structure-changes) ``` import express from 'express' const app = express() app.get('/old', (req, res) => { res.redirect(301, '/new') }) app.get('/blog/:slug', (req, res) => { res.redirect(301, \`/articles/${req.params.slug}\`) }) app.get('/products/:id', (req, res) => { res.redirect(301, \`/shop/${req.params.id}\`) }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { if (req.path === '/old') { return res.redirect(301, '/new') } if (req.path.startsWith('/blog/')) { const slug = req.path.replace('/blog/', '') return res.redirect(301, \`/articles/${slug}\`) } if (req.path.startsWith('/products/')) { const id = req.path.replace('/products/', '') return res.redirect(301, \`/shop/${id}\`) } next() }) ``` ``` import { defineEventHandler, sendRedirect } from 'h3' export default defineEventHandler((event) => { if (event.path === '/old') { return sendRedirect(event, '/new', 301) } if (event.path.startsWith('/blog/')) { const slug = event.path.replace('/blog/', '') return sendRedirect(event, \`/articles/${slug}\`, 301) } if (event.path.startsWith('/products/')) { const id = event.path.replace('/products/', '') return sendRedirect(event, \`/shop/${id}\`, 301) } }) ``` h3. [HTTPS Enforcement](#https-enforcement) ``` import express from 'express' const app = express() app.use((req, res, next) => { if (req.headers['x-forwarded-proto'] !== 'https') { return res.redirect(301, \`https://${req.get('host')}${req.path}\`) } next() }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { if (req.headers['x-forwarded-proto'] !== 'https') { return res.redirect(301, \`https://${req.get('host')}${req.path}\`) } next() }) ``` ``` import { defineEventHandler, getHeader, getRequestHost, sendRedirect } from 'h3' export default defineEventHandler((event) => { if (getHeader(event, 'x-forwarded-proto') !== 'https') { const host = getRequestHost(event) return sendRedirect(event, \`https://${host}${event.path}\`, 301) } }) ``` Learn more about HTTPS in our [**security guide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects/learn-seo/vue/routes-and-rendering/security#https). h3. [WWW Standardization](#www-standardization) ``` import express from 'express' const app = express() app.use((req, res, next) => { const host = req.get('host') if (!host.startsWith('www.')) { return res.redirect(301, \`https://www.${host}${req.path}\`) } next() }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { const host = req.get('host') if (!host.startsWith('www.')) { return res.redirect(301, \`https://www.${host}${req.path}\`) } next() }) ``` ``` import { defineEventHandler, getRequestHost, sendRedirect } from 'h3' export default defineEventHandler((event) => { const host = getRequestHost(event) if (!host.startsWith('www.')) { return sendRedirect(event, \`https://www.${host}${event.path}\`, 301) } }) ``` h2. [Testing Redirects](#testing-redirects) Verify redirects work correctly before deploying: 1. **Check status code** - Use browser dev tools Network tab, confirm 301 or 302 2. **Test destination** - Ensure redirect points to correct final URL 3. **Verify no chains** - Confirm single hop to destination 4. **Test trailing slashes** - Check with and without trailing slash 5. **Check query parameters** - Verify parameters carry over if needed h3. [Tools](#tools) - [**Google Search Console**](https://search.google.com/search-console) - Monitor crawl errors and redirect issues - Browser Dev Tools Network tab - Check status codes and headers - [**Screaming Frog**](https://www.screamingfrog.co.uk/seo-spider/) - Bulk redirect testing and chain detection - curl - `curl -I https://example.com/old-page` shows redirect headers h2. [Common Mistakes](#common-mistakes) h3. [Redirecting to Irrelevant Pages](#redirecting-to-irrelevant-pages) Redirecting deleted pages to your homepage damages SEO and user experience. Google may treat this as a soft 404, ignoring link equity transfer ([**Victorious**](https://victorious.com/blog/301-redirects/)). ``` // ❌ Bad - mass redirects to homepage app.get('/blog/*', (req, res) => res.redirect(301, '/')) // ✅ Good - redirect to relevant content app.get('/blog/vue-tips', (req, res) => res.redirect(301, '/articles/vue-tips')) app.get('/blog/seo-guide', (req, res) => res.redirect(301, '/articles/seo-guide')) ``` ``` // ❌ Bad app.use((req, res, next) => { if (req.path.startsWith('/blog/')) return res.redirect(301, '/') next() }) // ✅ Good app.use((req, res, next) => { if (req.path === '/blog/vue-tips') return res.redirect(301, '/articles/vue-tips') if (req.path === '/blog/seo-guide') return res.redirect(301, '/articles/seo-guide') next() }) ``` ``` import { defineEventHandler, sendRedirect } from 'h3' // ❌ Bad export default defineEventHandler((event) => { if (event.path.startsWith('/blog/')) return sendRedirect(event, '/', 301) }) // ✅ Good export default defineEventHandler((event) => { if (event.path === '/blog/vue-tips') return sendRedirect(event, '/articles/vue-tips', 301) if (event.path === '/blog/seo-guide') return sendRedirect(event, '/articles/seo-guide', 301) }) ``` h3. [Redirect Loops](#redirect-loops) Circular redirects break your site: ``` // ❌ Bad - creates infinite loop app.get('/page-a', (req, res) => res.redirect(301, '/page-b')) app.get('/page-b', (req, res) => res.redirect(301, '/page-a')) // ✅ Good - both redirect to final destination app.get('/page-a', (req, res) => res.redirect(301, '/final')) app.get('/page-b', (req, res) => res.redirect(301, '/final')) ``` ``` // ❌ Bad - creates infinite loop app.use((req, res, next) => { if (req.path === '/page-a') return res.redirect(301, '/page-b') if (req.path === '/page-b') return res.redirect(301, '/page-a') next() }) // ✅ Good - both redirect to final destination app.use((req, res, next) => { if (req.path === '/page-a') return res.redirect(301, '/final') if (req.path === '/page-b') return res.redirect(301, '/final') next() }) ``` ``` import { defineEventHandler, sendRedirect } from 'h3' // ❌ Bad - creates infinite loop export default defineEventHandler((event) => { if (event.path === '/page-a') return sendRedirect(event, '/page-b', 301) if (event.path === '/page-b') return sendRedirect(event, '/page-a', 301) }) // ✅ Good - both redirect to final destination export default defineEventHandler((event) => { if (event.path === '/page-a') return sendRedirect(event, '/final', 301) if (event.path === '/page-b') return sendRedirect(event, '/final', 301) }) ``` h3. [Using Client-Side Redirects for SEO](#using-client-side-redirects-for-seo) JavaScript redirects don't pass link equity reliably. Search engines may not execute JavaScript before indexing. Always use server-side redirects (301/302 status codes) for SEO purposes. h3. [Not Updating Internal Links](#not-updating-internal-links) Relying on redirects for internal links wastes server resources and slows page speed. Update internal links to point directly to new URLs, keep redirects for external links and old bookmarks. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about redirects in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects/learn-seo/nuxt/controlling-crawlers/redirects) --- [**Canonical Link Tag** Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Vue.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects/learn-seo/vue/controlling-crawlers/canonical-urls) [**Duplicate Content** Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects/learn-seo/vue/controlling-crawlers/duplicate-content) **On this page** - [Quick Setup](#quick-setup) - [301 vs 302 Redirects](#_301-vs-302-redirects) - [Common Patterns](#common-patterns) - [Testing Redirects](#testing-redirects) - [Common Mistakes](#common-mistakes) - [Using Nuxt?](#using-nuxt) --- ### Prerendering Vue SPAs for SEO · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/spa/prerendering Description: Build-time and on-demand prerendering for client-side Vue apps. How to get SPA performance with SSR indexing. h1. **Prerendering Vue SPAs for SEO** Build-time and on-demand prerendering for client-side Vue apps. How to get SPA performance with SSR indexing. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - Prerendering generates HTML at build time. deploy as static files to any CDN - Works for static content with routes known at build time - Use vite-ssg for modern projects, Prerender.io for legacy SPAs with frequent updates Prerendering gives you SPA performance with SSR indexing. You fire up a headless browser at build time, load your routes, dump the HTML. Google sees content immediately. [**Google can execute JavaScript**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics), but it's slower and less reliable than HTML. Prerendering removes the wait. h2. [When to Prerender](#when-to-prerender) Use prerendering when: - You have a client-side SPA (no SSR framework) - Routes are known at build time - Content changes daily or less frequently - You want fast deployment (static files, no server) Don't prerender when: - Content changes hourly (use SSR instead) - Routes depend on user data (profiles, dashboards) - You have 10,000+ dynamic pages (builds time out) If your `/about` and `/pricing` pages never change, prerender them. If you're showing user-generated content or real-time data, you need [**SSR**](https://nuxtseo.com/learn-seo/vue/spa/prerendering/learn-seo/vue/routes-and-rendering/rendering). h2. [Build-Time vs On-Demand Prerendering](#build-time-vs-on-demand-prerendering) **Build-time prerendering** (vite-ssg, prerender-spa-plugin): - Generates HTML during `npm run build` - Deploy as static files to CDN - Fast serving, no runtime cost - Can't handle frequently updated content **On-demand prerendering** (Prerender.io, Rendertron): - Service prerenders when crawler visits - Detects bots by user agent - Serves fresh content to crawlers - Costs money, adds latency Most Vue apps need build-time prerendering. On-demand is for SPAs with frequent content changes that can't use SSR. h2. [Build-Time: vite-ssg](#build-time-vite-ssg) [**vite-ssg**](https://github.com/antfu/vite-ssg) prerenders Vue apps using Vite's SSR capabilities. It's the modern replacement for prerender-spa-plugin. Install: ``` npm install -D vite-ssg ``` Update your entry file: ``` import { ViteSSG } from 'vite-ssg' import App from './App.vue' import routes from './routes' export const createApp = ViteSSG( App, { routes }, ({ app, router, initialState }) => { // Install plugins, setup app } ) ``` ``` import vue from '@vitejs/plugin-vue' import { defineConfig } from 'vite' export default defineConfig({ plugins: [vue()], ssgOptions: { script: 'async', formatting: 'minify' } }) ``` Build generates HTML files matching your routes: ``` npm run build h1. Creates dist/index.html, dist/about/index.html, etc. ``` Deploy `dist/` to Netlify, Vercel, Cloudflare Pages. anywhere that serves static files. **Limitations:** - Routes must be defined at build time (no `/user/:id` unless you generate all IDs) - Build time increases with route count - Client-side navigation still works after hydration h2. [Build-Time: prerender-spa-plugin](#build-time-prerender-spa-plugin) [**prerender-spa-plugin**](https://github.com/chrisvfritz/prerender-spa-plugin) uses Puppeteer to render pages. Works with webpack and Vue CLI, but hasn't been updated since 2019. Use vite-ssg for new projects. If you're on Vue CLI: ``` npm install -D prerender-spa-plugin ``` ``` const path = require('node:path') const PrerenderSPAPlugin = require('prerender-spa-plugin') module.exports = { configureWebpack: { plugins: [ new PrerenderSPAPlugin({ staticDir: path.join(__dirname, 'dist'), routes: ['/', '/about', '/pricing'], renderer: new PrerenderSPAPlugin.PuppeteerRenderer({ renderAfterDocumentEvent: 'render-event' }) }) ] } } ``` ``` import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app') // Signal prerender completion document.dispatchEvent(new Event('render-event')) ``` **Why vite-ssg wins:** - Maintained actively (2025 updates) - No Puppeteer dependency (faster builds) - Built for Vite (modern tooling) - Better DX h2. [On-Demand: Prerender.io](#on-demand-prerenderio) [**Prerender.io**](https://prerender.io/framework/vue-js/) renders pages when crawlers visit. It detects bots by user agent, serves cached HTML to them, serves your SPA to humans. Setup with Express middleware: ``` npm install prerender-node ``` ``` import express from 'express' import prerender from 'prerender-node' const app = express() app.use(prerender.set('prerenderToken', 'YOUR_TOKEN')) app.use(express.static('dist')) app.get('*', (req, res) => { res.sendFile('dist/index.html') }) ``` Prerender.io caches rendered pages, serves them to crawlers. Your users still get fast client-side navigation. **Costs:** - Free tier: 250 cached pages - Paid: Starts at $30/month for 10,000 pages **When to use:** - Content updates hourly but you can't use SSR - Product catalogs with 1000+ pages - You need SEO but have legacy SPA codebase **Don't use if:** - You can implement SSG (it's free) - Content rarely changes (build-time wins) h2. [On-Demand: Rendertron](#on-demand-rendertron) [**Rendertron**](https://github.com/GoogleChrome/rendertron) is Google's open-source prerendering service. You host it yourself. ``` docker run -p 3000:3000 rendertron/rendertron ``` Configure your server to proxy bot requests: ``` import express from 'express' const app = express() const RENDERTRON_URL = 'http://localhost:3000' app.use((req, res, next) => { const botPattern = /googlebot|bingbot|slackbot|twitterbot/i if (botPattern.test(req.headers['user-agent'])) { const url = \`${RENDERTRON_URL}/render/${req.protocol}://${req.get('host')}${req.url}\` // Fetch from Rendertron, return HTML return fetch(url).then(r => r.text()).then(html => res.send(html)) } next() }) ``` **Benefits:** - Free (self-hosted) - No vendor lock-in - Control caching behavior **Drawbacks:** - You manage infrastructure - Cache invalidation is manual - Adds latency on first render h2. [Detecting Prerendered Environment](#detecting-prerendered-environment) Your Vue app needs to know when it's being prerendered to avoid browser-only APIs: ``` import { onMounted } from 'vue' export default { setup() { onMounted(() => { // Only runs in browser, not during prerender if (typeof window !== 'undefined') { initializeAnalytics() } }) } } ``` ``` if (window.__PRERENDER_INJECTED) { // Code that should only run during prerender } else { // Normal browser code } ``` Common mistakes: - Calling `localStorage` during prerender (crashes build) - Fetching from `http://localhost` (fails in CI) - Forgetting `data-server-rendered="true"` on root element Add to your root element so Vue hydrates instead of re-rendering: ``` <div id="app" data-server-rendered="true"></div> ``` h2. [Dynamic Routes](#dynamic-routes) Prerendering dynamic routes requires generating all possible paths: ``` // vite.config.ts export default { ssgOptions: { async includedRoutes(paths) { // Fetch all blog post slugs const posts = await fetch('https://api.example.com/posts') .then(r => r.json()) return posts.map(p => \`/blog/${p.slug}\`) } } } ``` For 1000+ dynamic routes, consider: - SSR instead (no build-time cost) - Incremental builds (prerender popular pages only) - On-demand services (Prerender.io) h2. [Testing Prerendered Output](#testing-prerendered-output) After building, verify Google sees content: **1. Check static HTML** ``` npm run build cat dist/about/index.html ``` Should contain your actual content, not just `<div id="app"></div>`. **2. [**Google Search Console URL Inspection**](https://search.google.com/search-console/)** - Test live URL - View rendered HTML - Check screenshot **3. Fetch as Googlebot** ``` curl -A "Mozilla/5.0 (compatible; Googlebot/2.1)" https://yoursite.com/about ``` If you see empty content, prerendering failed. h2. [Common Mistakes](#common-mistakes) **Mistake 1: Blocking JavaScript in robots.txt** ``` h1. ❌ Breaks prerendering detection User-agent: * Disallow: /*.js$ ``` [**Google needs JavaScript to hydrate your app**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics). Never block `.js` or `.css` from Googlebot. See our [**robots.txt guide**](https://nuxtseo.com/learn-seo/vue/spa/prerendering/learn-seo/vue/controlling-crawlers/robots-txt). **Mistake 2: Prerendering user-specific content** Prerendering generates static HTML. If your route shows different content per user, you need SSR. **Mistake 3: Not handling async data** Prerender runs before async requests complete: ``` // ❌ Data won't be in prerendered HTML export default { async setup() { const data = await fetch('/api/data') return { data } } } ``` Use `renderAfterDocumentEvent` to wait for data: ``` // Emit when data loads fetch('/api/data').then((data) => { document.dispatchEvent(new Event('render-event')) }) ``` **Mistake 4: Large route counts** Prerendering 10,000 routes takes hours and often fails in CI. Solutions: - Prerender popular pages only (use analytics data) - Switch to SSR - Use on-demand prerendering h2. [Comparison Table](#comparison-table) | **Approach** | **Cost** | **Setup** | **Best For** | | --- | --- | --- | --- | | **vite-ssg** | Free | Easy | Modern Vue apps, <1000 routes | | **prerender-spa-plugin** | Free | Medium | Legacy Vue CLI projects | | **Prerender.io** | $30+/mo | Easy | Frequent updates, can't use SSR | | **Rendertron** | Hosting | Hard | High traffic, need control | h2. [Using Nuxt?](#using-nuxt) Nuxt handles prerendering automatically with `nuxi generate`. It supports hybrid rendering (SSR + SSG mixed), ISR (Incremental Static Regeneration), and advanced route rules. [**Learn more about Nuxt prerendering →**](https://nuxtseo.com/learn-seo/vue/spa/prerendering/learn-seo/nuxt/routes-and-rendering/rendering) **Quick Check** **When should you use prerendering vs SSR?** - `Prerendering when content updates hourly` - SSR is better for frequently updated content - `SSR when routes are known at build time` - Prerendering works well for static routes known at build time - `Prerendering for static content, SSR for dynamic content` - Correct! Prerender static pages, use SSR for user-specific or frequently updated content --- [**SPA SEO** Why Vue SPAs struggle with search engines and how to fix it. Learn when you need SSR, when prerendering works, and when SPA is fine as-is.](https://nuxtseo.com/learn-seo/vue/spa/prerendering/learn-seo/vue/spa) [**Dynamic Rendering** Dynamic rendering serves pre-rendered HTML to crawlers while users see JavaScript. Google no longer recommends this. use SSR or SSG instead.](https://nuxtseo.com/learn-seo/vue/spa/prerendering/learn-seo/vue/spa/dynamic-rendering) **On this page** - [When to Prerender](#when-to-prerender) - [Build-Time vs On-Demand Prerendering](#build-time-vs-on-demand-prerendering) - [Build-Time: vite-ssg](#build-time-vite-ssg) - [Build-Time: prerender-spa-plugin](#build-time-prerender-spa-plugin) - [On-Demand: Prerender.io](#on-demand-prerenderio) - [On-Demand: Rendertron](#on-demand-rendertron) - [Detecting Prerendered Environment](#detecting-prerendered-environment) - [Dynamic Routes](#dynamic-routes) - [Testing Prerendered Output](#testing-prerendered-output) - [Common Mistakes](#common-mistakes) - [Comparison Table](#comparison-table) - [Using Nuxt?](#using-nuxt) --- ### Duplicate Content SEO in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content Description: Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling. h1. **Duplicate Content SEO in Vue** Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)15 mins read Published **Dec 17, 2025** **What you'll learn** - 67.6% of websites have duplicate content issues. it dilutes ranking signals - Use canonical tags or 301 redirects to consolidate duplicates - Query parameters are the most common source of duplicate content [**67.6% of websites have duplicate content issues**](https://www.womenintechseo.com/knowledge/dealing-with-duplicate-content-canonicalization-in-detail/). Same content at different URLs splits ranking signals and wastes crawl budget. Google picks which version to show. often not the one you want. [**Google doesn't penalize duplicate content**](https://www.hostpapa.com/blog/marketing/does-duplicate-content-hurt-seo/) unless you're deliberately scraping other sites. But it hurts SEO by diluting link equity across multiple URLs and confusing search engines about which page to rank. h2. [Common Causes](#common-causes) h3. [URL Variations](#url-variations) **www vs non-www** `www.mysite.com` and `mysite.com` are [**treated as separate sites**](https://yoast.com/video/ask-yoast-use-www-or-not/). Choose one, redirect the other. **HTTP vs HTTPS** `http://mysite.com` and `https://mysite.com` create duplicates. Always redirect HTTP to HTTPS. **Trailing slashes** `/products` and `/products/` are different URLs. [**Pick one format site-wide**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/routes-and-rendering/trailing-slashes). ``` import express from 'express' const app = express() app.use((req, res, next) => { const host = req.get('host') // Force non-www if (host.startsWith('www.')) { return res.redirect(301, \`https://${host.slice(4)}${req.path}\`) } next() }) ``` ``` import express from 'express' const app = express() app.use((req, res, next) => { if (req.protocol !== 'https') { return res.redirect(301, \`https://${req.get('host')}${req.path}\`) } next() }) ``` ``` import { defineEventHandler, getRequestHost, sendRedirect } from 'h3' export default defineEventHandler((event) => { const host = getRequestHost(event) if (host.startsWith('www.')) { return sendRedirect(event, \`https://${host.slice(4)}${event.path}\`, 301) } }) ``` h3. [Query Parameters](#query-parameters) [**URL parameters create exponential duplicates**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/routes-and-rendering/query-parameters). Three filters generate 8 combinations. Add sorting and pagination. hundreds of URLs. ``` /products /products?color=red /products?color=red&size=large /products?color=red&size=large&sort=price /products?color=red&size=large&sort=price&page=2 ``` **Fix: Canonical tags** pages/Products.vue ``` <script setup lang="ts"> import { useHead } from '@unhead/vue' import { useRoute } from 'vue-router' const route = useRoute() useHead({ link: [{ rel: 'canonical', // Always point to base URL, ignore parameters href: 'https://mysite.com/products' }] }) </script> ``` Or [**block filtered pages from indexing**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/controlling-crawlers/meta-tags): ``` <script setup lang="ts"> import { useSeoMeta } from '@unhead/vue' useSeoMeta({ robots: route.query.filter ? 'noindex, follow' : 'index, follow' }) </script> ``` h3. [Parameter Order](#parameter-order) `?sort=price&filter=red` and `?filter=red&sort=price` are identical content, different URLs. **Fix: Enforce consistent parameter order** composables/useCanonicalParams.ts ``` export function useCanonicalParams(params: Record<string, string>) { const siteUrl = import.meta.env.VITE_SITE_URL const route = useRoute() // Define parameter order const paramOrder = ['category', 'sort', 'filter', 'page'] const orderedParams = Object.fromEntries( Object.entries(params) .sort(([a], [b]) => { const indexA = paramOrder.indexOf(a) const indexB = paramOrder.indexOf(b) if (indexA === -1) return 1 if (indexB === -1) return -1 return indexA - indexB }) ) const queryString = new URLSearchParams(orderedParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${siteUrl}${route.path}?${queryString}\` : \`${siteUrl}${route.path}\` }] } } ``` h3. [Tracking Parameters](#tracking-parameters) Analytics params (`utm_source`, `fbclid`, `gclid`) don't change content but create duplicate URLs. **Fix: Strip from canonical** composables/useCanonicalUrl.ts ``` export function useCanonicalUrl(path: string) { const siteUrl = import.meta.env.VITE_SITE_URL const route = useRoute() const trackingParams = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'msclkid', 'mc_cid', 'mc_eid', '_ga', 'ref' ] const cleanParams = Object.fromEntries( Object.entries(route.query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${siteUrl}${path}?${queryString}\` : \`${siteUrl}${path}\` }] } } ``` Better: [**Redirect tracking params at the server level**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/routes-and-rendering/query-parameters#server-side-parameter-handling) for proper 301 status codes. h3. [Pagination](#pagination) [**Each paginated page has unique content**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/routes-and-rendering/pagination). Use self-referencing canonicals. don't point page 2 to page 1. pages/Blog.vue ``` <script setup lang="ts"> const route = useRoute() const page = route.query.page || '1' useHead({ link: [{ rel: 'canonical', // Each page references itself href: page === '1' ? 'https://mysite.com/blog' : \`https://mysite.com/blog?page=${page}\` }] }) </script> ``` h3. [Print and Mobile Versions](#print-and-mobile-versions) Printer-friendly URLs (`/article?print=true`) and mobile subdomains (`m.mysite.com`) create duplicates. **Fix: Canonical to desktop version** pages/Article.vue ``` <script setup lang="ts"> const route = useRoute() useHead({ link: [{ rel: 'canonical', // Always point to main URL href: 'https://mysite.com/article' }] }) </script> ``` For print, use CSS `@media print` instead of separate URLs. h3. [Session IDs and Click Tracking](#session-ids-and-click-tracking) Session IDs in URLs create infinite variations. ``` /products?sessionid=abc123 /products?sessionid=xyz789 /products?sessionid=def456 ``` **Fix: Don't put session IDs in URLs.** Use cookies. If unavoidable, [**block with robots.txt**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/controlling-crawlers/robots-txt): public/robots.txt ``` User-agent: * Disallow: /*?sessionid= Disallow: /*&sessionid= Disallow: /*?sid= Disallow: /*&sid= ``` h2. [Finding Duplicate Content](#finding-duplicate-content) h3. [Google Search Console](#google-search-console) [**Use the Page Indexing report**](https://support.google.com/webmasters/answer/12642436) to identify duplicates: 1. Open Search Console 2. Go to "Indexing" → "Pages" 3. Look for: - "Duplicate, Google chose different canonical than user" - "Duplicate without user-selected canonical" - "Alternate page with proper canonical tag" Click each category to see affected URLs. If Google chose a different canonical than you specified, [**conflicting signals exist**](https://developers.google.com/search/blog/2019/03/how-to-discover-suggest-google-selected). **Using URL Inspection:** 1. Enter any URL 2. Check "User-declared canonical" vs "Google-selected canonical" 3. If they differ, Google found stronger signals pointing to a different URL h3. [Screaming Frog](#screaming-frog) [**Screaming Frog detects exact and near-duplicate content**](https://www.screamingfrog.co.uk/seo-spider/tutorials/how-to-check-for-duplicate-content/): **Exact duplicates** . Pages with identical HTML (MD5 hash match) **Near duplicates** . Pages with 90%+ similarity (minhash algorithm) **Setup:** 1. Enable near duplicates: `Config > Content > Duplicates` 2. Crawl your site 3. Go to "Content" tab 4. Filter by "Exact Duplicates" or "Near Duplicates" Check these columns: - `Closest Similarity Match` . Percentage match to most similar page - `No. Near Duplicates` . Count of similar pages - `Hash` . MD5 hash for exact duplicate detection Screaming Frog auto-excludes nav and footer elements to focus on main content. [**Adjust threshold**](https://www.gtechme.com/insights/screaming-frog-duplicate-content-audit/) if needed (default 90%). h3. [Site Search](#site-search) Use Google site search to find duplicates manually: ``` site:mysite.com "exact title text" ``` If multiple URLs appear with the same title, you have duplicates. h3. [Siteliner and Copyscape](#siteliner-and-copyscape) **[**Siteliner**](https://www.siteliner.com/)** . Free tool that crawls up to 250 pages, shows duplicate content percentage **[**Copyscape**](https://www.copyscape.com/)** . Detects external duplicate content (other sites copying you) Both useful for content audits but don't replace Search Console or Screaming Frog for technical SEO. h2. [Canonical vs 301 Redirect](#canonical-vs-301-redirect) | **When to Use** | **Canonical Tag** | **301 Redirect** | | --- | --- | --- | | **Need both URLs live** | ✅ Yes | ❌ No | | **User should see one URL** | ❌ No | ✅ Yes | | **Products in multiple categories** | ✅ Yes | ❌ No | | **Old page no longer needed** | ❌ No | ✅ Yes | | **UTM tracking parameters** | ✅ Yes | ❌ No | | **www vs non-www** | ❌ No | ✅ Yes | | **HTTP vs HTTPS** | ❌ No | ✅ Yes | | **Moved/renamed pages** | ❌ No | ✅ Yes | **Canonical tags** are [**hints, not directives**](https://www.searchenginejournal.com/canonical-vs-301-redirect/383124/). Google may ignore them. Both versions remain accessible. Use for duplicates you need (tracking params, multiple category paths). **301 redirects** are permanent. Users see the redirect target. [**Pass the same link equity as canonicals**](https://seranking.com/blog/redirect-vs-canonical-tag/) but remove the duplicate from the index. Use for outdated or unnecessary URLs. **Don't combine:** Using both canonical tag and 301 redirect on the same page sends conflicting signals. Pick one. h2. [Decision Tree](#decision-tree) ![Duplicate Content Decision Tree](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/images/learn-seo/vue/duplicate-content-decision.svg) **Examples:** - `http://mysite.com` → `https://mysite.com` . **301 redirect** - `www.mysite.com` → `mysite.com` . **301 redirect** - `/products?utm_source=twitter` → `/products` . **Canonical tag** - `/products/shoes` and `/sale/shoes` (same product) . **Canonical tag** (one canonical, one alternate) - `/products?filter=red` . **Noindex + canonical to base URL** - `/old-page` → `/new-page` . **301 redirect** h2. [Common Mistakes](#common-mistakes) **Mistake 1: Canonicalizing all paginated pages to page 1** ``` <!-- ❌ Wrong - hides pages 2+ from search --> <script setup> useHead({ link: [{ rel: 'canonical', href: 'https://mysite.com/blog' }] }) </script> ``` [**Each paginated page should reference itself**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/routes-and-rendering/pagination#self-referencing-canonical-tags). **Mistake 2: Using relative canonical URLs** ``` <!-- ❌ Wrong - must be absolute --> <link rel="canonical" href="/products/phone"> <!-- ✅ Correct --> <link rel="canonical" href="https://mysite.com/products/phone"> ``` [**Google requires absolute URLs**](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls). **Mistake 3: Combining canonical with noindex** ``` <!-- ❌ Conflicting signals --> <script setup> useHead({ link: [{ rel: 'canonical', href: 'https://mysite.com/page' }] }) useSeoMeta({ robots: 'noindex, follow' }) </script> ``` Canonical says "this is a duplicate of X." Noindex says "don't index this." [**Pick one**](https://www.oncrawl.com/technical-seo/use-robots-txt-meta-robots-canonical-tags-correctly/). **Mistake 4: Canonical chains** ``` Page A → canonical → Page B → canonical → Page C ``` [**Google may ignore chained canonicals**](https://developers.google.com/search/docs/crawling-indexing/canonicalization-troubleshooting). Canonical directly to the final target. **Mistake 5: Client-side canonicals in SPAs** Googlebot doesn't execute JavaScript fast enough. [**Server-render canonical tags**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/routes-and-rendering/rendering) or use SSR. h2. [Testing](#testing) **1. View page source (not DevTools)** ``` curl https://mysite.com/products?sort=price | grep canonical ``` Should return: ``` <link rel="canonical" href="https://mysite.com/products"> ``` **2. Google Search Console URL Inspection** 1. Enter URL with parameters 2. Check "User-declared canonical" 3. Compare to "Google-selected canonical" 4. Investigate if they differ **3. Check for canonicalization conflicts** - Multiple `rel="canonical"` tags on same page - Canonical in `<head>` vs HTTP header - Canonical points to redirect - Canonical points to noindexed page - Canonical URL returns 4xx/5xx status **4. Test redirect chains** ``` curl -I https://mysite.com/old-url ``` Should show one 301 redirect, not a chain. h2. [Preventing Duplicate Content](#preventing-duplicate-content) h3. [Configure Vue Router](#configure-vue-router) Use consistent trailing slash handling: router/index.ts ``` import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [/* ... */], strict: true // Treat /page and /page/ as different }) // Enforce trailing slashes router.beforeEach((to, from, next) => { if (!to.path.endsWith('/') && to.path !== '/') { next({ path: \`${to.path}/\`, query: to.query }) } else { next() } }) ``` Or [**redirect at the server level**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/routes-and-rendering/trailing-slashes) for proper 301 status codes. h3. [Validate Parameter Values](#validate-parameter-values) Prevent infinite URL variations by whitelisting allowed parameter values: ``` const allowedSortValues = ['price', 'name', 'date', 'rating'] const sort = route.query.sort if (sort && !allowedSortValues.includes(sort)) { // Redirect to base URL or default sort router.replace({ query: { ...route.query, sort: undefined } }) } ``` h3. [Block Low-Value Pages](#block-low-value-pages) Use [**robots.txt**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/controlling-crawlers/robots-txt) to block search results, filtered pages, and admin sections: public/robots.txt ``` User-agent: * h1. Block search results Disallow: /search? Disallow: /*?q= Disallow: /*?query= h1. Block filters Disallow: /*?filter= Disallow: /*&filter= h1. Block tracking params Disallow: /*?utm_source= Disallow: /*?fbclid= Disallow: /*?gclid= h1. Block session IDs Disallow: /*?sessionid= Disallow: /*?sid= ``` h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/docs/nuxt-seo/getting-started/introduction) which handles canonical URLs automatically through site config and route rules. [**Learn more about duplicate content in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/nuxt/controlling-crawlers) --- [**HTTP Redirects** 301 redirects preserve SEO value when content moves. Implement server-side redirects in Vue SSR to pass link equity and maintain rankings.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/controlling-crawlers/redirects) [**llms.txt** Help AI assistants understand your Vue documentation with the llms.txt standard. Learn the file format, implementation, and when it matters.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content/learn-seo/vue/controlling-crawlers/llms-txt) **On this page** - [Common Causes](#common-causes) - [Finding Duplicate Content](#finding-duplicate-content) - [Canonical vs 301 Redirect](#canonical-vs-301-redirect) - [Decision Tree](#decision-tree) - [Common Mistakes](#common-mistakes) - [Testing](#testing) - [Preventing Duplicate Content](#preventing-duplicate-content) - [Using Nuxt?](#using-nuxt) --- ### Nuxt vs Quasar for SEO · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar Description: Nuxt dominates for SSR-first SEO. Quasar excels at cross-platform. Compare SSR capabilities, SEO features, and when to choose each. h1. **Nuxt vs Quasar for SEO** Nuxt dominates for SSR-first SEO. Quasar excels at cross-platform. Compare SSR capabilities, SEO features, and when to choose each. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - Nuxt dominates for SSR-first content sites. SEO tooling built-in with @nuxtjs/seo - Quasar excels at cross-platform apps (web, mobile, desktop) from single codebase - Both support SSR, but Nuxt enables it by default. Quasar requires manual activation [**Nuxt**](https://nuxt.com) is 42 times more popular than [**Quasar**](https://quasar.dev) for a reason: it's built for SSR-first content sites. Quasar's strength is cross-platform apps (mobile, desktop, PWA) from a single codebase. Both support server-side rendering. Their priorities differ. h2. [Quick Comparison](#quick-comparison) | **Feature** | **Nuxt** | **Quasar** | | --- | --- | --- | | **Primary focus** | SSR/SSG, content sites | Cross-platform (web, mobile, desktop) | | **SSR by default** | Yes | No (must enable) | | **SEO modules** | [**@nuxtjs/seo**](https://nuxtseo.com) ecosystem | [**Meta Plugin**](https://quasar.dev/quasar-plugins/meta/), manual setup | | **UI components** | Bring your own | 90+ built-in components | | **GitHub stars** | 59,097 ([**GitHub**](https://github.com/nuxt/nuxt)) | 26,900 ([**GitHub**](https://github.com/quasarframework/quasar)) | | **npm downloads** | 1,028,885/week ([**npm trends**](https://npmtrends.com/nuxt)) | 161,765/week ([**npm trends**](https://npmtrends.com/quasar)) | | **Learning curve** | Moderate (conventions to learn) | Steeper (more configuration) | | **Platform support** | Web | Web, iOS, Android, Electron, browser extensions | Nuxt dominates for [**content-driven sites**](https://simply-how.com/server-side-rendering-web-frameworks) that prioritize SEO. Quasar wins for apps shipping to multiple platforms. h2. [SSR Capabilities](#ssr-capabilities) h3. [Nuxt](#nuxt) SSR is enabled by default in Nuxt. [**Universal rendering**](https://nuxt.com/docs/guide/concepts/rendering#universal-rendering) happens automatically. server renders HTML, browser hydrates to interactive state. ``` // nuxt.config.ts - SSR enabled by default export default defineNuxtConfig({ ssr: true // default, no config needed }) ``` Nuxt's rendering is flexible. Use [**route rules**](https://nuxt.com/docs/guide/concepts/rendering#route-rules) to mix SSR, SSG, and SPA per route: ``` export default defineNuxtConfig({ routeRules: { '/': { prerender: true }, // SSG '/blog/**': { isr: 3600 }, // ISR (revalidate hourly) '/dashboard': { ssr: false }, // SPA '/api/**': { cors: true } } }) ``` [**Hybrid rendering**](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering) lets you optimize per-route. Blog posts static, dashboards dynamic, all in one app. h3. [Quasar](#quasar) Quasar supports SSR but requires [**manual activation**](https://quasar.dev/quasar-cli-vite/developing-ssr/introduction). Create a Quasar SSR project with CLI: ``` quasar create my-app h1. Select SSR mode during setup ``` Or add SSR to existing project: ``` quasar mode add ssr ``` SSR in Quasar is more configuration-heavy. You configure server behavior, prefetch data, handle SSR-specific code paths: ``` // quasar.config.js module.exports = { ssr: { pwa: true, // PWA takeover after hydration middlewares: [ 'render' // or array of other middleware functions ] } } ``` [**Where most frameworks transition to SPA after hydration**](https://medium.com/@kaushalsinh73/quasar-vs-nuxt-picking-the-right-vue-framework-in-2025-440132d4ab4a), Quasar adds PWA takeover. after initial SSR, the app can function as a progressive web app. h2. [SEO Features](#seo-features) h3. [Nuxt SEO](#nuxt-seo) Nuxt's [**@nuxtjs/seo**](https://nuxtseo.com) module combines 6 SEO modules: sitemaps, robots.txt, OG images, structured data, link checking, and meta utilities. [**One install, zero config**](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction). ``` npx nuxi module add @nuxtjs/seo ``` You get automatic sitemaps, robots.txt, and sensible meta defaults: ``` export default defineNuxtConfig({ site: { url: 'https://mysite.com', name: 'My Site' } }) ``` Built-in composables handle meta tags with full TypeScript support: ``` <script setup lang="ts"> useSeoMeta({ title: 'Page Title', description: 'Page description', ogImage: '/og-image.png' }) </script> ``` The `useSchemaOrg` composable adds structured data: ``` useSchemaOrg([ defineOrganization({ name: 'My Company', logo: '/logo.png' }) ]) ``` Nuxt integrates with [**@nuxt/content**](https://content.nuxt.com) for markdown-based sites, [**@nuxt/image**](https://image.nuxt.com) for optimized images, and [**Nuxt DevTools**](https://devtools.nuxt.com) for debugging SEO in development. h3. [Quasar SEO](#quasar-seo) Quasar handles SEO through the [**Quasar Meta Plugin**](https://quasar.dev/quasar-plugins/meta/). It manages `<title>`, `<meta>` tags, and dynamic head elements. ``` // quasar.config.js module.exports = { framework: { plugins: ['Meta'] } } ``` Use the `meta` option in components: ``` <script> export default { meta: { title: 'Page Title', meta: { description: { name: 'description', content: 'Page description' }, ogTitle: { property: 'og:title', content: 'OG Title' } } } } </script> ``` For SSR builds, [**meta tags are served by the server**](https://quasar.dev/quasar-cli-vite/developing-ssr/seo-for-ssr/). SPA builds inject them at runtime (not ideal for crawlers). Quasar's ecosystem is smaller. You'll implement sitemaps, structured data, and OG images manually. No equivalent to Nuxt's integrated SEO modules. Quasar simplifies SSR setup compared to custom Vite SSR but lacks Nuxt's SEO-first tooling. h2. [Ecosystem & Community](#ecosystem-community) h3. [Nuxt](#nuxt-1) [**Nuxt has 59,097 GitHub stars**](https://github.com/nuxt/nuxt) and 1 million weekly npm downloads. The ecosystem is large: - **Modules:** 250+ official and community modules at [**nuxt.com/modules**](https://nuxt.com/modules) - **Content:** [**@nuxt/content**](https://content.nuxt.com) for markdown/JSON content - **UI:** [**@nuxt/ui**](https://ui.nuxt.com), [**Nuxt UI Pro**](https://ui.nuxt.com/pro), PrimeVue, Vuetify integrations - **Deployment:** First-class support on Vercel, Netlify, Cloudflare, AWS - **SEO:** [**@nuxtjs/seo**](https://nuxtseo.com) handles technical SEO automatically Core team is full-time. [**Nuxt 4**](https://nuxt.com/blog/v4) (December 2025) brings improved performance, better TypeScript DX, and enhanced data fetching. h3. [Quasar](#quasar-1) [**Quasar has 26,900 GitHub stars**](https://github.com/quasarframework/quasar) and 161,765 weekly downloads. Smaller but active community. - **UI:** [**90+ built-in components**](https://quasar.dev/vue-components) (buttons, forms, tables, modals) - **Platforms:** Cordova, Capacitor, Electron support out of box - **Deployment:** Web deployment straightforward; mobile requires native toolchains - **Extensions:** Community plugins at [**awesome-quasar**](https://github.com/quasarframework/quasar-awesome) Quasar's strength is the complete UI library and cross-platform tooling. You get Material Design components, responsive utilities, and platform APIs without extra dependencies. For web-only projects, Nuxt's ecosystem is richer. For multi-platform, Quasar eliminates complexity. h2. [When to Choose Nuxt](#when-to-choose-nuxt) Use Nuxt if: - **SEO is priority #1** . You're building a blog, marketing site, e-commerce, or content platform - **You want convention over configuration** . Nuxt's file-based routing, auto-imports, and sensible defaults reduce boilerplate - **SSR/SSG is the default mode** . Most pages benefit from server rendering - **You need mature SEO tooling** . Sitemaps, OG images, structured data handled automatically - **Content is king** . Integrates with @nuxt/content for markdown-based sites - **You're targeting web only** . No need for mobile/desktop apps Nuxt is [**tailored for content-driven applications**](https://medium.com/@kaushalsinh73/quasar-vs-nuxt-picking-the-right-vue-framework-in-2025-440132d4ab4a) with fast loading and high SEO standards. h2. [When to Choose Quasar](#when-to-choose-quasar) Use Quasar if: - **You're shipping to multiple platforms** . iOS, Android, desktop (Electron), browser extensions from one codebase - **You want a complete UI library** . 90+ components, Material Design, no CSS framework decisions - **SSR is one of many targets** . You need SSR for web but also native mobile apps - **You prefer explicit configuration** . More control over build and deployment - **PWA support is critical** . [**PWA takeover after SSR**](https://quasar.dev/quasar-cli-vite/developing-ssr/introduction) unique to Quasar - **You're building an app, not a content site** . Dashboards, tools, SAAS products Quasar maximizes code reuse across platforms. If you're building for web _and_ mobile, Quasar eliminates maintaining separate codebases. h2. [Performance Considerations](#performance-considerations) Both frameworks support SSR, but implementation differs. **Nuxt:** - SSR optimized by default with automatic code splitting - [**Nitro server**](https://nitro.unjs.io) is fast and deploys to 15+ platforms - Vite-powered dev server with instant HMR - Prerendering during build (SSG) for static routes **Quasar:** - SSR requires more manual optimization - PWA mode improves perceived performance after initial load - [**SSR prefetching**](https://quasar.dev/quasar-cli-vite/developing-ssr/introduction) for data-heavy pages - Built-in performance directives (lazy loading, intersection observer) For content sites, [**Nuxt's SSR is faster to set up and deploy**](https://simply-how.com/server-side-rendering-web-frameworks). Quasar's advantage is runtime performance on mobile through native compilation. h2. [Migration Path](#migration-path) h3. [From SPA to Nuxt](#from-spa-to-nuxt) Nuxt provides [**migration guide from Vue SPA**](https://nuxt.com/docs/migration/overview). Key steps: 1. Install Nuxt 2. Move components to `components/` directory (auto-imported) 3. Convert routes to file-based routing in `pages/` 4. Update meta tags to use `useSeoMeta` 5. Add `nuxt.config.ts` for configuration Most Vue 3 code works unchanged. Nuxt adds conventions, doesn't remove Vue features. h3. [From SPA to Quasar](#from-spa-to-quasar) Quasar requires more restructuring. Steps: 1. Create Quasar project with `quasar create` 2. Copy components, add Quasar component imports 3. Update styling to use Quasar's utility classes 4. Configure SSR mode 5. Handle SSR-specific code paths (client vs server) Quasar's boot files and configuration format differ from standard Vite/Vue. h2. [Real-World Usage](#real-world-usage) **Nuxt sites:** - [**GitLab**](https://gitlab.com) - [**Upwork**](https://upwork.com) - [**Netlify**](https://netlify.com) - [**JetBrains**](https://jetbrains.com) documentation **Quasar sites:** - [**Laravel Spark**](https://spark.laravel.com) (dashboard) - [**Frappe**](https://frappe.io) (ERP system) - Cross-platform mobile apps (e.g., enterprise tools, dashboards) Nuxt dominates public-facing content sites. Quasar powers internal tools and multi-platform apps. h2. [Developer Experience](#developer-experience) h3. [Nuxt DX](#nuxt-dx) - **File-based routing** . Create `pages/blog/[slug].vue`, route exists - **Auto-imports** . Components, composables, utilities available without imports - **TypeScript** . Full type safety, auto-generated types for routes/modules - **DevTools** . [**Nuxt DevTools**](https://devtools.nuxt.com) for debugging, performance, SEO - **Error handling** . Detailed error pages in dev, customizable in production Nuxt reduces boilerplate. You write Vue components, Nuxt handles routing, imports, and SSR setup. h3. [Quasar DX](#quasar-dx) - **CLI-driven** . Generate components, pages, boot files with commands - **UI component explorer** . Browse all 90+ components with live examples - **Platform switching** . Run `quasar dev -m capacitor -T ios` to test on iOS - **Flexbox grid** . Powerful responsive layout system - **Icon sets** . Material Icons, Font Awesome, etc. included Quasar provides more upfront. UI library, layout system, and multi-platform tooling included. More to learn, more to configure. h2. [SEO Testing](#seo-testing) Both frameworks require verification that crawlers see content. **Nuxt verification:** ``` h1. Check server-rendered HTML curl https://yoursite.com | grep "your content" h1. Use Google Search Console URL Inspection h1. View rendered HTML and screenshot ``` Nuxt's SSR default means content is usually visible. Watch for client-only components breaking SSR: ``` <ClientOnly> <!-- This won't be in SSR HTML --> </ClientOnly> ``` **Quasar verification:** ``` h1. Ensure SSR mode is active curl https://yoursite.com | grep "your content" h1. Meta plugin works in SSR only h1. SPA mode injects meta at runtime (too late for crawlers) ``` In Quasar, confirm you're running SSR build, not SPA. [**SPA builds inject meta tags too late**](https://quasar.dev/quasar-plugins/meta/) for crawlers. h2. [Common Mistakes](#common-mistakes) h3. [Nuxt](#nuxt-2) **Mistake 1: Blocking SSR for entire app** Don't disable SSR globally unless necessary. Use route rules for client-only sections: ``` // ❌ Bad: Disables SSR everywhere export default defineNuxtConfig({ ssr: false }) // ✅ Good: Disable SSR per route export default defineNuxtConfig({ routeRules: { '/dashboard/**': { ssr: false } } }) ``` **Mistake 2: Not prerendering static pages** Marketing pages, blogs, docs should be static: ``` export default defineNuxtConfig({ routeRules: { '/': { prerender: true }, '/about': { prerender: true }, '/blog/**': { isr: 3600 } // ISR for frequently updated content } }) ``` h3. [Quasar](#quasar-2) **Mistake 1: Running SPA mode expecting SEO**[**SSR must be enabled**](https://quasar.dev/quasar-cli-vite/developing-ssr/seo-for-ssr/) for search engines. SPA mode injects meta at runtime. **Mistake 2: Not testing SSR build** Dev mode differs from SSR production. Always test SSR build: ``` quasar build -m ssr quasar serve dist/ssr ``` Then verify with `curl` or Google Search Console. h2. [Deployment](#deployment) h3. [Nuxt](#nuxt-3) Nuxt's [**Nitro server**](https://nitro.unjs.io) deploys to 15+ platforms with zero config: ``` h1. Netlify npx nuxi build --preset netlify h1. Vercel npx nuxi build --preset vercel h1. Cloudflare Pages npx nuxi build --preset cloudflare-pages ``` Many hosts detect Nuxt automatically. No deployment config needed. h3. [Quasar](#quasar-3) SSR deployment requires Node.js server: ``` h1. Build SSR quasar build -m ssr h1. Deploy dist/ssr folder to Node.js host h1. Run with pm2, systemd, or container node dist/ssr/index.js ``` Quasar SSR runs on traditional Node servers. Less automated than Nuxt's Nitro. For mobile/desktop, Quasar handles platform builds: ``` h1. Build iOS app quasar build -m capacitor -T ios h1. Build Electron app quasar build -m electron ``` h2. [Cost Considerations](#cost-considerations) **Nuxt:** - SSG routes → Free (deploy to CDN) - SSR routes → Serverless functions or Node server - Edge rendering → Premium on some hosts **Quasar:** - SSR web → Node server (VPS, containerized) - Mobile apps → App store fees ($99/year Apple, one-time $25 Google) - Electron apps → Free to distribute For content sites, Nuxt's SSG reduces hosting costs. For apps, Quasar's cross-platform support reduces development cost. h2. [Verdict](#verdict) **Choose Nuxt if:** - Building a content site (blog, docs, marketing, e-commerce) - SEO is non-negotiable - Targeting web only - Want conventions and ecosystem **Choose Quasar if:** - Shipping to web + mobile + desktop - Need complete UI library out of box - Building app, not content - Prioritize cross-platform code reuse Both are excellent Vue frameworks. [**Nuxt dominates for SSR-first content**](https://medium.com/@kaushalsinh73/quasar-vs-nuxt-picking-the-right-vue-framework-in-2025-440132d4ab4a). Quasar excels at cross-platform apps. For SEO, Nuxt wins. The ecosystem, defaults, and tooling are built for discoverability. --- [**SSR Frameworks** Compare Vue server-side rendering frameworks. Choose between Nuxt, Quasar, Vite SSR, and VitePress for better SEO and performance.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar/learn-seo/vue/ssr-frameworks) [**Custom Vite SSR** Building server-side rendering with Vite instead of frameworks. When custom SSR makes sense and SEO considerations.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar/learn-seo/vue/ssr-frameworks/vite-ssr) **On this page** - [Quick Comparison](#quick-comparison) - [SSR Capabilities](#ssr-capabilities) - [SEO Features](#seo-features) - [Ecosystem & Community](#ecosystem-community) - [When to Choose Nuxt](#when-to-choose-nuxt) - [When to Choose Quasar](#when-to-choose-quasar) - [Performance Considerations](#performance-considerations) - [Migration Path](#migration-path) - [Real-World Usage](#real-world-usage) - [Developer Experience](#developer-experience) - [SEO Testing](#seo-testing) - [Common Mistakes](#common-mistakes) - [Deployment](#deployment) - [Cost Considerations](#cost-considerations) - [Verdict](#verdict) --- ### Hydration Mismatches and SEO in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/spa/hydration Description: How hydration failures cause Google to index broken versions of your site. Debug mismatches, fix common causes, optimize with partial hydration. h1. **Hydration Mismatches and SEO in Vue** How hydration failures cause Google to index broken versions of your site. Debug mismatches, fix common causes, optimize with partial hydration. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** **What you'll learn** - Hydration mismatches cause Google to index broken versions of your site - Server-rendered HTML and client expectations must match exactly - Use `data-allow-mismatch` sparingly for unavoidable differences like timestamps Hydration failures are the silent killer of modern SEO. Google indexes a broken version of your site that humans never see. Your site works perfectly in the browser. But Google's crawler hits it during hydration, freezes mid-process, and indexes the half-built DOM. Rankings tank and you never know why. h2. [What is Hydration](#what-is-hydration) Server sends HTML. JavaScript makes it interactive. That process is hydration. ``` // Server renders this HTML <div id="app"> <h1>Products</h1> <ul> <li>Product 1</li> <li>Product 2</li> </ul> </div> ``` ``` // Client hydrates: Vue takes over, attaches listeners const app = createSSRApp(App) app.mount('#app') ``` Vue creates the same app that ran on the server, matches components to DOM nodes, attaches event listeners. If server HTML and client expectations differ, you get a [**hydration mismatch**](https://vuejs.org/guide/scaling-up/ssr.html). h2. [Why Hydration Mismatches Break SEO](#why-hydration-mismatches-break-seo) Human browsers and Googlebot don't execute JavaScript the same way. Real users get persistent execution, generous timeouts, GPU acceleration. Googlebot gets throttled execution, aggressive API cancellation, hard rendering cutoffs ([**The Better Web Movement**](https://thebetterwebmovement.com/javascript-seo-hydration-islands-and-crawling/)). When hydration fails or stalls, the browser freezes the DOM in a half-built state. Humans never see this. JavaScript eventually recovers. Search engines index the broken version ([**NRLC**](https://nrlc.ai/en-us/insights/silent-hydration-seo/)). **The fatal pattern:** 1. Google requests your page 2. Server sends complete HTML (good) 3. JavaScript starts hydrating 4. Hydration hits mismatch, throws error 5. Vue attempts recovery, discards nodes, remounts 6. Googlebot times out mid-recovery 7. Google indexes the broken intermediate state You lose rankings because of content Google never shows in Search Console's rendered HTML view. h2. [Common Causes of Hydration Mismatches](#common-causes-of-hydration-mismatches) h3. [Browser APIs During SSR](#browser-apis-during-ssr) ``` <script setup lang="ts"> import { onMounted, ref } from 'vue' // Breaks hydration - window doesn't exist on server const width = window.innerWidth const theme = localStorage.getItem('theme') </script> ``` ``` <script setup lang="ts"> import { onMounted, ref } from 'vue' // Only runs on client const width = ref(0) const theme = ref('light') onMounted(() => { width.value = window.innerWidth theme.value = localStorage.getItem('theme') || 'light' }) </script> ``` `window`, `document`, `localStorage` don't exist in Node.js. Use `onMounted()`. it only runs client-side. h3. [Inconsistent Data Between Server and Client](#inconsistent-data-between-server-and-client) ``` <script setup lang="ts"> // ❌ Server and client generate different timestamps </script> <script setup lang="ts"> import { useAsyncData } from '#app' const timestamp = Date.now() // ✅ Fixed - fetch once, use everywhere const { data: timestamp } = await useAsyncData('timestamp', () => Promise.resolve(Date.now())) </script> <template> <div>{{ timestamp }}</div> </template> ``` Server executes at build time. Client executes when user visits. `Date.now()`, `Math.random()`, API calls that return different data. all cause mismatches. h3. [Third-Party Scripts](#third-party-scripts) ``` // ❌ Analytics injects content during hydration <script> gtag('config', 'GA_ID') </script> // ✅ Fixed - load after hydration <script setup lang="ts"> import { onMounted } from 'vue' onMounted(() => { const script = document.createElement('script') script.src = 'https://www.googletagmanager.com/gtag/js?id=GA_ID' script.async = true document.head.appendChild(script) }) </script> ``` Analytics, chat widgets, ad scripts. anything that modifies DOM before hydration completes will cause mismatches. Load them after `onMounted()`. h3. [Invalid HTML Structure](#invalid-html-structure) ``` // ❌ Browser auto-corrects invalid HTML <template> <table> <div>Invalid - div inside table</div> </table> </template> // Server sends: <table><div>...</div></table> // Browser corrects to: <div>...</div><table></table> // Hydration fails ``` Browsers silently fix invalid HTML. Server sends one structure, browser corrects it, Vue expects the server version. mismatch ([**Vue.js docs**](https://vuejs.org/guide/scaling-up/ssr.html)). h2. [Debugging Hydration Mismatches](#debugging-hydration-mismatches) Vue logs mismatches to console in development: ``` [Vue warn]: Hydration node mismatch: - Client vnode: div - Server rendered DOM: span ``` **Check what Googlebot sees:** 1. Google Search Console → URL Inspection 2. Enter your URL → Test Live URL 3. View rendered HTML Compare to "View Page Source" in your browser. If they differ, you have a hydration problem. **Test with curl:** ``` h1. See what Google gets initially curl -A "Mozilla/5.0 (compatible; Googlebot/2.1)" https://yoursite.com h1. Should match browser's View Page Source ``` **Vue 3.5+ selective suppression:** ``` <template> <!-- Suppress inevitable mismatches --> <div data-allow-mismatch="text"> {{ timestamp }} </div> </template> ``` Use sparingly. Suppression doesn't fix the underlying issue. Google still sees mismatched content. h2. [Performance Impact on SEO](#performance-impact-on-seo) Hydration affects all Core Web Vitals ([**Antoine Eripret**](https://www.aeripret.com/content-rehydration/)): - **LCP** - Slow hydration delays largest contentful paint - **INP** - Heavy hydration blocks interaction - **CLS** - Mismatches cause layout shifts Target: LCP under 2.5s, INP ≤ 200ms, CLS < 0.1. Hydration on big sites with deeply nested HTML significantly increases rendering time. Every component needs matching, every event listener needs attaching. h2. [Partial Hydration](#partial-hydration) Hydrate only interactive components. Static content stays static ([**Markus Oberlehner**](https://markus.oberlehner.net/blog/partial-hydration-concepts-lazy-and-active/)). ``` // ❌ Hydrates everything <template> <article> <h1>Blog Post</h1> <p>Long static content...</p> <CommentSection /> </article> </template> // ✅ Only hydrate comments <template> <article> <h1>Blog Post</h1> <p>Long static content...</p> <ClientOnly> <CommentSection /> </ClientOnly> </article> </template> ``` **Lazy hydration** - defer until needed: ``` import { defineAsyncComponent } from 'vue' // Hydrate when visible in viewport const CommentSection = defineAsyncComponent(() => import('./CommentSection.vue') ) ``` **Tools for partial hydration:** - **[**vue-lazy-hydration**](https://github.com/maoberlehner/vue-lazy-hydration)** - Hydrate on visibility, interaction, or idle - **[**îles**](https://iles.pages.dev/)** - Static site generator with partial hydration built-in - **Nuxt Islands** - `<NuxtIsland>` prevents hydration Partial hydration can cut Time to Interactive by 50%+ on content-heavy sites ([**LogRocket**](https://blog.logrocket.com/vue-3-lazy-hydration-from-scratch/)). h2. [Hydration Best Practices](#hydration-best-practices) **Keep state consistent:** ``` // ❌ Different server vs client const isMobile = window.innerWidth < 768 // ✅ Same everywhere const isMobile = computed(() => { if (import.meta.client) { return window.innerWidth < 768 } return false // default for SSR }) ``` **Fetch data properly:** ``` // ❌ Client-only fetch causes empty server HTML onMounted(async () => { const data = await fetch('/api/products') }) // ✅ Fetch server-side, hydrate with data const { data } = await useAsyncData('products', () => $fetch('/api/products')) ``` **Avoid browser-specific logic:** ``` // ❌ Server crashes - navigator undefined const userAgent = navigator.userAgent // ✅ Check environment const userAgent = import.meta.client ? navigator.userAgent : '' ``` **Load third-party scripts after hydration:** Defer analytics, ads, chat widgets to `onMounted()`. They don't need to hydrate. they inject after. h2. [When Hydration Doesn't Matter](#when-hydration-doesnt-matter) Not every app needs perfect hydration for SEO. **Skip hydration worries for:** - Internal dashboards - Apps behind authentication - Admin panels - Content you don't want indexed If Google doesn't need to see it, hydration mismatches don't hurt SEO. h2. [Using Nuxt?](#using-nuxt) Nuxt provides hydration debugging tools and automatic detection. Check out [**Nuxt Delay Hydration**](https://github.com/harlan-zw/nuxt-delay-hydration) for optimized hydration with minimal setup. Nuxt also offers `<NuxtIsland>` for partial hydration and detailed hydration error messages. [**Learn more in Nuxt →**](https://nuxtseo.com/learn-seo/vue/spa/hydration/learn-seo/nuxt/routes-and-rendering/rendering) --- [**Dynamic Rendering** Dynamic rendering serves pre-rendered HTML to crawlers while users see JavaScript. Google no longer recommends this. use SSR or SSG instead.](https://nuxtseo.com/learn-seo/vue/spa/hydration/learn-seo/vue/spa/dynamic-rendering) [**Routes & Rendering** URL structure and rendering mode determine whether search engines can crawl, index, and rank your Vue application.](https://nuxtseo.com/learn-seo/vue/spa/hydration/learn-seo/vue/routes-and-rendering) **On this page** - [What is Hydration](#what-is-hydration) - [Why Hydration Mismatches Break SEO](#why-hydration-mismatches-break-seo) - [Common Causes of Hydration Mismatches](#common-causes-of-hydration-mismatches) - [Debugging Hydration Mismatches](#debugging-hydration-mismatches) - [Performance Impact on SEO](#performance-impact-on-seo) - [Partial Hydration](#partial-hydration) - [Hydration Best Practices](#hydration-best-practices) - [When Hydration Doesn't Matter](#when-hydration-doesnt-matter) - [Using Nuxt?](#using-nuxt) --- ### Custom Vite SSR for Vue: When to Roll Your Own · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr Description: Building server-side rendering with Vite instead of frameworks. When custom SSR makes sense and SEO considerations. h1. **Custom Vite SSR for Vue: When to Roll Your Own** Building server-side rendering with Vite instead of frameworks. When custom SSR makes sense and SEO considerations. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw) Published **Dec 17, 2025** **What you'll learn** - Custom Vite SSR gives full control but requires building routing, data fetching, and SEO tooling yourself - Most apps should use Nuxt. custom SSR is for specific use cases (microservices, legacy integration) - Remember: meta tags must render server-side, and hydration mismatches break SEO Most Vue apps don't need custom SSR. Nuxt handles it better. But if you're building a highly specific app where framework conventions create more friction than value, Vite's SSR primitives let you build exactly what you need. h2. [When Custom Vite SSR Makes Sense](#when-custom-vite-ssr-makes-sense) [**Vite provides built-in SSR support**](https://vite.dev/guide/ssr) as a low-level API "meant for library and framework authors." Use it when: - You're building a framework or library yourself - Framework conventions conflict with your architecture (microservices, multi-tenant apps, legacy integration) - You need full control over the SSR pipeline for performance optimization - Your app is small enough that framework overhead isn't worth it Don't use it for typical marketing sites, blogs, or e-commerce. Nuxt gives you SSR plus routing, data fetching, and SEO tooling. Custom Vite SSR gives you none of that. you build it all. [**The Vue.js docs are explicit**](https://vuejs.org/guide/scaling-up/ssr.html): "If you prefer a higher-level solution that provides a smooth out-of-the-box experience, you should probably give Nuxt.js a try." h2. [Basic Vite SSR Setup](#basic-vite-ssr-setup) Vite SSR requires three entry points: ``` ├── index.html ├── server.js # Node server ├── src/ │ ├── main.ts # Shared app factory │ ├── entry-client.ts # Client hydration │ └── entry-server.ts # SSR rendering ``` h3. [Shared App Factory](#shared-app-factory) ``` // src/main.ts import { createSSRApp } from 'vue' import App from './App.vue' export function createApp() { const app = createSSRApp(App) return { app } } ``` Use `createSSRApp()` instead of `createApp()` for SSR compatibility. This configures Vue for server rendering and client hydration. h3. [Server Entry](#server-entry) ``` // src/entry-server.ts import { renderToString } from 'vue/server-renderer' import { createApp } from './main' export async function render(url: string) { const { app } = createApp() const html = await renderToString(app) return { html } } ``` [`renderToString()`](https://vuejs.org/guide/scaling-up/ssr.html) generates HTML from your Vue app. Google receives this HTML immediately. no JavaScript execution required. h3. [Client Entry](#client-entry) ``` // src/entry-client.ts import { createApp } from './main' const { app } = createApp() app.mount('#app') ``` Hydrates the server-rendered HTML. The DOM structure must match exactly or you'll get hydration errors (which break SEO by causing layout shifts). h3. [Server Setup](#server-setup) ``` // server.js import express from 'express' import { createServer as createViteServer } from 'vite' const app = express() const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'custom' }) app.use(vite.middlewares) app.get('*', async (req, res) => { const { render } = await vite.ssrLoadModule('/src/entry-server.ts') const { html } = await render(req.url) res.send(\` <!DOCTYPE html> <html> <head> <title>My App
${html}
\`) }) app.listen(3000) ``` ``` // server.ts import { Hono } from 'hono' import { createServer as createViteServer } from 'vite' const app = new Hono() const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'custom' }) app.use('*', async (c) => { const { render } = await vite.ssrLoadModule('/src/entry-server.ts') const { html } = await render(c.req.url) return c.html(\` My App
${html}
\`) }) export default app ``` `middlewareMode: true` lets your server handle routing. Vite only handles module transformation and HMR. h2. [SEO Considerations for Custom SSR](#seo-considerations-for-custom-ssr) h3. [1. Meta Tags Must Render Server-Side](#_1-meta-tags-must-render-server-side) Default Vue apps render meta tags client-side. Google might not wait for JavaScript. Install [**Unhead**](https://unhead.unjs.io/): ``` npm install @unhead/vue ``` Setup requires server context: ``` import { renderSSRHead } from '@unhead/ssr' // src/entry-server.ts import { renderToString } from 'vue/server-renderer' import { createApp } from './main' export async function render(url: string) { const { app, head } = createApp() const html = await renderToString(app) const { headTags } = await renderSSRHead(head) return { html, headTags } } ``` ``` import { createHead } from '@unhead/vue' // src/main.ts import { createSSRApp } from 'vue' import App from './App.vue' export function createApp() { const app = createSSRApp(App) const head = createHead() app.use(head) return { app, head } } ``` Now meta tags render in the initial HTML: ``` ``` h3. [2. Routing Requires Manual Setup](#_2-routing-requires-manual-setup) Vite doesn't include routing. Add Vue Router yourself: ``` // src/router.ts import { createMemoryHistory, createRouter, createWebHistory } from 'vue-router' export function createAppRouter(isServer: boolean) { return createRouter({ history: isServer ? createMemoryHistory() : createWebHistory(), routes: [ { path: '/', component: () => import('./pages/Home.vue') }, { path: '/about', component: () => import('./pages/About.vue') } ] }) } ``` Server uses `createMemoryHistory()` (no URL manipulation). Client uses `createWebHistory()` (browser history API). Update app factory: ``` import { createHead } from '@unhead/vue' // src/main.ts import { createSSRApp } from 'vue' import App from './App.vue' import { createAppRouter } from './router' export function createApp(isServer = false) { const app = createSSRApp(App) const head = createHead() const router = createAppRouter(isServer) app.use(head) app.use(router) return { app, head, router } } ``` Server entry must wait for routing: ``` import { renderSSRHead } from '@unhead/ssr' // src/entry-server.ts import { renderToString } from 'vue/server-renderer' import { createApp } from './main' export async function render(url: string) { const { app, head, router } = createApp(true) await router.push(url) await router.isReady() const html = await renderToString(app) const { headTags } = await renderSSRHead(head) return { html, headTags } } ``` `router.isReady()` waits for async components to load. Without this, Google sees empty content. h3. [3. Data Fetching Needs Coordination](#_3-data-fetching-needs-coordination) No built-in data fetching like Nuxt's `useFetch()`. Options: **Option A: Route-level data loading** src/pages/Blog.vue ``` ``` `onServerPrefetch()` runs during SSR, not on client. You need to fetch again client-side or serialize state. **Option B: State serialization** ``` // src/entry-server.ts export async function render(url: string) { const { app, head, router } = createApp(true) await router.push(url) await router.isReady() // Data loaded during SSR is available here const state = { /* extract state */ } const html = await renderToString(app) const { headTags } = await renderSSRHead(head) return { html, headTags, state } } ``` ``` ``` Client rehydrates from `window.__INITIAL_STATE__`. This prevents duplicate fetches but requires manual state management. [**Nuxt handles this automatically**](https://nuxt.com/docs/getting-started/data-fetching). Custom Vite SSR doesn't. h2. [Using Vite SSR Plugins](#using-vite-ssr-plugins) Writing custom SSR is tedious. Plugins reduce boilerplate. h3. [Vike](#vike) [**Vike**](https://vike.dev/) (formerly vite-plugin-ssr) is "like Next.js/Nuxt but as do-one-thing-do-it-well Vite plugin." ``` npm install vike vike-vue ``` ``` // vite.config.ts import vue from '@vitejs/plugin-vue' import { defineConfig } from 'vite' import vike from 'vike/plugin' export default defineConfig({ plugins: [vue(), vike()] }) ``` File-based routing with `+Page.vue` convention: ``` pages/ index/+Page.vue # / about/+Page.vue # /about blog/ index/+Page.vue # /blog @slug/+Page.vue # /blog/:slug ``` Data fetching per page: ``` // blog/@slug/+data.ts export async function data(pageContext) { const { slug } = pageContext.routeParams const res = await fetch(\`https://api.example.com/posts/${slug}\`) return { post: await res.json() } } ``` Server-only code. Never sent to client. Better for SEO (less JavaScript). h3. [vite-ssr (frandiox)](#vite-ssr-frandiox) [**vite-ssr**](https://github.com/frandiox/vite-ssr) integrates with Vue Router and state management: ``` npm install vite-ssr ``` ``` // src/main.ts import { viteSSR } from 'vite-ssr/vue' import App from './App.vue' import routes from './routes' export default viteSSR(App, { routes }, ({ app, router, initialState }) => { // Setup runs once on server, once on client // initialState syncs automatically }) ``` Handles state serialization and hydration. Supports Pinia out of the box. Deploy to serverless (Vercel, Netlify) or edge workers (Cloudflare). h2. [Production Build](#production-build) Development uses middleware mode. Production needs separate client and server builds. ``` { "scripts": { "dev": "node server.js", "build": "npm run build:client && npm run build:server", "build:client": "vite build --outDir dist/client", "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server" } } ``` ``` import vue from '@vitejs/plugin-vue' // vite.config.ts import { defineConfig } from 'vite' export default defineConfig({ plugins: [vue()], build: { rollupOptions: { input: { main: './index.html' } } } }) ``` Production server imports from `server`: ``` // server.prod.js import express from 'express' import { render } from './server/entry-server.js' const app = express() app.use(express.static('dist/client')) app.get('*', async (req, res) => { const { html, headTags } = await render(req.url) res.send(\` ${headTags}
${html}
\`) }) app.listen(3000) ``` h2. [Trade-offs vs Frameworks](#trade-offs-vs-frameworks) **Custom Vite SSR gives you:** - Full architectural control - Minimal bundle size (no framework overhead) - Custom rendering pipeline (streaming, partial hydration) - Integration flexibility **You lose:** - File-based routing (manual setup) - Data fetching utilities (manual state management) - SEO tooling (no automatic sitemaps, meta management, schema.org) - Deployment presets (manual server configuration) - Developer experience (no conventions) [**The Vue SSR guide warns**](https://vuejs.org/guide/scaling-up/ssr.html): "More involved build setup and deployment requirements. Unlike a fully static SPA that can be deployed on any static file server, a server-rendered app requires an environment where a Node.js server can run." If you're building a typical web app, use Nuxt. If you need SSR for a specific use case (embedded widgets, multi-tenant platforms, legacy integration), Vite's primitives let you build exactly what you need. h2. [Common Mistakes](#common-mistakes) **Using lifecycle hooks incorrectly** `onMounted()` never runs on server. `onServerPrefetch()` never runs on client. Use the right hook for each environment. **Hydration mismatches** Server HTML must match client exactly. Random IDs, timestamps, or client-only rendering breaks hydration: ``` ❌ Bad ✅ Good ``` **Accessing browser APIs during SSR** `window`, `document`, `localStorage` don't exist on server: ``` // ❌ Bad const saved = localStorage.getItem('theme') // ✅ Good const saved = import.meta.env.SSR ? null : localStorage.getItem('theme') ``` Or use `onMounted()` which only runs client-side. **Not testing the production build** Vite's dev server behaves differently than production. Always test `npm run build && node server.prod.js` before deploying. h2. [Verification](#verification) Test SSR output before deploying: ``` curl http://localhost:3000/blog/my-post ``` Should return full HTML with content. If you see `
` with no content, SSR isn't working. Use [**Google's URL Inspection tool**](https://search.google.com/search-console) to verify Googlebot sees rendered content. h2. [Using Nuxt?](#using-nuxt) Nuxt handles all of this automatically with file-based routing, built-in data fetching, and SEO utilities. Check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr/docs/nuxt-seo/getting-started/introduction) for production-ready SEO tooling. [**Learn more about Nuxt →**](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr/learn-seo/nuxt/routes-and-rendering/rendering) --- [**Nuxt vs Quasar** Nuxt dominates for SSR-first SEO. Quasar excels at cross-platform. Compare SSR capabilities, SEO features, and when to choose each.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar) [**VitePress SEO** VitePress offers built-in SEO features like sitemap generation, meta tags, and fast static site generation for documentation and blogs.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vite-ssr/learn-seo/vue/ssr-frameworks/vitepress) **On this page** - [When Custom Vite SSR Makes Sense](#when-custom-vite-ssr-makes-sense) - [Basic Vite SSR Setup](#basic-vite-ssr-setup) - [SEO Considerations for Custom SSR](#seo-considerations-for-custom-ssr) - [Using Vite SSR Plugins](#using-vite-ssr-plugins) - [Production Build](#production-build) - [Trade-offs vs Frameworks](#trade-offs-vs-frameworks) - [Common Mistakes](#common-mistakes) - [Verification](#verification) - [Using Nuxt?](#using-nuxt) --- ### VitePress SEO Guide · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vitepress Description: VitePress offers built-in SEO features like sitemap generation, meta tags, and fast static site generation for documentation and blogs. h1. **VitePress SEO Guide** VitePress offers built-in SEO features like sitemap generation, meta tags, and fast static site generation for documentation and blogs. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)7 mins read Published **Dec 17, 2025** **What you'll learn** - VitePress is ideal for documentation and blogs. fast static generation with built-in search - Native sitemap generation with `sitemap.hostname` config option - Static-only (no SSR). use Nuxt Content for dynamic features or authentication VitePress is a static site generator built on Vite and Vue 3, designed for documentation sites and content-heavy blogs. The initial visit serves pre-rendered HTML for [**fast loading and optimal SEO**](https://vitepress.dev/guide/what-is-vitepress), while subsequent navigation behaves like an SPA. h2. [When to Use VitePress](#when-to-use-vitepress) VitePress beats Nuxt Content when: - Building technical documentation with minimal setup - Speed and simplicity matter more than flexibility - Content is 100% static (no authentication, no dynamic server features) - You want [**built-in search and code highlighting**](https://vitepress.dev/guide/what-is-vitepress) without configuration VitePress has [**395,965 weekly npm downloads**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/) vs Nuxt Content's 74,690. Choose Nuxt Content when you need SSR, complex routing with non-markdown pages, or deeper integration with Nuxt modules. h2. [SEO Features](#seo-features) h3. [Automatic Static HTML](#automatic-static-html) VitePress pre-renders pages at build time, generating static HTML files. This approach [**delivers excellent SEO while maintaining SPA benefits**](https://soubiran.dev/series/create-a-blog-with-vitepress-and-vue-js-from-scratch/enhance-website-visibility-seo-metadata-and-sitemap) for user experience. On PageSpeed Insights, VitePress sites achieve [**near-perfect performance scores**](https://vitepress.dev/guide/what-is-vitepress) even on low-end mobile devices. Google's Core Web Vitals directly impact rankings. h3. [Built-in Sitemap Generation](#built-in-sitemap-generation) VitePress includes [**native sitemap support**](https://vitepress.dev/guide/sitemap-generation). Enable it in `.vitepress/config.ts`: .vitepress/config.ts ``` export default { sitemap: { hostname: 'https://mysite.com' } } ``` This generates `sitemap.xml` in `.vitepress/dist` during build. For `` tags, enable the `lastUpdated` option: .vitepress/config.ts ``` export default { sitemap: { hostname: 'https://mysite.com' }, lastUpdated: true } ``` VitePress uses the [**sitemap module**](https://vitepress.dev/guide/sitemap-generation) under the hood. Pass any SitemapStream options directly to the `sitemap` config. h3. [Custom Sitemap Items](#custom-sitemap-items) Use `transformItems` to [**modify sitemap entries before writing**](https://vitepress.dev/guide/sitemap-generation): .vitepress/config.ts ``` export default { sitemap: { hostname: 'https://mysite.com', transformItems: (items) => { // Filter out draft pages return items.filter(item => !item.url.includes('/drafts/')) } } } ``` h3. [Alternative: Build Hooks](#alternative-build-hooks) For advanced use cases, use [**VitePress build hooks**](https://dev.to/paullaros/generating-a-dynamic-sitemap-with-vitepress-ldd) like `buildEnd` to generate custom sitemaps: .vitepress/config.ts ``` import { writeFileSync } from 'node:fs' import { SitemapStream, streamToPromise } from 'sitemap' export default { async buildEnd(siteConfig) { const sitemap = new SitemapStream({ hostname: 'https://mysite.com' }) const pages = await generatePageList(siteConfig) pages.forEach(page => sitemap.write(page)) sitemap.end() const data = await streamToPromise(sitemap) writeFileSync('.vitepress/dist/sitemap.xml', data.toString()) } } ``` h2. [Meta Tags Configuration](#meta-tags-configuration) h3. [Site-Level Meta Tags](#site-level-meta-tags) Add meta tags globally using the [`head`** config**](https://vitepress.dev/reference/site-config): .vitepress/config.ts ``` export default { head: [ ['meta', { name: 'description', content: 'My documentation site' }], ['meta', { property: 'og:type', content: 'website' }], ['meta', { property: 'og:image', content: 'https://mysite.com/og.png' }], ['link', { rel: 'icon', href: '/favicon.ico' }] ] } ``` User-added tags render before the closing `` tag, [**after VitePress's built-in tags**](https://vitepress.dev/reference/site-config). h3. [Page-Level Meta Tags](#page-level-meta-tags) Override meta tags per page using [**frontmatter**](https://vitepress.dev/reference/frontmatter-config): ``` --- head: - - meta - name: description content: Custom description for this page - - meta - property: og:title content: Custom OG Title - - meta - name: keywords content: vitepress, seo, vue --- ``` Frontmatter meta tags [**append after site-level tags**](https://vitepress.dev/reference/frontmatter-config). Note that setting OG tags globally and overriding per-page can [**cause duplicate tags in HTML output**](https://github.com/vuejs/vitepress/issues/975). h3. [Dynamic Meta Tags](#dynamic-meta-tags) For dynamic meta tags, use [**transform hooks**](https://laros.io/adding-dynamic-meta-tags-to-vitepress): .vitepress/config.ts ``` export default { transformPageData(pageData) { pageData.frontmatter.head ??= [] pageData.frontmatter.head.push([ 'meta', { property: 'og:title', content: pageData.frontmatter.layout === 'home' ? 'Homepage - My Site' : pageData.title } ]) } } ``` `transformPageData` runs during both dev and build. For build-only transforms, use [`transformHead`](https://olets.dev/posts/per-page-dynamic-social-metas-in-vitepress/), but note it doesn't run during development. h2. [Built-in Search](#built-in-search) VitePress includes [**fuzzy full-text search using minisearch**](https://vitepress.dev/reference/default-theme-search). Enable local search in `.vitepress/config.ts`: .vitepress/config.ts ``` export default { themeConfig: { search: { provider: 'local' } } } ``` No external service required. search runs in-browser using an indexed bundle. h3. [Algolia DocSearch](#algolia-docsearch) For larger sites, integrate [**Algolia DocSearch**](https://vitepress.dev/reference/default-theme-search): .vitepress/config.ts ``` export default { themeConfig: { search: { provider: 'algolia', options: { appId: 'YOUR_APP_ID', apiKey: 'YOUR_API_KEY', indexName: 'YOUR_INDEX_NAME' } } } } ``` Algolia crawls your site and provides advanced search with typo tolerance and result ranking. h2. [Clean URLs](#clean-urls) VitePress supports [**clean URLs without **`.html`** extensions**](https://github.com/vuejs/vitepress/discussions/548). Enable them in config: .vitepress/config.ts ``` export default { cleanUrls: true } ``` Clean URLs avoid [**duplicate content issues**](https://github.com/vuejs/vitepress/discussions/548) where `/page` and `/page.html` are treated as separate URLs. This matters for sitemaps and canonical tags. [**search engines mark mismatched URLs as duplicate content**](https://github.com/vuejs/vitepress/discussions/548). h2. [VitePress vs Nuxt Content](#vitepress-vs-nuxt-content) | **Feature** | **VitePress** | **Nuxt Content** | | --- | --- | --- | | **Build Speed** | [**Blazing fast with Vite**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/) | Slower with larger sites | | **Use Case** | [**Documentation, blogs, static content**](https://vitepress.dev/guide/what-is-vitepress) | Full web apps with markdown | | **SSR** | Static-only | Full SSR + SSG | | **Search** | Built-in local search | Requires integration | | **Learning Curve** | [**Simpler, minimalistic**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/) | [**Steeper, feature-rich**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/) | | **Page Directory** | Markdown only | [**Mixes markdown + Vue pages**](https://github.com/vuejs/vitepress/discussions/3785) (with friction) | | **Ecosystem** | Growing, documentation-focused | [**Larger, mature module system**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/) | | **Authentication** | [**Doesn't fit SSG model**](https://github.com/vuejs/vitepress/discussions/548) | Supported with SSR | VitePress [**excels for performance-critical static sites**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/) where all content is known at build time. Nuxt Content handles [**complex routing, dynamic content, and SSR requirements**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/) better. h2. [Limitations](#limitations) - **No SSR**: All content generates at build time. Dynamic features like user authentication [**don't fit VitePress**](https://github.com/vuejs/vitepress/discussions/548). - **Static Content Only**: Pages requiring server-side data fetching need Nuxt or custom Vite SSR. - **Smaller Ecosystem**: [**Fewer plugins compared to Nuxt**](https://www.vuemastery.com/blog/nuxt-vs-vitepress-vs-astro/), though actively growing. - **Mixed Routing Challenges**: [**Integrating VitePress within existing Nuxt projects**](https://github.com/vuejs/vitepress/discussions/3785) causes routing conflicts. better to run as separate sites. h2. [Using Nuxt?](#using-nuxt) Nuxt offers deeper SEO integration through [**@nuxtjs/seo**](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vitepress/docs/nuxt-seo/getting-started/introduction), which handles sitemaps, robots.txt, structured data, and OG images with zero configuration. [**Learn more about Nuxt Content for documentation →**](https://content.nuxt.com/) --- [**Custom Vite SSR** Building server-side rendering with Vite instead of frameworks. When custom SSR makes sense and SEO considerations.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vitepress/learn-seo/vue/ssr-frameworks/vite-ssr) [**Launch & Listen** Deploy your Vue site to production and monitor its indexing status in search engines.](https://nuxtseo.com/learn-seo/vue/ssr-frameworks/vitepress/learn-seo/vue/launch-and-listen) **On this page** - [When to Use VitePress](#when-to-use-vitepress) - [SEO Features](#seo-features) - [Meta Tags Configuration](#meta-tags-configuration) - [Built-in Search](#built-in-search) - [Clean URLs](#clean-urls) - [VitePress vs Nuxt Content](#vitepress-vs-nuxt-content) - [Limitations](#limitations) - [Using Nuxt?](#using-nuxt) --- ### SEO-Friendly URLs in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure Description: Create search-optimized URLs using Vue Router. Learn slug formatting, parameter handling, and route patterns that improve rankings. h1. **SEO-Friendly URLs in Vue** Create search-optimized URLs using Vue Router. Learn slug formatting, parameter handling, and route patterns that improve rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** **What you'll learn** - Use hyphens as word separators (not underscores). Google treats hyphens as word breaks - Keep URLs under 60 characters and lowercase only - Use path segments for content, query parameters for filters and sorting URLs appear in search results before users click. `/blog/vue-seo-guide` tells users what to expect. `/p?id=847` doesn't. Search engines use URLs to understand page hierarchy and relevance. Well-structured URLs improve click-through rates by up to 15%. For Vue applications requiring SSR, use Unhead for meta tags and configure Vue Router with proper slug patterns. h2. [Quick Setup](#quick-setup) Create SEO-friendly slugs from titles: composables/useSeoSlug.ts ``` export function useSeoSlug(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens .replace(/^-+|-+$/g, '') // Trim leading/trailing hyphens .substring(0, 60) // Keep under 60 chars } ``` router.ts ``` import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/blog/:slug', component: BlogPost }, { path: '/products/:category/:slug', component: ProductPage } ] }) ``` pages/BlogPost.vue ``` ``` For Vue applications, you'll need to [**install Unhead manually**](https://unhead.unjs.io/guide/getting-started/installation). h2. [URL Formatting Rules](#url-formatting-rules) h3. [Hyphens Over Underscores](#hyphens-over-underscores) [**Google treats hyphens as word separators**](https://developers.google.com/search/docs/crawling-indexing/url-structure). Underscores connect words into single terms. ``` ✅ /performance-optimization → "performance" + "optimization" ❌ /performance_optimization → "performanceoptimization" ``` Google's Matt Cutts confirmed in 2011: "We use the words in a URL as a very lightweight factor... we can't easily segment at underscores." **Vue Router implementation:** ``` // ✅ Good { path: '/learn-vue-router', component: Guide } // ❌ Bad { path: '/learn_vue_router', component: Guide } ``` h3. [Lowercase Only](#lowercase-only) URLs are case-sensitive. `/About`, `/about`, and `/ABOUT` are different pages. This creates duplicate content issues. ``` ✅ /about ✅ /products/phones ❌ /About ❌ /products/Phones ``` Enforce lowercase in your slug helper: ``` export function useSeoSlug(text: string): string { return text .toLowerCase() // ← Always lowercase .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') } ``` h3. [Keep URLs Short](#keep-urls-short) URLs under 60 characters perform better in search results. Longer URLs get truncated with ellipsis. **Length comparison:** | **URL** | **Length** | **Result** | | --- | --- | --- | | `/blog/vue-seo` | 14 chars | ✅ Displays fully | | `/blog/comprehensive-guide-to-vue-seo-optimization` | 50 chars | ⚠️ Works but verbose | | `/blog/a-comprehensive-guide-to-vue-server-side-rendering-seo-optimization-best-practices` | 98 chars | ❌ Truncated in results | ``` export function useSeoSlug(text: string): string { return text .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .substring(0, 60) // ← Limit to 60 chars } ``` **When longer URLs make sense:** ``` ✅ /docs/getting-started/installation-guide (clear hierarchy) ✅ /blog/2025/fixing-vue-hydration-mismatch (date + topic) ❌ /the-ultimate-comprehensive-complete-guide (keyword stuffing) ``` h3. [Keywords Near the Start](#keywords-near-the-start) [**Including keywords in URLs provides a lightweight ranking boost**](https://www.americaneagle.com/insights/blog/post/creating-seo-friendly-urls). Front-load important terms. ``` ✅ /vue-router-seo-guide ✅ /seo/vue-best-practices ❌ /guides-and-tutorials-for-seo-in-vue-router ``` But don't sacrifice readability: ``` // ✅ Natural keyword placement { path: '/vue-seo/:topic', component: Guide } // ❌ Keyword stuffing { path: '/vue-seo-guide-vue-router-seo-tutorial', component: Guide } ``` h3. [Avoid Dates (Usually)](#avoid-dates-usually) Dates in URLs prevent content updates. `/blog/2024/vue-guide` becomes outdated when you refresh it in 2025. ``` ❌ /blog/2024/vue-router-guide (looks stale) ❌ /blog/2024/12/17/post-title (prevents evergreen updates) ✅ /blog/vue-router-guide (can be updated anytime) ``` **Exception:** Time-sensitive content like news, events, changelogs: ``` const routes = [ // News/events - dates make sense { path: '/changelog/:year/:month/:slug', component: Changelog }, { path: '/events/2025/:slug', component: Event }, // Evergreen content - skip dates { path: '/blog/:slug', component: BlogPost }, { path: '/guides/:slug', component: Guide } ] ``` [**Removing dates allows republishing old posts**](https://www.octaria.com/blog/seo-friendly-url-structure-best-practices-2025) with new content without changing URLs. a strong SEO strategy. h2. [Path Segments vs Query Parameters](#path-segments-vs-query-parameters) ![Path vs Query Parameter Decision](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure/images/learn-seo/vue/path-vs-query-decision.svg) Search engines prefer path segments over query parameters. [**Path segments are indexed and ranked**](https://www.searchenginejournal.com/technical-seo/url-parameter-handling/). Query parameters often cause duplicate content. **Comparison:** | **Type** | **Example** | **SEO Impact** | | --- | --- | --- | | Path segments | `/products/phones/iphone-15` | ✅ Clean, indexed, ranks well | | Query parameters | `/products?category=phones&id=15` | ⚠️ Duplicate content risk | | Mixed | `/products/phones?sort=price` | ✅ Path for content, query for filters | **Problems with query parameters:** ``` /products /products?sort=price /products?sort=date /products?page=2 /products?sort=price&page=2 ``` Five URLs, same content. Google sees [**duplicate content and wastes crawl budget**](https://www.seozoom.com/a-guide-to-query-strings-url-parameters-for-the-seo/). h3. [Vue Router Path Segments](#vue-router-path-segments) Use dynamic segments for content that should be indexed: router.ts ``` const routes = [ // ✅ Path segments for SEO { path: '/products/:category/:slug', component: Product }, { path: '/blog/:year/:month/:slug', component: BlogPost }, { path: '/docs/:section/:page', component: Documentation } ] ``` This generates clean URLs: - `/products/phones/iphone-15` - `/blog/2025/12/vue-seo-guide` - `/docs/getting-started/installation` h3. [Query Parameters for Filters](#query-parameters-for-filters) Use query parameters for sorting, filtering, pagination. features that modify display without changing core content: pages/Products.vue ``` ``` Set [**canonical URLs**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure/learn-seo/vue/controlling-crawlers/canonical-urls) to consolidate ranking signals: ``` // Filter/sort variations point to base URL useHead({ link: [{ rel: 'canonical', href: \`https://mysite.com/products/${category}\` }] }) ``` h2. [Dynamic Routes](#dynamic-routes) [**Vue Router's dynamic routes**](https://router.vuejs.org/guide/essentials/dynamic-matching.html) create SEO-friendly URLs automatically. h3. [Basic Dynamic Segments](#basic-dynamic-segments) router.ts ``` const routes = [ { path: '/blog/:slug', component: BlogPost, props: true } ] ``` pages/BlogPost.vue ``` ``` h3. [Nested Dynamic Routes](#nested-dynamic-routes) router.ts ``` const routes = [ { path: '/products/:category/:slug', component: Product, props: true } ] ``` Generates hierarchical URLs: - `/products/electronics/laptop` - `/products/clothing/jacket` pages/Product.vue ``` ``` h3. [Multiple Optional Parameters](#multiple-optional-parameters) router.ts ``` const routes = [ { path: '/search/:query/:filters?', component: SearchResults } ] ``` Matches both: - `/search/vue-router` - `/search/vue-router/recent` **Important:** Optional parameters create multiple URLs for similar content. Use canonical tags: ``` const route = useRoute() useHead({ link: [{ rel: 'canonical', href: \`https://mysite.com/search/${route.params.query}\` }] }) ``` h2. [Slug Generation Patterns](#slug-generation-patterns) h3. [From CMS Content](#from-cms-content) composables/useSlugFromTitle.ts ``` export function useSlugFromTitle(title: string): string { return title .toLowerCase() .trim() // Replace accented characters .normalize('NFD') .replace(/[\u0300-\u036F]/g, '') // Replace non-alphanumeric with hyphens .replace(/[^a-z0-9]+/g, '-') // Remove leading/trailing hyphens .replace(/^-+|-+$/g, '') // Limit length .substring(0, 60) } ``` Example usage ``` const title = 'Vue Router: The Complete Guide (2025)' const slug = useSlugFromTitle(title) // Result: "vue-router-the-complete-guide-2025" ``` h3. [Preserving Non-ASCII Characters](#preserving-non-ascii-characters) For international content, [**use UTF-8 encoding in URLs**](https://developers.google.com/search/docs/crawling-indexing/url-structure): composables/useInternationalSlug.ts ``` export function useInternationalSlug(text: string): string { return text .toLowerCase() .trim() // Keep Unicode letters and numbers .replace(/[^\p{L}\p{N}]+/gu, '-') .replace(/^-+|-+$/g, '') .substring(0, 60) } ``` ``` // Preserves non-ASCII useInternationalSlug('Vue 路由指南') // Result: "vue-路由指南" // vs ASCII-only useSlugFromTitle('Vue 路由指南') // Result: "vue" ``` h3. [Handling Duplicates](#handling-duplicates) Append numbers when slugs collide: server/api/posts.ts ``` async function generateUniqueSlug(title: string): Promise { const baseSlug = useSlugFromTitle(title) let slug = baseSlug let counter = 1 while (await slugExists(slug)) { slug = \`${baseSlug}-${counter}\` counter++ } return slug } ``` Results: - First post: `/blog/vue-router-guide` - Second post with same title: `/blog/vue-router-guide-2` h2. [Server Configuration](#server-configuration) h3. [History Mode Requirements](#history-mode-requirements) Vue Router's History mode requires server configuration. All routes must serve `index.html`: ``` location / { try_files $uri $uri/ /index.html; } ``` ``` RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] ``` ``` import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import express from 'express' const app = express() const __dirname = dirname(fileURLToPath(import.meta.url)) // Serve static files app.use(express.static(join(__dirname, 'dist'))) // All routes return index.html app.get('*', (req, res) => { res.sendFile(join(__dirname, 'dist', 'index.html')) }) app.listen(3000) ``` ``` import { join } from 'node:path' import { defineEventHandler, readFile, sendRedirect } from 'h3' export default defineEventHandler(async (event) => { const path = event.node.req.url // Try to serve static file try { const file = await readFile(join('./dist', path)) return file } catch { // Fall back to index.html for client-side routing return await readFile('./dist/index.html') } }) ``` Without this configuration, direct URLs like `/blog/vue-seo` return 404 errors. h2. [What NOT to Do](#what-not-to-do) **Don't use uppercase letters:** ``` ❌ /Blog/Vue-SEO-Guide ❌ /PRODUCTS/phones ✅ /blog/vue-seo-guide ✅ /products/phones ``` **Don't expose internal IDs:** ``` ❌ /products/db-id-84792 ❌ /posts?id=12345 ✅ /products/laptop-pro-15 ✅ /blog/vue-seo-guide ``` **Don't create infinite parameter variations:** ``` ❌ /products?color=red&size=large&material=cotton&style=casual ✅ /products/red-cotton-casual-shirt ``` **Don't change URLs without redirects:** ``` // When changing URL structure, add redirects const routes = [ { path: '/old-blog-post', redirect: '/blog/new-post' }, { path: '/blog/:slug', component: BlogPost } ] ``` But note: Client-side redirects don't send 301 status codes. Use [**server-side redirects**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure/learn-seo/vue/controlling-crawlers/redirects) for SEO. **Don't use special characters:** ``` ❌ /blog/vue&react-comparison ❌ /products/50%-off-sale! ✅ /blog/vue-react-comparison ✅ /products/50-percent-off-sale ``` h2. [Testing URL Structure](#testing-url-structure) **View in search results:** ``` h1. Test how Google displays your URLs site:yoursite.com "vue router" ``` **Check canonicalization:** Use [**Google Search Console URL Inspection**](https://search.google.com/search-console) to verify: - User-declared canonical matches your intent - Google-selected canonical agrees with your preference - No conflicting signals from redirects or alternates **Validate slug generation:** tests/slugs.test.ts ``` import { describe, expect, it } from 'vitest' import { useSlugFromTitle } from '~/composables/useSlugFromTitle' describe('useSlugFromTitle', () => { it('converts title to lowercase slug', () => { expect(useSlugFromTitle('Vue Router Guide')) .toBe('vue-router-guide') }) it('replaces spaces with hyphens', () => { expect(useSlugFromTitle('Learn Vue Router')) .toBe('learn-vue-router') }) it('removes special characters', () => { expect(useSlugFromTitle('Vue & React!')) .toBe('vue-react') }) it('limits length to 60 chars', () => { const longTitle = 'A'.repeat(100) expect(useSlugFromTitle(longTitle).length).toBe(60) }) }) ``` h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, file-based routing generates URLs automatically. The [**Nuxt SEO module**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure/docs/nuxt-seo/getting-started/introduction) handles canonical URLs, sitemaps, and route rules. [**Learn more about URL structure in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure/learn-seo/nuxt/routes-and-rendering) --- [**Routes & Rendering** URL structure and rendering mode determine whether search engines can crawl, index, and rank your Vue application.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure/learn-seo/vue/routes-and-rendering) [**Pagination** How to implement SEO-friendly pagination in Vue using canonical tags, self-referencing URLs, and proper link structure.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/url-structure/learn-seo/vue/routes-and-rendering/pagination) **On this page** - [Quick Setup](#quick-setup) - [URL Formatting Rules](#url-formatting-rules) - [Path Segments vs Query Parameters](#path-segments-vs-query-parameters) - [Dynamic Routes](#dynamic-routes) - [Slug Generation Patterns](#slug-generation-patterns) - [Server Configuration](#server-configuration) - [What NOT to Do](#what-not-to-do) - [Testing URL Structure](#testing-url-structure) - [Using Nuxt?](#using-nuxt) --- ### Pagination SEO for Vue Applications · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination Description: How to implement SEO-friendly pagination in Vue using canonical tags, self-referencing URLs, and proper link structure. h1. **Pagination SEO for Vue Applications** How to implement SEO-friendly pagination in Vue using canonical tags, self-referencing URLs, and proper link structure. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)14 mins read Published **Dec 17, 2025** **What you'll learn** - Google deprecated `rel=prev/next` in 2019. use self-referencing canonicals instead - Each paginated page should canonical to itself, not page 1 - Infinite scroll requires hybrid approach with crawlable pagination links Pagination splits content across multiple pages. Search engines treat each page as separate. Set canonical tags wrong and Google indexes page 1 only. Set them right and all pages rank. Google [**no longer uses**](https://developers.google.com/search/blog/2011/09/pagination-with-relnext-and-relprev) `rel=prev/next` tags (deprecated March 2019). Modern pagination relies on self-referencing canonicals and crawlable `
` links. h2. [Self-Referencing Canonical Tags](#self-referencing-canonical-tags) Each paginated page should have its own canonical URL pointing to itself. ![Pagination Canonical Flow](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination/images/learn-seo/vue/pagination-canonical-flow.svg) **Don't** point all pages to page 1. That tells Google only page 1 matters, hiding pages 2+ from search results. ``` ``` ``` ``` [**Self-referencing canonicals**](https://www.semrush.com/blog/pagination-seo/) tell Google each page has unique content worth indexing. h2. [Pagination URL Structure](#pagination-url-structure) Use query parameters or path segments. Both work for SEO. | **URL Pattern** | **Example** | **SEO Impact** | | --- | --- | --- | | Query parameter | `/blog?page=2` | Good - simple, flexible | | Path segment | `/blog/page/2` | Good - cleaner URLs | | Hash fragment | `/blog#page=2` | Bad - Google ignores `#` | **Query parameter approach:** ``` import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/blog', component: BlogList, // Handles /blog?page=2 } ] }) ``` **Path segment approach:** ``` const router = createRouter({ history: createWebHistory(), routes: [ { path: '/blog', component: BlogList }, { path: '/blog/page/:page', component: BlogList } ] }) ``` Never use fragment identifiers (`#page=2`). [**Google ignores everything after **`#`](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). h2. [Crawlable Links](#crawlable-links) Google needs `` tags to discover paginated pages. Navigation rendered after JavaScript runs may not be crawled. **SSR/SSG approach (recommended):** ``` ``` The `href` attribute makes links crawlable. `@click.prevent` enables client-side navigation for users. **SPA approach (requires prerendering):** If using a client-only SPA, you need [**prerendering**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination/learn-seo/vue/spa/prerendering) or SSR for Google to discover pagination links. Pure SPAs without prerendering won't get pages 2+ indexed. h2. [Pagination Component](#pagination-component) Full example with prev/next links and numbered pages: ``` ``` [**Include links from each page to following pages**](https://www.amsive.com/insights/seo/how-to-correctly-implement-pagination-for-seo-user-experience/) using `` tags. Googlebot follows these to discover your content. h2. [View All Page Approach](#view-all-page-approach) Offer a single page with all content. Point canonicals from paginated pages to the View All page. ``` ``` **Drawbacks:** - Slow page load with 100+ items - Poor mobile experience - Images kill performance [**View All pages work**](https://seotactica.com/learn/canonical-pagination/) for small datasets (50 items). For large catalogs, use self-referencing canonicals. h2. [Infinite Scroll vs Pagination](#infinite-scroll-vs-pagination) ![Pagination vs Infinite Scroll Decision](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination/images/learn-seo/vue/pagination-vs-infinite-scroll.svg) | **Pattern** | **SEO Impact** | **UX** | **When to Use** | | --- | --- | --- | --- | | Pagination | Good - all pages indexable | Predictable | Catalogs, search results | | Infinite scroll | Poor - requires special handling | Frictionless | Social feeds, inspiration | | Load More button | Poor - unless URL changes | Balanced | Product listings | **Infinite scroll SEO challenges:** [**Googlebot cannot scroll**](https://www.seoclarity.net/blog/pagination-vs-infinite-scroll). Content below the fold stays hidden. Solutions: 1. **Hybrid approach** - Infinite scroll for users, paginated URLs for crawlers: ``` ``` 1. **Component pages** - Break infinite scroll into paginated URLs with unique canonicals. [**Google recommends paginated series**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) alongside infinite scroll. **Load More button:** Works for UX but [**Googlebot can't click buttons**](https://www.resultfirst.com/blog/ai-seo/pagination-vs-infinite-scroll-vs-load-more/). Use the hybrid approach above if SEO matters. h2. [Server-Side Pagination](#server-side-pagination) ``` import express from 'express' const app = express() app.get('/api/posts', (req, res) => { const page = Number(req.query.page) || 1 const limit = 10 const offset = (page - 1) * limit const posts = db.query( 'SELECT * FROM posts LIMIT ? OFFSET ?', [limit, offset] ) res.json({ posts, page, totalPages: Math.ceil(totalPosts / limit) }) }) ``` ``` import { defineEventHandler, getQuery } from 'h3' export default defineEventHandler(async (event) => { const query = getQuery(event) const page = Number(query.page) || 1 const limit = 10 const offset = (page - 1) * limit const posts = await db.query( 'SELECT * FROM posts LIMIT ? OFFSET ?', [limit, offset] ) return { posts, page, totalPages: Math.ceil(totalPosts / limit) } }) ``` ``` import { defineConfig } from 'vite' export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } } }) ``` Limit queries to avoid performance issues. Use database indexes on sort columns. h2. [Meta Titles and Descriptions](#meta-titles-and-descriptions) Differentiate each paginated page for SEO: ``` ``` Unique titles prevent [**duplicate content confusion**](https://www.searchenginejournal.com/technical-seo/pagination/). h2. [Noindex on Paginated Pages](#noindex-on-paginated-pages) **Don't** noindex paginated pages. [**This loses indexed content**](https://www.seoclarity.net/blog/pagination-seo). Google stops crawling noindexed pages, hiding your products/articles. Only noindex if: - Filter/sort variations create infinite URLs (`/blog?sort=date&order=asc&filter=vue`) - You have a View All page as canonical - Pages have no unique content For most sites, [**keep paginated pages indexable**](https://ahrefs.com/blog/rel-prev-next-pagination/). h2. [Common Mistakes](#common-mistakes) **Mistake 1: Canonicalizing all pages to page 1** ``` ``` This tells Google pages 2+ are duplicates. Use self-referencing canonicals. **Mistake 2: Using hash fragments** ``` ❌ /blog#page=2 ✅ /blog?page=2 ✅ /blog/page/2 ``` Google ignores `#`. Your pagination won't be indexed. **Mistake 3: Client-only pagination links** ``` Next ``` **Mistake 4: Inconsistent trailing slashes** ``` /blog?page=1 /blog/?page=2 ← Duplicate content ``` Pick one format. See [**trailing slashes guide**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination/learn-seo/vue/routes-and-rendering/trailing-slashes). **Mistake 5: Blocking pagination in robots.txt** ``` h1. ❌ Hides paginated content User-agent: * Disallow: /*?page= ``` [**Don't block pagination URLs**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). Google needs to crawl them. h2. [Testing Pagination SEO](#testing-pagination-seo) **1. Check canonical tags** ``` curl -s https://nuxtseo.com/blog?page=2 | grep canonical ``` Should return: ``` ``` **2. Verify crawlable links** View page source (not DevTools). Look for `` tags with pagination URLs. If pagination appears only in JavaScript, add SSR/prerendering. **3. Google Search Console** - URL Inspection tool - Check "Coverage" report for indexed pages - Look for paginated URLs in index **4. Site search** ``` site:nuxtseo.com/blog inurl:page ``` Shows indexed paginated pages. h2. [Using Nuxt?](#using-nuxt) Nuxt SEO handles pagination automatically with route rules and canonical URL generation. The `` component ensures consistent URL formatting across paginated pages. [**Learn more about Nuxt pagination →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination/learn-seo/nuxt/routes-and-rendering) h2. [Sources](#sources) - [**Pagination and SEO: What you need to know in 2025**](https://searchengineland.com/pagination-seo-what-you-need-to-know-453707) - [**How to Correctly Implement Pagination for SEO**](https://www.amsive.com/insights/seo/how-to-correctly-implement-pagination-for-seo-user-experience/) - [**SEOs Are Breaking Pagination After Google Changed Rel=Prev/Next**](https://ahrefs.com/blog/rel-prev-next-pagination/) - [**Pagination Best Practices for Google**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading) - [**Pagination and SEO: A Complete Guide**](https://www.semrush.com/blog/pagination-seo/) - [**What Is Canonical Pagination in SEO**](https://seotactica.com/learn/canonical-pagination/) - [**Is Pagination or Infinite Scroll Better for SEO?**](https://www.seoclarity.net/blog/pagination-vs-infinite-scroll) - [**Pagination vs Infinite Scroll vs Load More**](https://www.resultfirst.com/blog/ai-seo/pagination-vs-infinite-scroll-vs-load-more/) --- [**URL Structure** Create search-optimized URLs using Vue Router. Learn slug formatting, parameter handling, and route patterns that improve rankings.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination/learn-seo/vue/routes-and-rendering/url-structure) [**Trailing Slashes** Trailing slashes can cause duplicate content issues. Here's how to handle them in Vue Router.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/pagination/learn-seo/vue/routes-and-rendering/trailing-slashes) **On this page** - [Self-Referencing Canonical Tags](#self-referencing-canonical-tags) - [Pagination URL Structure](#pagination-url-structure) - [Crawlable Links](#crawlable-links) - [Pagination Component](#pagination-component) - [View All Page Approach](#view-all-page-approach) - [Infinite Scroll vs Pagination](#infinite-scroll-vs-pagination) - [Server-Side Pagination](#server-side-pagination) - [Meta Titles and Descriptions](#meta-titles-and-descriptions) - [Noindex on Paginated Pages](#noindex-on-paginated-pages) - [Common Mistakes](#common-mistakes) - [Testing Pagination SEO](#testing-pagination-seo) - [Using Nuxt?](#using-nuxt) - [Sources](#sources) --- ### Trailing Slashes in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes Description: Trailing slashes can cause duplicate content issues. Here's how to handle them in Vue Router. h1. **Trailing Slashes in Vue** Trailing slashes can cause duplicate content issues. Here's how to handle them in Vue Router. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Nov 6, 2024** Updated **Dec 5, 2024** **What you'll learn** - Pick one format (with or without trailing slash) and enforce site-wide - Use server-side 301 redirects. client-side redirects don't pass SEO signals - Mixing formats creates duplicate content and splits ranking signals A trailing slash is the "/" at the end of a URL: - With trailing slash: `/about/` - Without trailing slash: `/about` While trailing slashes don't directly impact SEO rankings, they can create technical challenges: 1. **Duplicate Content**: When both `/about` and `/about/` serve the same content without proper canonicalization, search engines have to choose which version to index. While Google is generally good at figuring this out, it's better to be explicit. 2. **Crawl Budget**: On large sites, having multiple URLs for the same content can waste your [**crawl budget**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes/learn-seo/vue/controlling-crawlers#improve-organic-traffic). 3. **Analytics Accuracy**: Different URL formats can split your analytics data, making it harder to track page performance. The solution is simple: pick one format and stick to it by [**redirecting**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes/learn-seo/vue/controlling-crawlers/redirects) the other and set [**canonical URLs**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes/learn-seo/vue/controlling-crawlers/canonical-urls). h2. [Vue Router Configuration](#vue-router-configuration) h3. [Enforcing Trailing Slashes](#enforcing-trailing-slashes) Vue Router has a `strict` mode that enforces exact path matching: ``` import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ // Define all routes with trailing slashes { path: '/about/', component: About }, { path: '/products/', component: Products }, { path: '/blog/:slug/', component: BlogPost } ], strict: true // Enforce exact path matching }) ``` With `strict: true`, `/about` and `/about/` are treated as different routes. h3. [Removing Trailing Slashes](#removing-trailing-slashes) To remove trailing slashes consistently: ``` const router = createRouter({ history: createWebHistory(), routes: [ // Define all routes without trailing slashes { path: '/about', component: About }, { path: '/products', component: Products }, { path: '/blog/:slug', component: BlogPost } ], strict: true }) ``` h3. [Client-Side Redirects](#client-side-redirects) Add a navigation guard to redirect inconsistent URLs: ``` router.beforeEach((to, from, next) => { // Remove trailing slashes if (to.path !== '/' && to.path.endsWith('/')) { next({ path: to.path.slice(0, -1), query: to.query, hash: to.hash }) return } // Or add trailing slashes // if (to.path !== '/' && !to.path.endsWith('/')) { // next({ path: to.path + '/', query: to.query, hash: to.hash }) // return // } next() }) ``` **Important:** Client-side redirects don't send proper HTTP 301 status codes to search engines. Use server-side redirects for SEO. h2. [Server-Side Redirects](#server-side-redirects) For proper SEO, configure your web server to redirect trailing slash variations: h3. [Nginx](#nginx) Remove trailing slashes: ``` rewrite ^/(.*)/$ /$1 permanent; ``` Add trailing slashes: ``` rewrite ^([^.]*[^/])$ $1/ permanent; ``` h3. [Apache](#apache) Remove trailing slashes: ``` RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1 [R=301,L] ``` Add trailing slashes: ``` RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_URI} !(.*)/$ RewriteRule ^(.*)$ $1/ [R=301,L] ``` h2. [Canonical URLs](#canonical-urls) Set canonical URLs to indicate your preferred URL format: ``` ``` h2. [Best Practices](#best-practices) 1. **Choose One Format**: Pick with or without trailing slashes and stick to it site-wide 2. **Server-Side Redirects**: Use 301 redirects at the server level, not client-side 3. **Canonical Tags**: Always set canonical URLs to your preferred format 4. **Internal Links**: Be consistent in your internal linking 5. **Sitemap**: Use consistent URLs in your sitemap h2. [What NOT to Do](#what-not-to-do) ❌ Don't mix URL formats across your site ❌ Don't rely only on client-side redirects for SEO ❌ Don't ignore the issue on large sites ❌ Don't change your format without proper redirects h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes/docs/nuxt-seo/getting-started/introduction) for built-in SEO features. Nuxt SEO provides automatic trailing slash handling through site config and the `` component. [**Learn more in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes/learn-seo/nuxt/routes-and-rendering/trailing-slashes) --- [**Pagination** How to implement SEO-friendly pagination in Vue using canonical tags, self-referencing URLs, and proper link structure.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes/learn-seo/vue/routes-and-rendering/pagination) [**Query Parameters** Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Vue Router.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/trailing-slashes/learn-seo/vue/routes-and-rendering/query-parameters) **On this page** - [Vue Router Configuration](#vue-router-configuration) - [Server-Side Redirects](#server-side-redirects) - [Canonical URLs](#canonical-urls) - [Best Practices](#best-practices) - [What NOT to Do](#what-not-to-do) - [Using Nuxt?](#using-nuxt) --- ### Query Parameters and SEO in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters Description: Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Vue Router. h1. **Query Parameters and SEO in Vue** Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Vue Router. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - Filter parameters should be noindexed or canonical to base URL - Strip tracking params (utm_, fbclid, gclid) from canonical URLs - Move important filters to path segments for better SEO value Query parameters (`?sort=price&filter=red`) create duplicate content. The URL `/products?sort=price` and `/products?sort=name` show the same products in different orders, but search engines treat them as separate pages. With filters, sorting, and pagination, a single page can generate hundreds of URL variations. Each variation wastes crawl budget and dilutes ranking signals across duplicates. h2. [Quick Setup](#quick-setup) Handle query parameters with canonical URLs to tell search engines which version to index: pages/Products.vue ``` import { useHead } from '@unhead/vue' import { useRoute } from 'vue-router' const route = useRoute() const { sort, filter, page } = route.query useHead({ link: [{ rel: 'canonical', // Remove filter and page, keep sort href: sort ? \`https://mysite.com/products?sort=${sort}\` : 'https://mysite.com/products' }] }) ``` pages/Products.vue - Block All Parameters ``` useHead({ link: [{ rel: 'canonical', // Always canonical to base URL href: 'https://mysite.com/products' }] }) ``` pages/Products.vue - Block from Indexing ``` import { useSeoMeta } from '@unhead/vue' // Use noindex for filtered/sorted views useSeoMeta({ robots: computed(() => route.query.filter || route.query.page ? 'noindex, follow' : 'index, follow' ) }) ``` For Vue applications, you'll need to [**install Unhead manually**](https://unhead.unjs.io/guide/getting-started/installation). h2. [Common Parameter Types](#common-parameter-types) Different query parameters need different handling: | **Parameter Type** | **Examples** | **SEO Treatment** | **Why** | | --- | --- | --- | --- | | **Filters** | `?color=red&size=large` | Canonical to base or noindex | Creates duplicates, thin content | | **Sorting** | `?sort=price` | Include in canonical | Changes value, users link to sorted views | | **Pagination** | `?page=2` | Self-referencing canonical | Each page has unique content | | **Tracking** | `?utm_source=twitter` | Strip from canonical | No content value, analytics only | | **Search** | `?q=shoes` | Depends on results | Index if unique results, noindex if duplicates | | **Sessions** | `?sessionid=abc` | Canonical to base | Creates infinite URLs | h2. [Filter Parameters](#filter-parameters) Filters create exponential URL variations. Three color filters generate 8 combinations (red, blue, green, red+blue, red+green, blue+green, red+blue+green, none). h3. [Block Filtered Pages](#block-filtered-pages) pages/Products.vue ``` const route = useRoute() const hasFilters = computed(() => route.query.color || route.query.size || route.query.brand ) useHead({ link: [{ rel: 'canonical', href: 'https://mysite.com/products' }] }) useSeoMeta({ robots: hasFilters.value ? 'noindex, follow' : 'index, follow' }) ``` h3. [Move Important Filters to Path](#move-important-filters-to-path) For SEO-valuable filters (categories, main attributes), use route paths instead of query params: ``` // /products?category=shoes const routes = [{ path: '/products', component: Products }] ``` ``` // /products/shoes const routes = [{ path: '/products/:category', component: Products }] ``` h2. [Sort Parameters](#sort-parameters) Sorting changes presentation but not content. Users share sorted URLs ("cheapest laptops" links to `?sort=price`). h3. [Include Sort in Canonical](#include-sort-in-canonical) pages/Products.vue ``` const route = useRoute() const allowedSortValues = ['price', 'name', 'date', 'rating'] const sort = computed(() => allowedSortValues.includes(route.query.sort) ? route.query.sort : undefined ) useHead({ link: [{ rel: 'canonical', href: sort.value ? \`https://mysite.com/products?sort=${sort.value}\` : 'https://mysite.com/products' }] }) ``` **Why validate sort values?** Prevents parameter manipulation creating infinite URLs (`?sort=abc`, `?sort=xyz`). h3. [Block Sort from Indexing](#block-sort-from-indexing) If sorted views don't add value (same content, different order), use noindex: ``` useSeoMeta({ robots: route.query.sort ? 'noindex, follow' : 'index, follow' }) ``` h2. [Pagination Parameters](#pagination-parameters) Each page in a sequence has unique content. [**Google recommends self-referencing canonicals**](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading). don't point page 2 to page 1. pages/Blog.vue ``` const route = useRoute() const page = computed(() => route.query.page || '1') useHead({ link: [{ rel: 'canonical', // Each page references itself href: page.value === '1' ? 'https://mysite.com/blog' : \`https://mysite.com/blog?page=${page.value}\` }] }) ``` h3. [Validate Page Numbers](#validate-page-numbers) ``` const page = computed(() => { const pageNum = Number.parseInt(route.query.page) return pageNum > 0 && pageNum <= maxPages ? pageNum : 1 }) ``` Prevents crawlers requesting `?page=999999` and wasting server resources. h2. [Tracking Parameters](#tracking-parameters) Analytics parameters (utm_, fbclid, gclid) don't change content but create duplicate URLs. h3. [Strip Tracking Params](#strip-tracking-params) composables/useCanonicalUrl.ts ``` export function useCanonicalUrl(path: string) { const siteUrl = import.meta.env.VITE_SITE_URL const route = useRoute() // List of tracking params to ignore const trackingParams = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'msclkid', 'mc_cid', 'mc_eid', '_ga', 'ref', 'source' ] // Keep only non-tracking params const cleanParams = Object.fromEntries( Object.entries(route.query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${siteUrl}${path}?${queryString}\` : \`${siteUrl}${path}\` }] } } ``` h3. [Server-Side Parameter Handling](#server-side-parameter-handling) Redirect tracking parameters at the server level for proper 301 status codes: ``` import express from 'express' const app = express() app.use((req, res, next) => { const trackingParams = ['utm_source', 'fbclid', 'gclid'] const hasTracking = trackingParams.some(param => req.query[param]) if (hasTracking) { const cleanQuery = Object.fromEntries( Object.entries(req.query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanQuery).toString() return res.redirect(301, \`${req.path}${queryString ? \`?${queryString}\` : ''}\`) } next() }) ``` ``` // server.js for Vite SSR import express from 'express' const app = express() app.use((req, res, next) => { const trackingParams = ['utm_source', 'fbclid', 'gclid'] const hasTracking = trackingParams.some(param => req.query[param]) if (hasTracking) { const cleanQuery = Object.fromEntries( Object.entries(req.query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanQuery).toString() return res.redirect(301, \`${req.path}${queryString ? \`?${queryString}\` : ''}\`) } next() }) ``` ``` import { defineEventHandler, getQuery, sendRedirect } from 'h3' export default defineEventHandler((event) => { const query = getQuery(event) const trackingParams = ['utm_source', 'fbclid', 'gclid'] const hasTracking = trackingParams.some(param => query[param]) if (hasTracking) { const cleanQuery = Object.fromEntries( Object.entries(query).filter(([key]) => !trackingParams.includes(key) ) ) const queryString = new URLSearchParams(cleanQuery).toString() return sendRedirect(event, \`${event.path}${queryString ? \`?${queryString}\` : ''}\`, 301) } }) ``` h2. [Parameter Order Consistency](#parameter-order-consistency) `?sort=price&filter=red` and `?filter=red&sort=price` are identical content but different URLs. Enforce consistent parameter ordering: composables/useCanonicalUrl.ts ``` export function useCanonicalUrl(path: string, params: Record) { const siteUrl = import.meta.env.VITE_SITE_URL // Define parameter order const paramOrder = ['category', 'sort', 'filter', 'page'] // Sort params by predefined order const orderedParams = Object.fromEntries( Object.entries(params) .sort(([a], [b]) => { const indexA = paramOrder.indexOf(a) const indexB = paramOrder.indexOf(b) if (indexA === -1) return 1 if (indexB === -1) return -1 return indexA - indexB }) ) const queryString = new URLSearchParams(orderedParams).toString() return { link: [{ rel: 'canonical', href: queryString ? \`${siteUrl}${path}?${queryString}\` : \`${siteUrl}${path}\` }] } } ``` h3. [Vue Router Navigation](#vue-router-navigation) Force parameter order when updating query strings: ``` import { useRouter } from 'vue-router' const router = useRouter() function updateFilters(filters: Record) { const paramOrder = ['category', 'sort', 'filter', 'page'] const ordered = Object.fromEntries( Object.entries(filters) .sort(([a], [b]) => { const indexA = paramOrder.indexOf(a) const indexB = paramOrder.indexOf(b) return indexA - indexB }) ) router.push({ query: ordered }) } ``` h2. [Search Parameters](#search-parameters) Search queries create unique URLs for each search term. Treatment depends on result quality: h3. [Block Thin Search Results](#block-thin-search-results) pages/Search.vue ``` const route = useRoute() const query = route.query.q const results = await searchProducts(query) useSeoMeta({ robots: !query || results.length < 5 ? 'noindex, follow' : 'index, follow' }) useHead({ link: [{ rel: 'canonical', href: query ? \`https://mysite.com/search?q=${encodeURIComponent(query)}\` : 'https://mysite.com/search' }] }) ``` h3. [robots.txt for Search](#robotstxt-for-search) Block crawlers from search entirely: public/robots.txt ``` User-agent: * Disallow: /search? h1. Or use URL patterns Disallow: /*?q= Disallow: /*?query= ``` h2. [Testing Parameter Handling](#testing-parameter-handling) h3. [Google Search Console](#google-search-console) 1. URL Inspection tool 2. Enter URL with parameters 3. Check "User-declared canonical" vs "Google-selected canonical" 4. Verify they match your preference h3. [Manual Verification](#manual-verification) ``` h1. Check canonical in HTML curl https://mysite.com/products?sort=price | grep canonical h1. Should return: h1. ``` h3. [Test Parameter Variations](#test-parameter-variations) Create a test matrix: | **URL** | **Expected Canonical** | **Expected Robots** | | --- | --- | --- | | `/products` | Self | index, follow | | `/products?sort=price` | Self or base | Depends on strategy | | `/products?filter=red` | Base URL | noindex, follow | | `/products?utm_source=twitter` | Base URL | index, follow | | `/products?page=2` | Self | index, follow | h2. [robots.txt Parameter Blocking](#robotstxt-parameter-blocking) Block specific parameters from crawling entirely: public/robots.txt ``` User-agent: * h1. Block all URLs with these params Disallow: /*?sessionid= Disallow: /*?sid= Disallow: /*&sessionid= Disallow: /*&sid= h1. Block tracking params Disallow: /*?utm_source= Disallow: /*?fbclid= Disallow: /*?gclid= h1. Block filter combinations Disallow: /*?filter= Disallow: /*&filter= ``` [**Google deprecated parameter handling in Search Console**](https://developers.google.com/search/blog/2022/06/retiring-url-parameters-tool) in 2022. Use robots.txt or meta robots instead. h2. [Common Mistakes](#common-mistakes) **Using client-side canonicals for SPAs:** Googlebot doesn't execute JavaScript fast enough. Server-render canonical tags or use SSR. **Indexing every parameter variation:** Creates thin content and wastes crawl budget. Pick one canonical version. **Inconsistent parameter handling:** Some pages canonical to base, others to self. Be consistent site-wide. **Ignoring tracking parameters:** Analytics params create duplicate URLs. Strip them from canonicals. **Not validating parameter values:** Allows `?sort=anything` creating infinite URLs. Whitelist valid values. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters/docs/nuxt-seo/getting-started/introduction) for automatic canonical URL handling and parameter management through site config. [**Learn more about query parameters in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters/learn-seo/nuxt/routes-and-rendering) --- [**Trailing Slashes** Trailing slashes can cause duplicate content issues. Here's how to handle them in Vue Router.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters/learn-seo/vue/routes-and-rendering/trailing-slashes) [**Hreflang & i18n** Set hreflang tags in Vue to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/query-parameters/learn-seo/vue/routes-and-rendering/i18n) **On this page** - [Quick Setup](#quick-setup) - [Common Parameter Types](#common-parameter-types) - [Filter Parameters](#filter-parameters) - [Sort Parameters](#sort-parameters) - [Pagination Parameters](#pagination-parameters) - [Tracking Parameters](#tracking-parameters) - [Parameter Order Consistency](#parameter-order-consistency) - [Search Parameters](#search-parameters) - [Testing Parameter Handling](#testing-parameter-handling) - [robots.txt Parameter Blocking](#robotstxt-parameter-blocking) - [Common Mistakes](#common-mistakes) - [Using Nuxt?](#using-nuxt) --- ### 404 Pages and SEO in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages Description: 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Vue applications. h1. **404 Pages and SEO in Vue** 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Vue applications. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** **What you'll learn** - 404 errors don't hurt SEO, but soft 404s do (200 status with error content) - SSR required. SPAs return 200 for all routes and render errors client-side - Return proper 404 HTTP status code from the server, not just client-side error pages 404 errors don't hurt SEO. They're expected. deleted products, outdated links, user typos all create legitimate 404s. Google ignores them. Soft 404s hurt SEO. A soft 404 returns `200 OK` status but shows "page not found" content. Google excludes these from search results and wastes your [**crawl budget**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/learn-seo/vue/controlling-crawlers#crawler-budget) recrawling pages it thinks exist. SSR required. SPAs typically return `200 OK` for all routes and render 404 content client-side. search engines see this as a soft 404. h2. [Quick Setup](#quick-setup) Return proper 404 status codes from your server: ``` import express from 'express' const app = express() app.get('*', (req, res) => { // Check if route exists in your Vue Router config const routeExists = checkRoute(req.path) if (!routeExists) { res.status(404).send(render404Page()) return } // Normal SSR render res.send(renderVueApp(req.path)) }) function render404Page() { return \` 404 Not Found

Page Not Found

The page you're looking for doesn't exist.

Go home \` } ``` ``` // server.js for Vite SSR import express from 'express' import { createServer as createViteServer } from 'vite' const app = express() const vite = await createViteServer({ server: { middlewareMode: true } }) app.use(vite.middlewares) app.use('*', async (req, res) => { const url = req.originalUrl const routeExists = await checkRoute(url) if (!routeExists) { res.status(404) const html = await vite.transformIndexHtml(url, render404Template()) res.send(html) return } // Normal SSR render const html = await renderSSR(url) res.send(html) }) ``` ``` import { defineEventHandler, setResponseStatus } from 'h3' export default defineEventHandler((event) => { const routeExists = checkRoute(event.path) if (!routeExists) { setResponseStatus(event, 404) return render404Page() } return renderVueApp(event.path) }) ``` Add `noindex` meta tag to prevent 404 pages from appearing in search results if accidentally crawled with wrong status code. h2. [Soft 404 Errors Explained](#soft-404-errors-explained) Soft 404 detection happens when Google sees content that looks like an error page but receives `200 OK` status ([**Google Search Central**](https://developers.google.com/search/docs/crawling-indexing/http-network-errors#soft-404-errors)). Common triggers: - "Page not found" in title or heading - Minimal content (under ~200 words) - Redirecting all 404s to homepage - Empty page body with "coming soon" message - Generic error messages without meaningful content Google Search Console flags soft 404s in the "Page Indexing" report. Fix by returning proper `404` status code. h3. [Why Soft 404s Hurt SEO](#why-soft-404s-hurt-seo) 1. **Wasted crawl budget** - Google recrawls pages thinking they exist, leaving less budget for real pages 2. **Index bloat** - Search Console shows thousands of indexed URLs that don't exist 3. **Ranking signals confusion** - Google doesn't know if content moved or disappeared 4. **No link equity transfer** - Can't redirect or canonicalize non-existent pages properly h3. [Vue SPA Soft 404 Problem](#vue-spa-soft-404-problem) Vue Router handles routing client-side. Server returns `200 OK` for all paths: ``` // ❌ Bad - SPA returns 200 for /fake-page app.get('*', (req, res) => { res.send(indexHtml) // Always 200 OK }) ``` Vue Router then renders 404 component in browser after JavaScript executes. Google sees `200 OK` response, might see error content, flags as soft 404. **Solution:** Check route existence server-side before rendering. h2. [Checking Routes Server-Side](#checking-routes-server-side) Match incoming paths against your Vue Router configuration: ``` import { createMemoryHistory, createRouter } from 'vue-router' import routes from './routes' function checkRoute(path: string): boolean { const router = createRouter({ history: createMemoryHistory(), routes }) const resolved = router.resolve(path) return resolved.matched.length > 0 } ``` Integrate with server: ``` import express from 'express' import { checkRoute } from './router-check' const app = express() app.get('*', (req, res) => { if (!checkRoute(req.path)) { res.status(404).send(render404Page()) return } res.send(renderVueApp(req.path)) }) ``` ``` import express from 'express' import { checkRoute } from './router-check' const app = express() app.use('*', async (req, res) => { if (!checkRoute(req.originalUrl)) { res.status(404) res.send(await render404SSR()) return } res.send(await renderVueApp(req.originalUrl)) }) ``` ``` import { defineEventHandler, setResponseStatus } from 'h3' import { checkRoute } from './router-check' export default defineEventHandler((event) => { if (!checkRoute(event.path)) { setResponseStatus(event, 404) return render404Page() } return renderVueApp(event.path) }) ``` h2. [Dynamic Routes Considerations](#dynamic-routes-considerations) Dynamic routes (`/products/:id`) need data fetching to determine existence: ``` async function checkDynamicRoute(path: string): Promise { const match = path.match(/^\/products\/([^/]+)$/) if (!match) return false const productId = match[1] const exists = await productExists(productId) return exists } async function productExists(id: string): Promise { // Query database/API const product = await db.products.findById(id) return !!product } ``` Server-side route checking: ``` app.get('/products/:id', async (req, res) => { const exists = await productExists(req.params.id) if (!exists) { res.status(404).send(render404Page()) return } res.send(await renderProductPage(req.params.id)) }) ``` ``` app.use('/products/:id', async (req, res) => { const id = req.params.id const exists = await productExists(id) if (!exists) { res.status(404).send(await render404SSR()) return } res.send(await renderProductPage(id)) }) ``` ``` import { defineEventHandler, getRouterParam, setResponseStatus } from 'h3' export default defineEventHandler(async (event) => { const id = getRouterParam(event, 'id') const exists = await productExists(id) if (!exists) { setResponseStatus(event, 404) return render404Page() } return renderProductPage(id) }) ``` h2. [404 vs 410 Status Codes](#_404-vs-410-status-codes) **404 Not Found** - Resource doesn't exist, might never have existed, might come back: - User typos - Outdated external links - Deleted products that might return to inventory - Seasonal content (holiday pages) **410 Gone** - Resource existed, now permanently removed: - Discontinued products - Deleted blog posts (no redirect target) - Expired promotions - Intentionally removed content Google treats both similarly for indexing. removes from search results. 410 signals faster removal but rarely needed. Use 404 for most cases. h2. [Custom 404 Page Design](#custom-404-page-design) Good 404 pages keep users on your site: ``` ``` **Don't:** - Redirect all 404s to homepage (soft 404 risk) - Auto-redirect after countdown (bad UX) - Show only "404" with no explanation - Display technical error messages **Do:** - Explain what happened clearly - Provide search functionality - Link to popular/relevant pages - Match site design (keeps users oriented) - Include contact option for reporting broken links h2. [Crawl Budget Impact](#crawl-budget-impact) 404 errors have minimal crawl budget impact ([**Google Search Central**](https://developers.google.com/search/docs/crawling-indexing/http-network-errors)). Google expects them. Soft 404s waste crawl budget because Google recrawls pages thinking content exists. Large sites (10,000+ pages) should: - Monitor 404 rates in Search Console - Fix internal links pointing to 404s - Remove 404 URLs from [**sitemap**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/learn-seo/vue/controlling-crawlers/sitemaps) - Use 301 redirects for high-value deleted pages with relevant replacements Don't worry about occasional 404s from external links or user typos. h2. [Handling 404s for Deleted Content](#handling-404s-for-deleted-content) h3. [Content Moved](#content-moved) Use [**301 redirect**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/learn-seo/vue/controlling-crawlers/redirects) to new location: ``` app.get('/old-product', (req, res) => { res.redirect(301, '/new-product') }) ``` ``` app.use((req, res, next) => { if (req.path === '/old-product') { return res.redirect(301, '/new-product') } next() }) ``` ``` import { defineEventHandler, sendRedirect } from 'h3' export default defineEventHandler((event) => { if (event.path === '/old-product') { return sendRedirect(event, '/new-product', 301) } }) ``` h3. [Content Permanently Removed](#content-permanently-removed) Return 404 or 410. If similar content exists, redirect to relevant category: ``` // ✅ Good - redirect to relevant category app.get('/discontinued-product', (req, res) => { res.redirect(301, '/products/similar-items') }) // ✅ Also good - return 404 if no replacement app.get('/old-blog-post', (req, res) => { res.status(404).send(render404Page()) }) ``` ``` app.use((req, res, next) => { if (req.path === '/discontinued-product') { return res.redirect(301, '/products/similar-items') } if (req.path === '/old-blog-post') { res.status(404).send(render404Page()) return } next() }) ``` ``` import { defineEventHandler, sendRedirect, setResponseStatus } from 'h3' export default defineEventHandler((event) => { if (event.path === '/discontinued-product') { return sendRedirect(event, '/products/similar-items', 301) } if (event.path === '/old-blog-post') { setResponseStatus(event, 404) return render404Page() } }) ``` h2. [Testing 404 Responses](#testing-404-responses) Verify proper status codes before deploying: **Browser DevTools:** 1. Open Network tab 2. Navigate to non-existent URL 3. Check status code in response headers 4. Should show `404` not `200` **Command line:** ``` curl -I https://example.com/fake-page h1. Output should show: h1. HTTP/1.1 404 Not Found ``` **Google Search Console:** 1. Use URL Inspection tool 2. Enter 404 URL 3. "Request indexing" 4. Check if Google recognizes 404 status 5. Monitor "Page Indexing" report for soft 404 flags **Lighthouse:** Run Lighthouse audit, check "Crawling and Indexing" section for status code issues. h2. [Common Mistakes](#common-mistakes) h3. [Redirecting All 404s to Homepage](#redirecting-all-404s-to-homepage) Creates soft 404 risk. Google may ignore [**redirects**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/learn-seo/vue/controlling-crawlers/redirects) to irrelevant pages. ``` // ❌ Bad - mass redirect to homepage app.get('*', (req, res) => { res.redirect(301, '/') }) ``` Only redirect if replacement content is relevant. Otherwise return proper 404. h3. [Client-Side 404 Handling Only](#client-side-404-handling-only) JavaScript-rendered error pages return `200 OK` to search engines: ``` ``` Server sees `200 OK`, Google sees error content, flags soft 404. Set status server-side. h3. [Forgetting noindex Meta Tag](#forgetting-noindex-meta-tag) If 404 page accidentally returns `200 OK`, `noindex` prevents indexing: ``` ``` Safety net, not primary solution. Fix the status code. h3. [Not Monitoring 404 Patterns](#not-monitoring-404-patterns) Repeated 404s to same path indicate broken internal links or outdated external links. Check Search Console "Not Found" report monthly, fix internal links immediately. h2. [Using Nuxt?](#using-nuxt) Nuxt handles 404 errors automatically with the `error.vue` component and proper status codes. Check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/docs/nuxt-seo/getting-started/introduction) for built-in crawl budget optimization and error handling. [**Learn more in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/learn-seo/nuxt/routes-and-rendering) --- [**Hreflang & i18n** Set hreflang tags in Vue to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/learn-seo/vue/routes-and-rendering/i18n) [**Dynamic Routes** How to configure Vue Router dynamic route params, set per-route meta tags, and avoid duplicate content issues with URL parameters.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/404-pages/learn-seo/vue/routes-and-rendering/dynamic-routes) **On this page** - [Quick Setup](#quick-setup) - [Soft 404 Errors Explained](#soft-404-errors-explained) - [Checking Routes Server-Side](#checking-routes-server-side) - [Dynamic Routes Considerations](#dynamic-routes-considerations) - [404 vs 410 Status Codes](#_404-vs-410-status-codes) - [Custom 404 Page Design](#custom-404-page-design) - [Crawl Budget Impact](#crawl-budget-impact) - [Handling 404s for Deleted Content](#handling-404s-for-deleted-content) - [Testing 404 Responses](#testing-404-responses) - [Common Mistakes](#common-mistakes) - [Using Nuxt?](#using-nuxt) --- ### Hreflang Tags in Vue · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n Description: Set hreflang tags in Vue to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites. h1. **Hreflang Tags in Vue** Set hreflang tags in Vue to tell search engines which language version to show users. Avoid duplicate content penalties across multilingual sites. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - Hreflang tags tell search engines which language version to show users - All pages in a cluster must link to each other bidirectionally (return links) - Use `x-default` as fallback for users whose language isn't available Hreflang tags tell search engines which language version of your page to show users. Without them, Google might show your French content to English speakers or rank the wrong regional version. ``` ``` [**Google recommends hreflang**](https://developers.google.com/search/docs/specialty/international/localized-versions) when you have: - Same content in different languages - Regional variations (en-US vs en-GB) - Partial translations mixed with original language Hreflang is a signal, not a directive. Search engines can ignore it if they think a different version better matches user intent. h2. [Quick Reference](#quick-reference) ``` useHead({ link: [ { rel: 'alternate', hreflang: 'en', href: 'https://example.com/en' }, { rel: 'alternate', hreflang: 'fr', href: 'https://example.com/fr' }, { rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/en' } ] }) ``` ``` useSeoMeta({ alternateLanguages: { 'en': 'https://example.com/en', 'fr': 'https://example.com/fr', 'x-default': 'https://example.com/en' } }) ``` h2. [Hreflang Format](#hreflang-format) Hreflang values use [**ISO 639-1 language codes**](https://www.linguise.com/blog/guide/list-of-the-hreflang-language-codes-how-to-implement-them/) and optional [**ISO 3166-1 Alpha-2 region codes**](https://hreflang.org/what-is-a-valid-hreflang/): | **Format** | **Example** | **Use Case** | | --- | --- | --- | | Language only | `en` | English content for all regions | | Language + region | `en-US` | English for United States | | Language + region | `en-GB` | English for United Kingdom | | Special fallback | `x-default` | Default for unmatched languages/regions | Language codes must be lowercase. Region codes must be uppercase. Separate with hyphen: `en-US`. h3. [Common Examples](#common-examples) ``` en English (all regions) fr French (all regions) es Spanish (all regions) en-US English for USA en-GB English for UK fr-CA French for Canada es-MX Spanish for Mexico zh-CN Simplified Chinese (China) zh-TW Traditional Chinese (Taiwan) ``` h3. [Region Codes Are Tricky](#region-codes-are-tricky) [**UK is Ukraine**](https://www.weglot.com/blog/hreflang-language-codes), not United Kingdom. Use `GB` for Great Britain. [**Chinese uses zh-CN/zh-TW or zh-Hans/zh-Hant**](https://www.seroundtable.com/google-iso-3166-1-for-hreflang-24663.html) for simplified/traditional variants since ISO 639-1 only defines one `zh` code. h2. [Implementation in Vue](#implementation-in-vue) h3. [With Unhead](#with-unhead) Use [`useHead()`](https://unhead.unjs.io/) to set hreflang tags that work in SSR: ``` ``` h3. [With vue-i18n](#with-vue-i18n) If you're using [**vue-i18n**](https://vue-i18n.intlify.dev/) for translations, generate hreflang from available locales: ``` import { useI18n } from 'vue-i18n' const { locale, availableLocales } = useI18n() const route = useRoute() // Generate alternate links for all locales const alternateLinks = availableLocales.map(loc => ({ rel: 'alternate', hreflang: loc, href: \`https://example.com/${loc}${route.path}\` })) // Add self-referential link for current locale alternateLinks.push({ rel: 'alternate', hreflang: locale.value, href: \`https://example.com/${locale.value}${route.path}\` }) useHead({ link: alternateLinks }) ``` h3. [Creating a Composable](#creating-a-composable) Extract hreflang logic into a reusable composable: ``` // composables/useHreflang.ts export function useHreflang(locales: string[]) { const route = useRoute() const links = computed(() => { const baseUrl = 'https://example.com' return [ ...locales.map(locale => ({ rel: 'alternate', hreflang: locale, href: \`${baseUrl}/${locale}${route.path}\` })), // x-default to primary locale { rel: 'alternate', hreflang: 'x-default', href: \`${baseUrl}/${locales[0]}${route.path}\` } ] }) useHead({ link: links }) } ``` Use it in components: ``` ``` h2. [X-Default Tag](#x-default-tag) The `x-default` hreflang provides a fallback URL when no language matches the user's preferences. [**Google recommends x-default**](https://www.weglot.com/guides/hreflang-tag) for all hreflang clusters. ``` ``` h3. [When to Use X-Default](#when-to-use-x-default) Set x-default to: - Your primary language version - A language selector page - The most universally understood language version ``` // Point x-default to language selector useHead({ link: [ { rel: 'alternate', hreflang: 'en-US', href: 'https://example.com/en-us' }, { rel: 'alternate', hreflang: 'en-GB', href: 'https://example.com/en-gb' }, { rel: 'alternate', hreflang: 'fr-FR', href: 'https://example.com/fr-fr' }, { rel: 'alternate', hreflang: 'x-default', href: 'https://example.com/choose-language' } ] }) ``` [**According to Victorious**](https://victorious.com/blog/what-is-x-default/), x-default improves user experience by routing unmatched users to an appropriate fallback instead of a random language version. h2. [Hreflang Rules](#hreflang-rules) h3. [1. Bidirectional Links (Return Links)](#_1-bidirectional-links-return-links) Every page referenced in hreflang must link back. If page A links to page B, page B must link to page A. [**This is the most common hreflang error**](https://www.iloveseo.net/how-to-use-hreflang-correctly/). ``` ``` h3. [2. Self-Referential Links](#_2-self-referential-links) Each page must include a hreflang tag pointing to itself: ``` ``` h3. [3. Use Canonical URLs Only](#_3-use-canonical-urls-only) All hreflang URLs must: - Return HTTP 200 status - Be indexable (no `noindex`) - Not be blocked by robots.txt - Point to canonical URLs (not redirects) [**According to I Love SEO**](https://www.iloveseo.net/how-to-use-hreflang-correctly/), using non-200, non-indexable, or non-canonical URLs is the root cause of most hreflang mistakes. ``` ``` h3. [4. Canonical vs Hreflang](#_4-canonical-vs-hreflang) Canonical tags and hreflang serve different purposes. Don't use canonical to point between language versions. [**that signals duplicate content**](https://www.iloveseo.net/how-to-use-hreflang-correctly/), not translations. ``` ``` ``` ``` h2. [Server-Side Rendering Required](#server-side-rendering-required) Hreflang tags must exist in the initial HTML response. SPAs that render hreflang client-side won't work reliably: ``` ``` If you're building a SPA, see the [**prerendering guide**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n/learn-seo/vue/spa/prerendering) or [**SSR frameworks comparison**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n/learn-seo/vue/ssr-frameworks). h2. [Implementation Methods](#implementation-methods) You can implement hreflang using HTML `` tags, HTTP headers, or XML sitemaps. [**Best practice is to choose one method**](https://backlinko.com/hreflang-tag) and stick to it. Mixing methods creates conflicting signals. h3. [HTML Link Tags (Recommended)](#html-link-tags-recommended) Most flexible approach. Set per-page using Unhead: ``` ``` h3. [HTTP Headers](#http-headers) Useful for non-HTML resources (PDFs, etc.). Configure in your server: ``` app.get('/document.pdf', (req, res) => { res.set('Link', [ '; rel="alternate"; hreflang="en"', '; rel="alternate"; hreflang="fr"', '; rel="alternate"; hreflang="x-default"' ].join(', ')) res.sendFile('/path/to/document.pdf') }) ``` ``` export default defineEventHandler((event) => { setHeader(event, 'Link', [ '; rel="alternate"; hreflang="en"', '; rel="alternate"; hreflang="fr"', '; rel="alternate"; hreflang="x-default"' ].join(', ')) return sendStream(event, createReadStream('/path/to/document.pdf')) }) ``` ``` // server.ts server.use((req, res, next) => { if (req.url.endsWith('.pdf')) { res.setHeader('Link', [ '; rel="alternate"; hreflang="en"', '; rel="alternate"; hreflang="fr"', '; rel="alternate"; hreflang="x-default"' ].join(', ')) } next() }) ``` h3. [XML Sitemap](#xml-sitemap) Add hreflang to your sitemap if you can't modify HTML/headers: ``` https://example.com/en https://example.com/fr ``` h2. [Common Mistakes](#common-mistakes) h3. [Missing Return Links](#missing-return-links) ``` ``` h3. [Non-Canonical URLs](#non-canonical-urls) ``` ``` h3. [Noindex Pages](#noindex-pages) ``` ``` All pages in hreflang cluster must be indexable. Remove `noindex` or remove the page from hreflang annotations. h3. [Blocked by Robots.txt](#blocked-by-robotstxt) If a URL is blocked in `robots.txt`, crawlers can't access it to validate hreflang links. Ensure all alternate URLs are crawlable. h3. [Mixing with Canonical](#mixing-with-canonical) ``` ``` h2. [Testing Hreflang](#testing-hreflang) h3. [Manual Inspection](#manual-inspection) View page source and verify: - All alternate links are present - Self-referential link exists - x-default is set - All URLs return 200 - All URLs are canonical (not redirects) h3. [Google Search Console](#google-search-console) After implementing hreflang, check [**Google Search Console**](https://search.google.com/search-console) > International Targeting for errors: - Missing return tags - Incorrect language codes - Invalid URLs h3. [Third-Party Tools](#third-party-tools) - [**Hreflang Tags Testing Tool**](https://hreflang.org) - [**Merkle Hreflang Checker**](https://technicalseo.com/tools/hreflang/) - Search Console reports h2. [Full Example: Multilingual Vue App](#full-example-multilingual-vue-app) Here's a complete implementation with vue-i18n and Vue Router: ``` // router/index.ts import { createRouter, createWebHistory } from 'vue-router' const locales = ['en', 'fr', 'de', 'es'] const router = createRouter({ history: createWebHistory(), routes: locales.flatMap(locale => [ { path: \`/${locale}\`, component: () => import('@/pages/Home.vue'), meta: { locale } }, { path: \`/${locale}/products\`, component: () => import('@/pages/Products.vue'), meta: { locale } } ]) }) export default router ``` ``` // composables/useHreflang.ts export function useHreflang() { const route = useRoute() const { locale } = useI18n() const locales = ['en', 'fr', 'de', 'es'] const baseUrl = 'https://example.com' // Remove locale prefix to get path const pathWithoutLocale = route.path.replace(\`/${locale.value}\`, '') const links = computed(() => [ // All locale variants ...locales.map(loc => ({ rel: 'alternate', hreflang: loc, href: \`${baseUrl}/${loc}${pathWithoutLocale}\` })), // Current page (self-referential) { rel: 'alternate', hreflang: locale.value, href: \`${baseUrl}${route.path}\` }, // x-default fallback { rel: 'alternate', hreflang: 'x-default', href: \`${baseUrl}/en${pathWithoutLocale}\` } ]) useHead({ link: links }) } ``` ``` ``` h2. [Using Nuxt?](#using-nuxt) Nuxt's [**@nuxtjs/i18n**](https://i18n.nuxtjs.org/docs/guide/seo/) module automatically generates hreflang tags for all configured locales with zero configuration. It handles return links, self-referential tags, and x-default automatically. [**Learn more in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n/learn-seo/nuxt/routes-and-rendering) --- [**Query Parameters** Query parameters create duplicate content and waste crawl budget. Here's how to handle filters, sorting, and tracking params in Vue Router.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n/learn-seo/vue/routes-and-rendering/query-parameters) [**404 Pages** 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Vue applications.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/i18n/learn-seo/vue/routes-and-rendering/404-pages) **On this page** - [Quick Reference](#quick-reference) - [Hreflang Format](#hreflang-format) - [Implementation in Vue](#implementation-in-vue) - [X-Default Tag](#x-default-tag) - [Hreflang Rules](#hreflang-rules) - [Server-Side Rendering Required](#server-side-rendering-required) - [Implementation Methods](#implementation-methods) - [Common Mistakes](#common-mistakes) - [Testing Hreflang](#testing-hreflang) - [Full Example: Multilingual Vue App](#full-example-multilingual-vue-app) - [Using Nuxt?](#using-nuxt) --- ### Dynamic Routes in Vue for SEO · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes Description: How to configure Vue Router dynamic route params, set per-route meta tags, and avoid duplicate content issues with URL parameters. h1. **Dynamic Routes in Vue for SEO** How to configure Vue Router dynamic route params, set per-route meta tags, and avoid duplicate content issues with URL parameters. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - Dynamic params create semantic URLs. `/blog/:slug` instead of `/blog?id=123` - Set per-route meta tags by fetching data before render, not in `onMounted()` - Canonical URLs should strip query params to prevent duplicate content Dynamic routes generate clean URLs from parameters. `/blog/:slug` creates `/blog/vue-seo-guide` instead of `/blog?id=123`. Search engines prefer semantic paths over query parameters. Vue Router's [**dynamic route matching**](https://router.vuejs.org/guide/essentials/dynamic-matching) handles this automatically. Configure your routes once, set per-route meta tags, and Google indexes each page correctly. h2. [Route Params vs Query Parameters](#route-params-vs-query-parameters) Google treats these differently: ``` ✅ /products/electronics/laptop ❌ /products?category=electronics&item=laptop ``` Route params create semantic URLs. Query parameters generate duplicate content issues. Google sees infinite URL variations when you add filters, sorting, or tracking params. From [**Google's 2025 URL structure guidelines**](https://developers.google.com/search/docs/crawling-indexing/url-structure): use clean paths for important content, reserve query parameters for filters that shouldn't be indexed. h2. [Basic Dynamic Routes](#basic-dynamic-routes) Define routes with `:param` syntax: ``` import { createRouter, createWebHistory } from 'vue-router' const routes = [ { path: '/blog/:slug', component: BlogPost }, { path: '/products/:category/:id', component: Product } ] const router = createRouter({ history: createWebHistory(), routes }) ``` This generates: - `/blog/vue-ssr-guide` - `/products/electronics/123` Access params in components via `route.params`: ``` ``` h2. [Per-Route Meta Tags](#per-route-meta-tags) Search engines need unique titles and descriptions for each dynamic route. Set these with [**route meta fields**](https://router.vuejs.org/guide/advanced/meta.html) and Unhead. h3. [Static Meta for Route Groups](#static-meta-for-route-groups) Define generic meta in route config: ``` const routes = [ { path: '/blog/:slug', component: BlogPost, meta: { title: 'Blog', description: 'Read our latest articles' } } ] ``` Apply meta in a navigation guard: ``` router.afterEach((to) => { useHead({ title: to.meta.title as string, titleTemplate: '%s | MySite' }) }) ``` This works for basic cases but gives every blog post the same title ("Blog | MySite"). Override in components for dynamic content. h3. [Dynamic Meta from Data](#dynamic-meta-from-data) Fetch data and set specific meta tags per page: ``` ``` **SSR requirement:** Fetch data before render, not in `onMounted()`. Client-side fetches mean search engines see loading states. Use SSR-compatible patterns: ``` // server.ts import express from 'express' import { createSSRApp } from 'vue' import { renderToString } from 'vue/server-renderer' const app = express() app.get('/blog/:slug', async (req, res) => { const post = await fetchPost(req.params.slug) const vueApp = createSSRApp({ setup() { useHead({ title: post.title, meta: [{ name: 'description', content: post.excerpt }] }) } }) const html = await renderToString(vueApp) res.send(html) }) ``` ``` // server.js import { createSSRApp } from 'vue' import { renderToString } from 'vue/server-renderer' export async function render(url, manifest) { const app = createSSRApp(App) // Prefetch data on server await router.push(url) await router.isReady() const post = await fetchPost(router.currentRoute.value.params.slug) app.use(head) useHead({ title: post.title, meta: [{ name: 'description', content: post.excerpt }] }) const html = await renderToString(app) return { html } } ``` ``` // server/routes/blog/[slug].get.ts import { defineEventHandler } from 'h3' export default defineEventHandler(async (event) => { const slug = event.context.params.slug const post = await fetchPost(slug) const app = createSSRApp({ setup() { useHead({ title: post.title, meta: [{ name: 'description', content: post.excerpt }] }) } }) return renderToString(app) }) ``` Read the [**SSR rendering guide**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes/learn-seo/vue/routes-and-rendering/rendering) for full SSR setup. h2. [Multiple Route Params](#multiple-route-params) Combine params for hierarchical URLs: ``` const routes = [ { path: '/docs/:category/:page', component: DocPage } ] ``` Generates: `/docs/getting-started/installation` Access all params: ``` ``` h2. [Optional Params](#optional-params) Make params optional with `?`: ``` const routes = [ { path: '/blog/:category?/:slug', component: BlogPost } ] ``` Matches both: - `/blog/vue-guide` (category undefined) - `/blog/tutorials/vue-guide` (category = "tutorials") Handle undefined params in components: ``` const route = useRoute() const category = route.params.category || 'general' ``` h2. [Catch-All Routes](#catch-all-routes) Use `*` or `:pathMatch(.*)*` for 404 pages: ``` const routes = [ { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound } ] ``` Set appropriate meta for 404s: ``` ``` h2. [Programmatic Navigation](#programmatic-navigation) Navigate to dynamic routes programmatically: ``` ``` Named routes prevent typos: ``` const routes = [ { path: '/products/:category/:id', name: 'Product', component: Product } ] // Type-safe with route names router.push({ name: 'Product', params: { category: 'electronics', id: 123 } }) ``` h2. [Common SEO Issues](#common-seo-issues) h3. [Issue 1: Duplicate Content from Query Params](#issue-1-duplicate-content-from-query-params) Mixing route params and query params creates duplicates: ``` /blog/vue-guide /blog/vue-guide?ref=twitter /blog/vue-guide?utm_source=newsletter ``` Google indexes these as separate pages. Fix with canonical tags: ``` ``` Or use [**Google's URL parameter handling**](https://developers.google.com/search/docs/crawling-indexing/url-structure) via `robots.txt`: ``` User-agent: * Disallow: /*?ref=* Disallow: /*?utm_* ``` h3. [Issue 2: Missing Titles on Dynamic Routes](#issue-2-missing-titles-on-dynamic-routes) Generic titles hurt SEO: ``` Blog | MySite ``` Always override with specific content: ``` ``` h3. [Issue 3: Client-Side Rendering](#issue-3-client-side-rendering) SPA routing means search engines see your loading state: ``` Loading...

Loading...

``` Use SSR or [**prerendering**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes/learn-seo/vue/spa/prerendering) to ship complete HTML. Google [**can render JavaScript**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) but it's slower and less reliable. h3. [Issue 4: Infinite Parameter Variations](#issue-4-infinite-parameter-variations) Dynamic routes with filters create crawl budget waste: ``` // ❌ Generates thousands of URLs /products/:category?sort=price /products/:category?sort=name /products/:category?color=red&sort=price // ... infinite combinations ``` Use `noindex` on filtered pages or block in `robots.txt`: ``` router.afterEach((to) => { // Noindex pages with query params if (Object.keys(to.query).length > 0) { useHead({ meta: [ { name: 'robots', content: 'noindex, follow' } ] }) } }) ``` Read more about [**controlling crawlers**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes/learn-seo/vue/controlling-crawlers). h2. [Route Params Table](#route-params-table) Common dynamic route patterns: | **Pattern** | **Matches** | **Params** | **Use Case** | | --- | --- | --- | --- | | `/blog/:slug` | `/blog/vue-guide` | `{ slug: 'vue-guide' }` | Blog posts, articles | | `/products/:id` | `/products/123` | `{ id: '123' }` | Product pages | | `/docs/:category/:page` | `/docs/api/methods` | `{ category: 'api', page: 'methods' }` | Documentation | | `/user/:id(\\d+)` | `/user/42` | `{ id: '42' }` | User profiles (numeric IDs only) | | `/:lang/about` | `/en/about` | `{ lang: 'en' }` | Internationalized routes | | `/files/:path(.*)` | `/files/docs/api.md` | `{ path: 'docs/api.md' }` | Nested file paths | h2. [Regex Constraints](#regex-constraints) Validate params with regex: ``` const routes = [ { // Only match numeric IDs path: '/user/:id(\\d+)', component: User }, { // Only match valid slugs (lowercase, hyphens) path: '/blog/:slug([a-z0-9-]+)', component: BlogPost } ] ``` Invalid params result in 404, preventing indexing of malformed URLs. h2. [Navigation Guards for Meta](#navigation-guards-for-meta) Set global meta patterns in guards: ``` router.beforeEach((to, from) => { // Set default meta for all routes useHead({ titleTemplate: '%s | MySite', meta: [ { property: 'og:site_name', content: 'MySite' }, { name: 'twitter:card', content: 'summary_large_image' } ] }) }) router.afterEach((to) => { // Apply route-specific meta if (to.meta.title) { useHead({ title: to.meta.title as string }) } }) ``` Components can override guard defaults with their own `useHead()` calls. Unhead merges them with component-level taking precedence. h2. [TypeScript Support](#typescript-support) Type route params and meta: ``` import 'vue-router' declare module 'vue-router' { interface RouteMeta { title?: string description?: string requiresAuth?: boolean } } // Type-safe route params interface BlogParams { slug: string } const route = useRoute() const slug: string = route.params.slug // Typed ``` h2. [Verification](#verification) Check what Google indexes: 1. **View Page Source** (not Inspect Element): Right-click → View Page Source. Should show complete HTML with title and meta tags. 2. **Google Search Console URL Inspection**: Test Live URL → View HTML. If you see `Loading...`, your SSR isn't working. 3. **Curl test**: ``` curl https://yoursite.com/blog/vue-guide | grep "" ``` Should return full title tag, not empty or "Loading". h2. [Using Nuxt?](#using-nuxt) Nuxt handles dynamic routes automatically with file-based routing. `pages/blog/[slug].vue` creates the route, `useSeoMeta()` sets tags, and SSR works out of the box. [**Learn more in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes/learn-seo/nuxt/routes-and-rendering) --- [**404 Pages** 404 errors don't hurt SEO, but soft 404s do. Learn proper HTTP status codes, custom 404 design, and crawl budget optimization for Vue applications.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes/learn-seo/vue/routes-and-rendering/404-pages) [**Rendering Modes** How SPA, SSR, and SSG affect Google indexing. Which mode to use and when.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/dynamic-routes/learn-seo/vue/routes-and-rendering/rendering) **On this page** - [Route Params vs Query Parameters](#route-params-vs-query-parameters) - [Basic Dynamic Routes](#basic-dynamic-routes) - [Per-Route Meta Tags](#per-route-meta-tags) - [Multiple Route Params](#multiple-route-params) - [Optional Params](#optional-params) - [Catch-All Routes](#catch-all-routes) - [Programmatic Navigation](#programmatic-navigation) - [Common SEO Issues](#common-seo-issues) - [Route Params Table](#route-params-table) - [Regex Constraints](#regex-constraints) - [Navigation Guards for Meta](#navigation-guards-for-meta) - [TypeScript Support](#typescript-support) - [Verification](#verification) - [Using Nuxt?](#using-nuxt) --- ### Rendering Modes in Vue: SPA, SSR, SSG · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering Description: How SPA, SSR, and SSG affect Google indexing. Which mode to use and when. h1. **Rendering Modes in Vue: SPA, SSR, SSG** How SPA, SSR, and SSG affect Google indexing. Which mode to use and when. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw) Published **Oct 25, 2024** Updated **Dec 5, 2024** **What you'll learn** - SPA adds 5-10s to indexing time. avoid for anything you want in Google - SSR is best for dynamic, frequently updated content - SSG is fastest for blogs, docs, and marketing pages with infrequent updates Google can render JavaScript. But it's slower and less reliable than HTML. Choose the wrong rendering mode and your content might not get indexed. h2. [Rendering Modes in Vue](#rendering-modes-in-vue) Vue applications support three main rendering strategies: **SPA (Single Page Application)** - JavaScript renders everything client-side **SSR (Server-Side Rendering)** - Server sends HTML, JavaScript hydrates **SSG (Static Site Generation)** - Pre-render HTML at build time ![SSR vs SSG vs SPA Comparison](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering/images/learn-seo/vue/ssr-vs-ssg-vs-spa.svg) h2. [SPA: Client-Side Rendering](#spa-client-side-rendering) Default behavior for Vue + Vite projects. ``` import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app') ``` Google's crawler downloads JavaScript, waits for execution, then indexes. This adds 5-10 seconds to indexing time. Use it for internal dashboards, apps behind authentication, content that shouldn't be indexed. Avoid it for marketing pages, blog posts, product catalogs. anything you want in Google. **Common mistake:** Launching a SPA site expecting Google to index it immediately. You'll wait weeks longer than SSR. h2. [SSR: Server-Side Rendering](#ssr-server-side-rendering) Server generates HTML on each request. Use Vite SSR or framework solutions like Quasar. **Basic Vite SSR setup:** ``` // server.js import { createSSRApp } from 'vue' import { renderToString } from 'vue/server-renderer' app.get('*', async (req, res) => { const app = createSSRApp({ // your app }) const html = await renderToString(app) res.send(\` <!DOCTYPE html> <html> <body> <div id="app">${html}</div> </body> </html> \`) }) ``` ✅ **Good for:** - Dynamic content (user profiles, dashboards) - Personalized pages - Real-time data - Content updated frequently ❌ **Don't use for:** - Static blogs (SSG is faster) - Documentation (SSG is cheaper) - Landing pages (SSG serves faster) **Performance note:** SSR requires server compute on every request. SSG serves from CDN. h2. [SSG: Static Site Generation](#ssg-static-site-generation) HTML generated once at build time. Use tools like VitePress or Vite SSG plugin. **Vite SSG example:** ``` import vue from '@vitejs/plugin-vue' // vite.config.ts import { defineConfig } from 'vite' import ViteSSG from 'vite-ssg' export default defineConfig({ plugins: [ vue(), ViteSSG({ routes: [ { path: '/', component: Home }, { path: '/blog/:slug', component: BlogPost } ] }) ] }) ``` ✅ **Good for:** - Blogs - Documentation - Marketing pages - Product catalogs - Content updated daily or less ❌ **Don't use for:** - User-specific content - Real-time dashboards - Content changing hourly - Sites with 10,000+ dynamic pages h2. [Verifying What Google Sees](#verifying-what-google-sees) Don't guess. Check what Googlebot actually received: **1. Google Search Console URL Inspection** - Go to URL Inspection tool - Enter your URL - Click "Test Live URL" - View "Screenshot" and "HTML" tabs If the HTML tab shows `<div id="app"></div>` with no content, Google isn't seeing your page. **2. View Page Source** Right-click page > "View Page Source" (not Inspect Element) ✅ **Good:** Full content in HTML ❌ **Bad:** Empty div with JavaScript **3. Fetch as Google** ``` curl -A "Mozilla/5.0 (compatible; Googlebot/2.1)" https://yoursite.com ``` Should return complete HTML with content. h2. [Common Mistakes](#common-mistakes) **Mistake 1: Using SPA for a blog** You're making Google work 10x harder. SSG renders in milliseconds. **Mistake 2: SSR for static documentation** Why run server compute for content that never changes? SSG is free to serve. **Mistake 3: SSG for 100,000 product pages** Your builds will time out. Use SSR instead. **Mistake 4: Not testing the output** Always verify with Search Console. Your local dev server lies. h2. [Default Recommendation](#default-recommendation) For content-heavy sites that need SEO: - Use **SSG** (VitePress, Vite SSG) for blogs, docs, marketing - Use **SSR** (Vite SSR, Quasar) for dynamic, frequently updated content - Use **SPA** only for authenticated apps or internal tools Avoid SPA for anything you want Google to index quickly. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering/docs/nuxt-seo/getting-started/introduction) for built-in SEO features. Nuxt provides sophisticated rendering controls with `routeRules`, hybrid rendering, and ISR (Incremental Static Regeneration). [**Learn more in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering/learn-seo/nuxt/routes-and-rendering/rendering) --- [**Dynamic Routes** How to configure Vue Router dynamic route params, set per-route meta tags, and avoid duplicate content issues with URL parameters.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering/learn-seo/vue/routes-and-rendering/dynamic-routes) [**Security** Robots.txt is a polite suggestion. Malicious crawlers ignore it. Here's how to actually protect your Vue app.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/rendering/learn-seo/vue/routes-and-rendering/security) **On this page** - [Rendering Modes in Vue](#rendering-modes-in-vue) - [SPA: Client-Side Rendering](#spa-client-side-rendering) - [SSR: Server-Side Rendering](#ssr-server-side-rendering) - [SSG: Static Site Generation](#ssg-static-site-generation) - [Verifying What Google Sees](#verifying-what-google-sees) - [Common Mistakes](#common-mistakes) - [Default Recommendation](#default-recommendation) - [Using Nuxt?](#using-nuxt) --- ### Getting Your Vue Site Indexed · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live Description: How to get your Vue site crawled and indexed for the first time by Google. h1. **Getting Your Vue Site Indexed** How to get your Vue site crawled and indexed for the first time by Google. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)6 mins read Published **Oct 25, 2024** Updated **Dec 17, 2025** **What you'll learn** - Set up Google Search Console and submit sitemap before launch - Request indexing for homepage and critical pages (10 requests/day limit) - Check Core Web Vitals scores. LCP ≤2.5s, INP ≤200ms, CLS ≤0.1 Deployed your Vue site to production? Two steps remain: get Google to crawl it, then get Google to index it. h2. [SSR vs SPA: The Indexing Reality](#ssr-vs-spa-the-indexing-reality) If your Vue app is a Single Page Application (SPA), [**Google needs to execute JavaScript**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) to see your content. This delays indexing. sometimes by weeks. **For faster, more reliable indexing:** - Use SSR with [**Vite SSR**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/ssr-frameworks/vite-ssr) or [**Nuxt**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar) - [**Pre-render critical pages**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/spa/prerendering) at build time - Set meta tags before hydration using [**Unhead**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/mastering-meta) If staying with SPA, use [**dynamic rendering**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/spa/dynamic-rendering) or a service like Prerender.io. Test your pages with Google's URL Inspection tool to verify content is visible. h2. [Canonical URL Configuration](#canonical-url-configuration) Multiple domains or subdomains pointing to your site? Only one version should be indexed. Example: `www.example.com` and `example.com` both serve your app, but only `example.com` should appear in Google. **Solutions:** 1. **Server-level redirect** (preferred): 301 redirect all non-canonical URLs 2. **Canonical tags**: Tell Google which version is authoritative ``` import { useHead } from '@unhead/vue' import { useRoute } from 'vue-router' const route = useRoute() useHead({ link: [ { rel: 'canonical', href: \`https://example.com${route.path}\` } ] }) ``` See [**Canonical URLs guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/controlling-crawlers/canonical-urls) for implementation details. h2. [Set Up Google Search Console](#set-up-google-search-console) [**Google Search Console**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/search-console) is required for monitoring indexing. Set it up before launch: 1. Visit [**search.google.com/search-console**](https://search.google.com/search-console) 2. Add your property (Domain property recommended) 3. Verify ownership via DNS, HTML file, or meta tag 4. Submit your [**sitemap**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/controlling-crawlers/sitemaps) at **Indexing > Sitemaps** For Vue apps, generate a sitemap using: - Static file in `public/` directory - Build-time generation with [**vite-plugin-sitemap**](https://github.com/jbaubree/vite-plugin-sitemap) - Server-side generation if using SSR h2. [Request Indexing](#request-indexing) After sitemap submission, request indexing for important pages: **Manual method:** 1. Open [**URL Inspection**](https://support.google.com/webmasters/answer/9012289) in Search Console 2. Enter your URL 3. Click **Request Indexing** You get [**~10 requests per day**](https://support.google.com/webmasters/answer/9012289). Use them for homepage and critical pages. **Bulk method:** Use [**RequestIndexing**](https://requestindexing.com/) by @harlan_zw to submit multiple URLs automatically. **Instant notification (Bing/Yandex):**[**IndexNow**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/indexnow) notifies search engines immediately when content changes. Google doesn't support it, but Bing and Yandex do. h2. [Core Web Vitals Check](#core-web-vitals-check) [**Google uses Core Web Vitals**](https://developers.google.com/search/docs/appearance/core-web-vitals) as a ranking signal. Check your scores before launch: | **Metric** | **Good** | **Poor** | | --- | --- | --- | | LCP (Largest Contentful Paint) | ≤2.5s | >4s | | INP (Interaction to Next Paint) | ≤200ms | >500ms | | CLS (Cumulative Layout Shift) | ≤0.1 | >0.25 | Use [**PageSpeed Insights**](https://pagespeed.web.dev/) or Lighthouse to test. Don't chase perfect scores. fix red flags and move on. See [**Core Web Vitals for Vue**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/core-web-vitals) for optimization techniques. h2. [Lighthouse SEO Audit](#lighthouse-seo-audit) Run Lighthouse on your key pages. Focus on the **SEO** and **Accessibility** categories. they catch issues Google cares about: - Missing meta descriptions - Images without alt text - Missing lang attribute - Low contrast text - Non-crawlable links Use [**Unlighthouse**](https://unlighthouse.dev/) to audit your entire site in bulk. h2. [Build Initial Backlinks](#build-initial-backlinks) New sites have zero authority. Google is skeptical of them. Signal legitimacy with a few quality backlinks: - Share on Twitter/X, LinkedIn, Reddit (relevant subreddits) - Submit to industry directories - Write guest posts on established sites - Build open-source tools that get linked Quality over quantity. One link from a respected site beats 100 from spam directories. h2. [Common Vue-Specific Issues](#common-vue-specific-issues) **Meta tags not updating on navigation:** - Use `@unhead/vue` with reactive values - Verify tags appear in **View Page Source** (not DevTools) - Check [**Mastering Meta**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/mastering-meta) guides **SPA content not indexed:** - Google may not execute JavaScript properly - Use [**URL Inspection**](https://support.google.com/webmasters/answer/9012289) to see rendered HTML - Consider SSR or [**prerendering**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/spa/prerendering) - See [**Indexing Issues**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/indexing-issues) for debugging **Slow Time to First Byte (TTFB):** - Optimize server response time - Use a CDN for static assets - Consider static hosting for SPA builds - Check [**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/core-web-vitals) for LCP fixes **Pages "Crawled - currently not indexed":** - Content may be too thin or duplicate - Site may lack authority (needs backlinks) - See [**Debugging Indexing Issues**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/indexing-issues) h2. [After Launch](#after-launch) 1. Check [**Search Console**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/search-console) weekly for errors 2. Monitor [**Core Web Vitals**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/core-web-vitals) in field data 3. Track organic traffic with [**SEO Monitoring tools**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/seo-monitoring) 4. Keep publishing content and building backlinks SEO is a long game. Most sites take 3-6 months to see meaningful organic traffic. Don't panic if rankings don't appear immediately. h2. [Using Nuxt?](#using-nuxt) Nuxt SEO handles sitemap generation, robots.txt, OG images, and many SEO tasks automatically. **Pre-Launch SEO Checklist** - Set up Google Search Console and verify ownership - Generate and submit XML sitemap - Verify meta tags appear in View Source (not just DevTools) - Check canonical URLs point to preferred domain version - Run Lighthouse SEO and accessibility audits - Test Core Web Vitals with PageSpeed Insights - Request indexing for homepage and key pages - Build initial backlinks from social and directories [**Learn more about going live with Nuxt →**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/nuxt/launch-and-listen/going-live) --- [**Launch & Listen** Deploy your Vue site to production and monitor its indexing status in search engines.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen) [**Google Search Console** Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/going-live/learn-seo/vue/launch-and-listen/search-console) **On this page** - [SSR vs SPA: The Indexing Reality](#ssr-vs-spa-the-indexing-reality) - [Canonical URL Configuration](#canonical-url-configuration) - [Set Up Google Search Console](#set-up-google-search-console) - [Request Indexing](#request-indexing) - [Core Web Vitals Check](#core-web-vitals-check) - [Lighthouse SEO Audit](#lighthouse-seo-audit) - [Build Initial Backlinks](#build-initial-backlinks) - [Common Vue-Specific Issues](#common-vue-specific-issues) - [After Launch](#after-launch) - [Using Nuxt?](#using-nuxt) --- ### Set Up Google Search Console for Your Vue Site · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console Description: Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console. h1. **Set Up Google Search Console for Your Vue Site** Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** **What you'll learn** - Use Domain property (not URL-prefix) for comprehensive tracking across subdomains - DNS verification is most reliable. don't delete the TXT record after verification - Submit sitemap immediately after verification and check status in 24-48 hours [**Google Search Console**](https://search.google.com/search-console) shows which pages Google indexed, what queries bring traffic, and what's broken. Every site needs this. without it you're blind to [**80% of indexing issues**](https://developers.google.com/search/docs/monitor-debug/search-console-start). h2. [Create a Search Console Property](#create-a-search-console-property) Visit [**search.google.com/search-console**](https://search.google.com/search-console) and add a property. You get two types: **Domain property** (`example.com`): - Includes all subdomains (`www`, `m`, `blog`) - Covers both HTTP and HTTPS - Requires DNS verification only **URL-prefix property** (`https://example.com`): - Single protocol and subdomain - Multiple verification methods - Better for subsections or testing [**Use Domain property**](https://support.google.com/webmasters/answer/9008080) unless you only control a subdomain. It captures all traffic variations without managing separate properties. h2. [Verification Methods](#verification-methods) Google needs proof you own the site. Pick one method and don't remove it. Google [**checks verification periodically**](https://www.bluehost.com/blog/verify-website-ownership-google-search-console/). h3. [DNS Verification (Recommended)](#dns-verification-recommended) Add a TXT record to your domain's DNS settings. This is the only method for Domain properties and the most reliable overall. **Steps:** 1. Google provides a TXT record like `google-site-verification=abc123xyz` 2. Add it to your DNS at your registrar (GoDaddy, Namecheap, Cloudflare, etc.) 3. Click **Verify** in Search Console DNS changes take up to 48 hours but verification works immediately once the record propagates. This method [**eliminates re-verification**](https://kinsta.com/blog/google-site-verification/) if you switch between `www` and non-`www` URLs. ``` Type: TXT Name: @ Content: google-site-verification=abc123xyz TTL: Auto ``` ``` Record type: TXT Name: example.com Value: "google-site-verification=abc123xyz" TTL: 300 ``` h3. [HTML Meta Tag](#html-meta-tag) Add a `<meta>` tag to your site's homepage `<head>`. Works for URL-prefix properties. app.vue ``` import { useHead } from '@unhead/vue' useHead({ meta: [ { name: 'google-site-verification', content: 'abc123xyz' } ] }) ``` The tag must appear in the HTML source before hydration. check with **View Page Source** (not DevTools). [**SPA sites need SSR or pre-rendering**](https://www.seozoom.com/google-website-verification/) for this to work since Google checks the initial HTML response. Common issue: If you use a layout component for the tag, make sure it renders on every page including the homepage. h3. [HTML File Upload](#html-file-upload) Upload `google-site-verification.html` to your site's root directory. Works for URL-prefix properties. **Steps:** 1. Download the verification file from Search Console 2. Place it in your `public/` directory (Vite/Vue CLI) or static root 3. Verify it loads at `https://example.com/google-site-verification.html` 4. Click **Verify** in Search Console Don't delete this file after verification. Google re-checks it. h3. [Google Analytics](#google-analytics) If you already use Google Analytics with the GA4 tracking code on your homepage, Search Console can verify via that tag. **Requirements:** - GA4 property set up - Tracking code in `<head>` section - You have "Edit" permission on the GA4 property [**This is the fastest method**](https://medium.com/@makarenko.roman121/how-do-you-verify-a-site-in-google-search-console-e0d179eb4ce9) if you already have analytics. no code changes needed. h3. [Google Tag Manager](#google-tag-manager) Same concept as Google Analytics. If you have GTM installed and publishing, Search Console auto-detects it for verification. **Requirements:** - GTM container installed on homepage - You have "Publish" permission on the container h2. [Submit Your Sitemap](#submit-your-sitemap) After verification, tell Google where to find your pages: 1. Navigate to **Sitemaps** under **Indexing** 2. Enter your sitemap URL: `https://example.com/sitemap.xml` 3. Click **Submit** Status meanings: - **Success**. sitemap parsed, URLs discovered - **Couldn't fetch**. URL wrong or [**blocked by robots.txt**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) - **Pending**. processing in queue Check back in 24-48 hours for discovered page counts. If Google found 0 URLs but your sitemap has URLs, your XML syntax is broken or the URLs return non-200 status codes. You can [**reference your sitemap in robots.txt**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console/learn-seo/vue/controlling-crawlers/robots-txt) as an alternative submission method. Google auto-discovers it when crawling. h2. [Key Reports Explained](#key-reports-explained) h3. [Performance Report](#performance-report) Shows the last 16 months of search data: **Metrics:** - **Impressions**. how many times your pages appeared in search results - **Clicks**. how many users clicked through - **CTR (Click-Through Rate)**. clicks ÷ impressions - **Position**. average ranking position (lower is better) Filter by: - **Queries**. what terms triggered your pages - **Pages**. which URLs get the most traffic - **Countries**. geographic breakdown - **Devices**. mobile vs desktop vs tablet Use this to find [**low-CTR pages with high impressions**](https://www.taylorscherseo.com/blog/google-search-console-audit). they rank well but have poor titles or descriptions. Fix the meta tags and watch CTR climb. h3. [Page Indexing Report](#page-indexing-report) Replaced the old Coverage report in 2025. Shows indexing status for all discovered URLs: **Categories:** - **Indexed**. pages in Google's index - **Not indexed**. pages excluded or rejected - **Crawled - currently not indexed**. pages Google saw but chose not to index Click any category to see specific reasons like "Duplicate content", "Soft 404", or "Crawled but not indexed". The [**June 2025 core update**](https://www.getpassionfruit.com/blog/why-website-pages-are-getting-de-indexed-after-june-2025-google-core-update-complete-recovery-guide) aggressively deindexed low-quality pages. this report shows the damage. Common issues for Vue sites: - **Page not found (404)**. sitemap lists URLs that don't exist - **Soft 404**. page returns 200 but contains "not found" content - **Redirect**. sitemap lists old URLs that redirect to new ones - **Blocked by robots.txt**. you accidentally blocked indexable pages h3. [URL Inspection Tool](#url-inspection-tool) Enter any URL to see: - Whether it's indexed - When Google last crawled it - Rendered HTML (what Googlebot saw after executing JavaScript) - Screenshot of the rendered page - Coverage errors or warnings - Mobile usability issues - Structured data found Click **Request Indexing** to ask Google to crawl the URL immediately. You get [**~10 requests per day**](https://support.google.com/webmasters/answer/9012289). use them for new or updated critical pages. The rendered screenshot is gold for debugging SPA issues. if it's blank or shows loading spinners, Google couldn't execute your JavaScript properly. h2. [Common Actions](#common-actions) h3. [Request Indexing](#request-indexing) After publishing new content: 1. Open **URL Inspection** tool 2. Enter the new URL 3. Click **Request Indexing** [**Google typically crawls within hours**](https://developers.google.com/search/docs/monitor-debug/search-console-start) but sometimes takes days. Requesting indexing doesn't guarantee it. Google still evaluates quality. h3. [Remove URLs Temporarily](#remove-urls-temporarily) Need to hide a URL from search results quickly? 1. Navigate to **Removals** under **Indexing** 2. Click **New Request** 3. Enter the URL 4. Choose removal type Removals last [**approximately 6 months**](https://support.google.com/webmasters/answer/9689846). For permanent removal, return 404/410 status or add `noindex` meta tag, then request removal. h3. [Security Issues](#security-issues) If Google detects malware or hacking, you'll see alerts in Search Console. Fix the issue first (remove malware, update plugins, patch vulnerabilities), then request a security review under **Security Issues**. Ignore these warnings and Google may deindex your entire site to protect users. h2. [Common Search Console Issues](#common-search-console-issues) **"Index Coverage report delayed since Nov 2025":** [**This is a known bug**](https://www.webpronews.com/google-search-console-index-coverage-report-delayed-since-nov-2025/). Google confirmed it's a reporting issue only. actual crawling and indexing still work. Use the URL Inspection tool for real-time checks on individual pages. **Sitemap shows "Discovered - currently not indexed":** Google found the URLs but chose not to index them. Reasons include: - Low quality content - Duplicate content - Thin content (under 300 words) - E-E-A-T signals too weak ([**June 2025 update issue**](https://www.getpassionfruit.com/blog/why-website-pages-are-getting-de-indexed-after-june-2025-google-core-update-complete-recovery-guide)) Fix content quality issues before re-submitting. Don't spam "Request Indexing". it doesn't override quality filters. **Verification suddenly fails:** You accidentally removed the verification method. Common causes: - Deleted DNS record during domain migration - Removed meta tag when refactoring `<head>` tags - Deleted HTML verification file during deploy - Lost GTM/GA permissions Re-verify using the original method. h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console/docs/nuxt-seo/getting-started/introduction) which provides automated sitemap generation, robots.txt, and OG images. [**Learn more about Search Console in Nuxt →**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console/learn-seo/nuxt/launch-and-listen) --- [**Getting Indexed** How to get your Vue site crawled and indexed for the first time by Google.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console/learn-seo/vue/launch-and-listen/going-live) [**Core Web Vitals** Measure and optimize LCP, INP, and CLS in Vue apps to improve user experience and search rankings.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/search-console/learn-seo/vue/launch-and-listen/core-web-vitals) **On this page** - [Create a Search Console Property](#create-a-search-console-property) - [Verification Methods](#verification-methods) - [Submit Your Sitemap](#submit-your-sitemap) - [Key Reports Explained](#key-reports-explained) - [Common Actions](#common-actions) - [Common Search Console Issues](#common-search-console-issues) - [Using Nuxt?](#using-nuxt) --- ### Core Web Vitals for Vue Applications · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/core-web-vitals Description: Measure and optimize LCP, INP, and CLS in Vue apps to improve user experience and search rankings. h1. **Core Web Vitals for Vue Applications** Measure and optimize LCP, INP, and CLS in Vue apps to improve user experience and search rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - Target thresholds: LCP ≤2.5s, INP ≤200ms, CLS ≤0.1 - Core Web Vitals account for 10-15% of ranking signals. only 47% of sites pass all three - Use PageSpeed Insights for field data (what Google uses) and Lighthouse for debugging [**Core Web Vitals**](https://developers.google.com/search/docs/appearance/core-web-vitals) became a Google ranking factor in June 2021. The metrics measure loading performance, interactivity, and visual stability. aspects that directly impact user experience and search rankings. h2. [The Three Metrics](#the-three-metrics) h3. [LCP (Largest Contentful Paint)](#lcp-largest-contentful-paint) LCP measures how long it takes for the largest visible element to render. This is typically a hero image, heading, or large text block. **Thresholds:** - Good: ≤ 2.5 seconds - Needs improvement: 2.5–4.0 seconds - Poor: > 4.0 seconds [**Google's research**](https://web.dev/blog/inp-cwv) shows sites with good LCP see 24% lower bounce rates. h3. [INP (Interaction to Next Paint)](#inp-interaction-to-next-paint) INP replaced First Input Delay (FID) in [**March 2024**](https://web.dev/blog/inp-cwv-march-12). FID only measured the first interaction. INP measures all interactions throughout the page lifecycle. clicks, taps, keyboard presses. **Thresholds:** - Good: ≤ 200 milliseconds - Needs improvement: 200–500 milliseconds - Poor: > 500 milliseconds [**Chrome usage data**](https://web.dev/blog/inp-cwv) shows users spend 90% of their time on a page after it loads, making responsiveness throughout the session more important than first-interaction metrics. h3. [CLS (Cumulative Layout Shift)](#cls-cumulative-layout-shift) CLS measures visual stability. Every unexpected layout shift gets scored. images loading without dimensions, fonts causing text reflow, dynamic content insertion. **Thresholds:** - Good: ≤ 0.1 - Needs improvement: 0.1–0.25 - Poor: > 0.25 h2. [Vue LCP Optimizations](#vue-lcp-optimizations) h3. [Avoid Client-Side Rendering for Content](#avoid-client-side-rendering-for-content) Pure client-side rendering hurts LCP. The browser downloads JavaScript, parses it, executes it, then renders content. [**Vue's official performance guide**](https://vuejs.org/guide/best-practices/performance.html) states: "If your use case is sensitive to page load performance, avoid shipping it as a pure client-side SPA." Use SSR or static site generation for content-heavy pages. h3. [Preload Critical Assets](#preload-critical-assets) Preload the LCP element's resources in your `index.html`: ``` <head> <link rel="preload" href="/hero-image.jpg" as="image"> <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin> </head> ``` h3. [Lazy Load Below-Fold Images](#lazy-load-below-fold-images) Load only what's visible on initial render: ``` <template> <div> <!-- Above-fold: eager loading --> <img src="/hero.jpg" alt="Hero" width="1200" height="600"> <!-- Below-fold: lazy loading --> <img src="/product-1.jpg" alt="Product" width="800" height="600" loading="lazy" > </div> </template> ``` Always specify width and height to prevent CLS. h3. [Optimize Font Loading](#optimize-font-loading) Use `font-display: swap` to show system fonts immediately while web fonts load: ``` @font-face { font-family: 'Inter'; src: url('/fonts/inter-var.woff2') format('woff2'); font-display: swap; } ``` h3. [Dynamic Imports for Route Components](#dynamic-imports-for-route-components) [**Split your bundle**](https://www.debugbear.com/blog/optimize-vue-performance) to load only what's needed: ``` import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: () => import('./views/Home.vue') }, { path: '/dashboard', component: () => import('./views/Dashboard.vue') } ] }) ``` [**Vue.js parallel fetching**](https://medium.com/@m.kiselyow/vue-js-parallel-fetching-improves-lcp-and-fcp-289f0898badc) improves LCP by loading component files, data, and images simultaneously. h2. [Vue INP Optimizations](#vue-inp-optimizations) h3. [Debounce Event Handlers](#debounce-event-handlers) [**Debouncing**](https://www.debugbear.com/blog/optimize-vue-performance) prevents performance issues from rapid-fire events: ``` <script setup> import { ref } from 'vue' const searchQuery = ref('') let timeout = null function handleSearch(event) { clearTimeout(timeout) timeout = setTimeout(() => { searchQuery.value = event.target.value // Perform search }, 300) } </script> <template> <input placeholder="Search..." @input="handleSearch"> </template> ``` h3. [Use v-memo for Large Lists](#use-v-memo-for-large-lists) [**v-memo**](https://www.bairesdev.com/blog/vue-performance-optimization/) memoizes component output to skip re-renders: ``` <template> <div v-for="item in items" :key="item.id" v-memo="[item.id, item.selected]"> {{ item.name }} </div> </template> ``` Re-renders only happen when `item.id` or `item.selected` changes. h3. [Offload Heavy Computation to Web Workers](#offload-heavy-computation-to-web-workers) Keep the main thread responsive: ``` // worker.ts self.addEventListener('message', (e) => { const result = heavyComputation(e.data) self.postMessage(result) }) // component.vue const worker = new Worker(new URL('./worker.ts', import.meta.url)) worker.postMessage(data) worker.addEventListener('message', (e) => { console.log('Result:', e.data) }) ``` h3. [Optimize Computed Properties](#optimize-computed-properties) [**Computed properties**](https://vuejs.org/guide/best-practices/performance.html) cache results. Use them instead of methods for expensive operations: ``` <script setup> import { computed, ref } from 'vue' const items = ref([...largeArray]) // Cached, recalculates only when items changes const filteredItems = computed(() => items.value.filter(item => item.active) ) </script> ``` [**Breaking long tasks**](https://rankture.com/blog/core-web-vitals-optimization-guide) can reduce INP from 350ms to 120ms. h2. [Vue CLS Fixes](#vue-cls-fixes) h3. [Set Image Dimensions](#set-image-dimensions) Prevent layout shifts by reserving space: ``` <template> <img src="/product.jpg" alt="Product" width="800" height="600" > </template> ``` Or use aspect ratio with CSS: ``` <template> <div class="image-container"> <img src="/product.jpg" alt="Product"> </div> </template> <style scoped> .image-container { aspect-ratio: 16 / 9; overflow: hidden; } .image-container img { width: 100%; height: 100%; object-fit: cover; } </style> ``` h3. [Reserve Space for Dynamic Content](#reserve-space-for-dynamic-content) Skeleton screens or placeholders prevent shifts: ``` <script setup> import { onMounted, ref } from 'vue' const data = ref(null) const loading = ref(true) onMounted(async () => { data.value = await fetchData() loading.value = false }) </script> <template> <div class="content"> <div v-if="loading" class="skeleton"> <!-- Same dimensions as actual content --> </div> <div v-else> {{ data }} </div> </div> </template> <style scoped> .skeleton { height: 200px; /* Match real content height */ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); } </style> ``` h3. [Avoid Flash of Unstyled Content (FOUC)](#avoid-flash-of-unstyled-content-fouc) Preload fonts and use `font-display: swap`: ``` <head> <link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin> </head> ``` ``` @font-face { font-family: 'Inter'; src: url('/fonts/inter-var.woff2') format('woff2'); font-display: swap; size-adjust: 100%; /* Adjust fallback font metrics */ } ``` [**Adjusting local system fonts to match web fonts**](https://www.ateamsoftsolutions.com/core-web-vitals-optimization-guide-2025-showing-lcp-inp-cls-metrics-and-performance-improvement-strategies-for-web-applications-2/) using fallback metrics reduces CLS. h3. [Handle Ads and Embeds](#handle-ads-and-embeds) Reserve space for third-party content: ``` <template> <div class="ad-container" style="min-height: 250px;"> <!-- Ad loads here --> </div> </template> ``` h2. [Measuring Core Web Vitals](#measuring-core-web-vitals) h3. [PageSpeed Insights](#pagespeed-insights) [**PageSpeed Insights**](https://pagespeed.web.dev/) shows field data (real user metrics from Chrome UX Report) and lab data (simulated tests). Field data is what Google uses for rankings. Lab data helps debug specific issues. h3. [Lighthouse](#lighthouse) Run Lighthouse in Chrome DevTools (Performance tab) for local testing. It measures LCP, CLS, and provides optimization suggestions. h3. [Chrome DevTools Performance Panel](#chrome-devtools-performance-panel) Record a session to see frame-by-frame rendering: 1. Open DevTools → Performance 2. Click Record 3. Interact with your page 4. Stop recording 5. View LCP, CLS, and long tasks [**Chrome DevTools**](https://www.clapcreative.com/web-vitals-in-2025-whats-new-and-how-to-stay-compliant/) shows local LCP and CLS scores instantly. Interact with the page to capture INP. h3. [web-vitals Library](#web-vitals-library) [**Google's web-vitals library**](https://github.com/GoogleChrome/web-vitals) sends metrics to your analytics: ``` import { onCLS, onINP, onLCP } from 'web-vitals' onLCP((metric) => { console.log('LCP:', metric.value) // Send to analytics sendToAnalytics({ metric: 'LCP', value: metric.value }) }) onINP((metric) => { console.log('INP:', metric.value) sendToAnalytics({ metric: 'INP', value: metric.value }) }) onCLS((metric) => { console.log('CLS:', metric.value) sendToAnalytics({ metric: 'CLS', value: metric.value }) }) function sendToAnalytics({ metric, value }) { // Send to Google Analytics, Plausible, etc. if (window.gtag) { window.gtag('event', metric, { value: Math.round(value) }) } } ``` [**Capture p75 and p95 metrics**](https://madewithvuejs.com/blog/advanced-vue-performance-monitoring) for all critical flows, sliced by device and region. h2. [Core Web Vitals Impact on Rankings](#core-web-vitals-impact-on-rankings) Google treats Core Web Vitals as a [**tiebreaker ranking factor**](https://developers.google.com/search/docs/appearance/core-web-vitals). Content relevance is still the primary signal. For queries with multiple relevant results, good page experience can be the differentiator. [**Industry research shows**](https://rankinggenerals.com/2025-core-web-vitals/) Core Web Vitals account for 10–15% of ranking signals. Only [**47% of websites pass all three metrics in 2025**](https://www.brightvessel.com/core-web-vitals-in-2025-how-they-affect-google-rankings-and-user-experience/). Sites passing all three vitals see: - 24% lower bounce rates - 25% higher conversion rates moving from Poor to Good - 30% revenue improvements from higher engagement [**Mobile-first indexing**](https://systemsarchitect.net/core-web-vitals-2025/) now treats Core Web Vitals as indexing requirements rather than just ranking signals. Google evaluates mobile versions of sites primarily. To pass assessment, [**75% of page visits**](https://increv.co/academy/core-web-vitals/) must meet "Good" thresholds. h2. [Using Nuxt?](#using-nuxt) Nuxt provides [**built-in performance optimizations**](https://nuxt.com/docs/4.x/guide/best-practices/performance) including automatic code splitting, image optimization, and hybrid rendering. See our [**Nuxt Core Web Vitals guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/core-web-vitals/learn-seo/nuxt/launch-and-listen/core-web-vitals) for framework-specific implementations. --- [**Google Search Console** Verify ownership, submit sitemaps, and monitor indexing status using Google Search Console.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/core-web-vitals/learn-seo/vue/launch-and-listen/search-console) [**Indexing Issues** Fix "crawled currently not indexed" and other GSC coverage errors affecting your Vue site.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/core-web-vitals/learn-seo/vue/launch-and-listen/indexing-issues) **On this page** - [The Three Metrics](#the-three-metrics) - [Vue LCP Optimizations](#vue-lcp-optimizations) - [Vue INP Optimizations](#vue-inp-optimizations) - [Vue CLS Fixes](#vue-cls-fixes) - [Measuring Core Web Vitals](#measuring-core-web-vitals) - [Core Web Vitals Impact on Rankings](#core-web-vitals-impact-on-rankings) - [Using Nuxt?](#using-nuxt) --- ### Debug Indexing Issues in Google Search Console · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues Description: Fix "crawled currently not indexed" and other GSC coverage errors affecting your Vue site. h1. **Debug Indexing Issues in Google Search Console** Fix "crawled currently not indexed" and other GSC coverage errors affecting your Vue site. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** **What you'll learn** - "Crawled - currently not indexed" usually means low quality, duplicate, or thin content - SPAs need SSR or prerendering for reliable indexing. Google may not execute JavaScript properly - Internal links signal importance. orphan pages rarely get indexed Google crawled your page but won't index it. This happens to [**millions of pages daily**](https://www.onely.com/blog/how-to-fix-crawled-currently-not-indexed-in-google-search-console/). The Page Indexing report in Google Search Console shows exactly which pages have issues and why. h2. [Understanding Page Indexing Status](#understanding-page-indexing-status) Google Search Console's Page Indexing report shows six main statuses: ![Page Indexing Status Flowchart](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues/images/learn-seo/vue/indexing-status-flowchart.svg) h3. [Good Statuses](#good-statuses) **Indexed**: Page appears in Google's search index. This is what you want. h3. [Warning Statuses](#warning-statuses) **Discovered - currently not indexed**: Google found your page but hasn't crawled it yet. The page sits in Google's queue. This is normal for new sites and low-priority pages. **Crawled - currently not indexed**: Google crawled your page but chose not to index it. This means Google decided your content isn't worth showing in search results. h3. [Excluded Statuses](#excluded-statuses) **Excluded by robots.txt**: Your robots.txt file blocks Google from accessing the page. Check your robots.txt configuration. **Blocked by noindex tag**: Page has a `noindex` meta tag or HTTP header. Remove it if you want the page indexed. **Duplicate without canonical**: Google found multiple identical pages without proper canonical tags. Set canonical URLs. **Soft 404**: Page returns a 200 status code but looks like a 404 error to Google. Fix by returning proper 404 status codes for missing content. h2. [Fixing "Crawled - Currently Not Indexed"](#fixing-crawled-currently-not-indexed) This status means Google crawled your page but decided it wasn't worth indexing. [**Google doesn't explicitly state why**](https://support.google.com/webmasters/answer/7440203), but five main causes exist. h3. [Thin or Low-Quality Content](#thin-or-low-quality-content) Google skips pages with little unique value. Product pages with only titles and prices, blog posts under 300 words, and auto-generated content typically get excluded. Fix: Add substantial content. Write detailed product descriptions, expand short blog posts to 800+ words, include images and videos, answer user questions comprehensively. h3. [Duplicate Content](#duplicate-content) Multiple pages with identical or near-identical content waste Google's crawl budget. Common culprits: paginated URLs without proper canonicals, URL parameters creating duplicate versions, tag/category archives with the same posts. Fix: Implement canonical tags pointing to the primary version. Consolidate similar pages. Use `rel="canonical"` in your Vue head: ``` <script setup> import { useHead } from '@unhead/vue' useHead({ link: [ { rel: 'canonical', href: 'https://yoursite.com/primary-page' } ] }) </script> ``` h3. [Too Many Similar Pages](#too-many-similar-pages) Sites with thousands of nearly-identical pages (e.g., faceted search, filter combinations) trigger quality filters. Google picks representative pages and excludes the rest. Fix: Use meta robots `noindex` on filter pages, parameter-based URLs, and search result pages. Consolidate similar content into fewer comprehensive pages. ``` <script setup> import { useHead } from '@unhead/vue' import { useRoute } from 'vue-router' const route = useRoute() // Noindex pages with filter parameters const shouldNoIndex = computed(() => route.query.color || route.query.size || route.query.sort ) useHead({ meta: [ { name: 'robots', content: shouldNoIndex.value ? 'noindex,follow' : 'index,follow' } ] }) </script> ``` h3. [Low Site Authority](#low-site-authority) New sites with few backlinks face stricter indexing thresholds. [**Google prioritizes crawling trusted sites**](https://www.onely.com/blog/how-to-fix-crawled-currently-not-indexed-in-google-search-console/). Fix: Build backlinks through guest posting, create linkable assets (tools, research, guides), get listed in industry directories, promote content on social media. This takes months. be patient. h3. [Orphan Pages](#orphan-pages) Pages without internal links from other pages on your site signal low importance to Google. [**Orphan pages lack link equity**](https://mangools.com/blog/orphan-pages/) and often don't get indexed. Fix: Add internal links from relevant pages. Include new pages in your main navigation or sidebar. Link from high-authority pages on your site. ``` <!-- Link to important pages from your main layout --> <template> <nav> <RouterLink to="/important-page"> Important Page </RouterLink> </nav> </template> ``` h2. [Fixing "Discovered - Currently Not Indexed"](#fixing-discovered-currently-not-indexed) This status means Google knows your page exists but hasn't crawled it. [**Three main causes exist**](https://www.onely.com/blog/how-to-fix-discovered-currently-not-indexed-in-google-search-console/). h3. [Site Too New](#site-too-new) Google takes weeks to crawl new sites. For brand-new domains, expect 2-4 weeks before regular crawling starts. Fix: Wait. Submit your sitemap. Request indexing for critical pages via URL Inspection tool. Keep publishing content regularly. h3. [Crawl Budget Issues](#crawl-budget-issues) Large sites (10,000+ pages) run into Google's crawl budget limits. Google won't crawl everything if your site has [**slow server responses, too many low-quality pages, or complex URL structures**](https://support.google.com/webmasters/community-guide/278777978). Fix: Optimize server response times (target under 200ms). Remove or noindex low-value pages. Fix redirect chains. Reduce duplicate content. Block unnecessary URLs in robots.txt: ``` h1. robots.txt User-agent: * h1. Allow important pages Allow: / h1. Block low-value sections Disallow: /admin/ Disallow: /search? Disallow: /*?filter= Disallow: /print-version/ ``` h3. [Slow Server Response](#slow-server-response) If your server takes over 500ms to respond, [**Google may crawl fewer pages**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget). Fix: Enable caching, use a CDN, optimize database queries, upgrade hosting. Monitor server response times in Search Console's Crawl Stats report. h3. [Pages Only in Sitemap](#pages-only-in-sitemap) If pages exist only in your sitemap without internal links, Google considers them low priority. Fix: Add internal links. Don't rely solely on sitemaps for discovery. [**Internal linking signals importance**](https://seotesting.com/google-search-console/discovered-currently-not-indexed/). h2. [Vue SPA-Specific Issues](#vue-spa-specific-issues) Single Page Applications create unique indexing challenges because content loads after the initial HTML renders. ![SPA Rendering Timeline](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues/images/learn-seo/vue/spa-rendering-timeline.svg) h3. [JavaScript Rendering Problems](#javascript-rendering-problems) Google crawls your initial HTML first, then renders JavaScript in a second wave (days later). If critical content only appears after JavaScript execution, it may not get indexed immediately. Test: View your page source (not DevTools). Right-click → View Page Source. If your content isn't visible in the raw HTML, Google's first crawl won't see it. ``` h1. Check if content is in initial HTML curl -s https://yoursite.com/page | grep "expected content" ``` Fix: Use Server-Side Rendering (SSR) or Static Site Generation (SSG). For Vue SPAs without SSR, consider [**prerendering critical pages**](https://prerender.io/blog/how-to-avoid-discovered-currently-not-indexed/). h3. [Content Loaded After Initial Render](#content-loaded-after-initial-render) Vue apps often fetch data after mounting. Google may not wait for all async operations to complete. ``` <!-- PROBLEM: Content loads after mount --> <script setup> import { onMounted, ref } from 'vue' const products = ref([]) onMounted(async () => { // Google's crawler might not wait for this products.value = await fetch('/api/products').then(r => r.json()) }) </script> <template> <div v-for="product in products" :key="product.id"> {{ product.name }} </div> </template> ``` Fix: Render content during SSR or prerender pages at build time: ``` <!-- SOLUTION: Fetch data before render --> <script setup> import { ref } from 'vue' // Data available immediately const products = ref(await fetch('/api/products').then(r => r.json())) </script> <template> <div v-for="product in products" :key="product.id"> {{ product.name }} </div> </template> ``` For client-only apps, show loading states with descriptive text that Google can index: ``` <template> <div> <h1>Product Catalog</h1> <p v-if="loading"> Loading 500+ products from our catalog... </p> <div v-for="product in products" v-else :key="product.id"> {{ product.name }} </div> </div> </template> ``` h3. [Client-Side Routing Issues](#client-side-routing-issues) Vue Router changes URLs without full page reloads. If your router isn't configured correctly, Google may not discover all routes. Fix: Generate a complete sitemap listing all routes. Don't rely on Google following JavaScript-generated links: ``` // generate-sitemap.ts import { routes } from './router' const sitemap = routes.map(route => ({ url: \`https://yoursite.com${route.path}\`, lastmod: new Date().toISOString() })) // Write to public/sitemap.xml ``` h3. [Testing JavaScript Rendering](#testing-javascript-rendering) Use Google Search Console's URL Inspection tool to see exactly what Google renders: 1. Open Search Console → URL Inspection 2. Enter your page URL 3. Click "Test Live URL" 4. Click "View Tested Page" → "Screenshot" Compare the screenshot to your actual page. If content is missing, Google isn't rendering it properly. h2. [Requesting Re-Indexing](#requesting-re-indexing) After fixing issues, request re-indexing via URL Inspection: 1. Search Console → URL Inspection 2. Enter fixed URL 3. Click "Request Indexing" Google prioritizes these requests but doesn't guarantee indexing. [**It still evaluates content quality**](https://support.google.com/webmasters/answer/7440203). For many URLs, request indexing programmatically using the [**Google Indexing API**](https://developers.google.com/search/apis/indexing-api/v3/quickstart): ``` // Request indexing via API import { google } from 'googleapis' async function requestIndexing(url: string) { const auth = await google.auth.getClient({ scopes: ['https://www.googleapis.com/auth/indexing'] }) const indexing = google.indexing({ version: 'v3', auth }) await indexing.urlNotifications.publish({ requestBody: { url, type: 'URL_UPDATED' } }) } ``` h2. [Monitoring Progress](#monitoring-progress) Track indexing status changes over time: 1. Search Console → Page Indexing 2. Check "Not indexed" count weekly 3. Look for status changes from "Crawled - not indexed" to "Indexed" Expect changes to take 1-4 weeks. [**Google doesn't index on demand**](https://www.onely.com/blog/how-to-fix-crawled-currently-not-indexed-in-google-search-console/). it re-evaluates pages on its schedule. h2. [Using Nuxt?](#using-nuxt) Nuxt handles SSR and prerendering automatically, avoiding most SPA indexing issues. See [**Nuxt indexing guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues/learn-seo/nuxt/launch-and-listen) for framework-specific solutions. --- [**Core Web Vitals** Measure and optimize LCP, INP, and CLS in Vue apps to improve user experience and search rankings.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues/learn-seo/vue/launch-and-listen/core-web-vitals) [**SEO Monitoring** Set up analytics, rank tracking, and alerts to monitor your Vue site's search performance.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexing-issues/learn-seo/vue/launch-and-listen/seo-monitoring) **On this page** - [Understanding Page Indexing Status](#understanding-page-indexing-status) - [Fixing "Crawled - Currently Not Indexed"](#fixing-crawled-currently-not-indexed) - [Fixing "Discovered - Currently Not Indexed"](#fixing-discovered-currently-not-indexed) - [Vue SPA-Specific Issues](#vue-spa-specific-issues) - [Requesting Re-Indexing](#requesting-re-indexing) - [Monitoring Progress](#monitoring-progress) - [Using Nuxt?](#using-nuxt) --- ### SEO Monitoring Tools for Vue Sites · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/seo-monitoring Description: Set up analytics, rank tracking, and alerts to monitor your Vue site's search performance. h1. **SEO Monitoring Tools for Vue Sites** Set up analytics, rank tracking, and alerts to monitor your Vue site's search performance. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** **What you'll learn** - Start with free tools: Google Search Console for indexing, GA4 or Plausible for traffic - Privacy-focused alternatives (Plausible, Fathom, Umami) work without cookie banners - Upgrade to paid tools (Ahrefs, SEMrush) when revenue justifies the cost Once your Vue site is live, you need data. Traffic numbers, keyword rankings, and technical issues don't surface themselves. you track them with the right tools. h2. [Start with Free Tools](#start-with-free-tools) h3. [Google Search Console](#google-search-console) [**Search Console**](https://search.google.com/search-console) shows you what Google sees. Add your property, verify ownership, submit your sitemap, and check back in 48 hours. Track these metrics: - **Impressions** - How many times your pages appeared in search - **Clicks** - Actual traffic from Google - **Average position** - Where you rank for queries - **Coverage errors** - Pages Google can't index Check the Performance report weekly. Sort by pages with high impressions but low clicks. these need better titles or descriptions. h3. [GA4 for Organic Traffic](#ga4-for-organic-traffic) Google Analytics 4 tracks user behavior and traffic sources. The challenge: Vue SPAs don't reload pages, so [**default pageview tracking breaks**](https://data-marketing-school.com/en/blog/google-analytics/track-single-page-applications/). h4. [Setup for Vue Router](#setup-for-vue-router) GA4 has built-in history change detection, but manual tracking gives you more control. Hook into `router.afterEach` to send pageviews on route changes: ``` // router/index.ts import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [/* your routes */] }) router.afterEach((to) => { // Send page_view to GA4 gtag('config', 'GA_MEASUREMENT_ID', { page_path: to.fullPath }) }) export default router ``` Add the GA4 script to your `index.html` with `send_page_view: false` to prevent double-tracking: ``` <!-- Google tag (gtag.js) --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-XXXXXXXXXX', { send_page_view: false }); </script> ``` h4. [Track Organic Traffic](#track-organic-traffic) Go to **Reports > Acquisition > Traffic acquisition** in GA4. Filter by `sessionDefaultChannelGroup == Organic Search` to see your Google traffic. Link GA4 to Search Console for keyword data. Navigate to **Admin > Property Settings > Product links > Search Console links**. Data appears 48 hours after linking. h2. [Privacy-Focused Alternatives](#privacy-focused-alternatives) GA4 requires cookie banners in the EU and collects more data than most sites need. Three alternatives give you clean analytics without privacy headaches. h3. [Plausible](#plausible) [**Plausible**](https://plausible.io/) is lightweight (under 1KB script) and GDPR-compliant without cookies. No cookie banner needed. Add the script to your HTML: ``` <script defer data-domain="yourdomain.com" src="https://plausible.io/js/script.js"></script> ``` Plausible automatically tracks [**single-page applications**](https://plausible.io/docs/hash-based-routing) using the History API. no router configuration needed for Vue Router. Dashboard shows: - Real-time visitors - Top pages and sources - Referrers and countries - Bounce rate and time on page Pricing starts at $9/month for 10k monthly pageviews. Self-hosted version is free. h3. [Fathom](#fathom) [**Fathom**](https://usefathom.com/) focuses on simplicity. No cookies, GDPR-compliant, Canadian company with strong privacy stance. Install via script tag: ``` <script src="https://cdn.usefathom.com/script.js" data-site="ABCDEFG" defer></script> ``` Fathom tracks SPAs automatically. Dashboard is cleaner than GA4. fewer metrics, faster insights. Starts at $15/month for 100k pageviews. No free tier, but 30-day free trial available. h3. [Umami](#umami) [**Umami**](https://umami.is/) is open-source and self-hosted. Free if you run it yourself, or $9/month for cloud hosting. Deploy on Vercel/Railway, add the tracking code: ``` <script defer src="https://analytics.umami.is/script.js" data-website-id="your-id"></script> ``` SPA tracking works out of the box. [**Umami's Vue integration**](https://umami.is/docs/tracker-functions) lets you track custom events: ``` // Track button clicks umami.track('signup_button_clicked') ``` Best choice if you want full data ownership and don't mind managing infrastructure. h2. [Paid Tools (When You Need More)](#paid-tools-when-you-need-more) Free tools show _what's happening_. Paid tools show _why_ and _what to do about it_. h3. [When to Upgrade](#when-to-upgrade) Upgrade when you hit these signals: - Traffic plateaus after 6 months - Competitors outrank you for target keywords - You need backlink data to build authority - Manual SEO audits take too long [**Don't pay for tools on day one**](https://www.ibeamconsulting.com/blog/seo-tools-free-vs-paid-premium/). Start with Search Console and free analytics. Upgrade when revenue justifies it. blog earning $500/month can justify $100/month tools. h3. [Ahrefs](#ahrefs) [**Ahrefs**](https://ahrefs.com/) excels at backlink analysis and keyword research. Use it to: - Find who links to competitors - Track keyword rankings daily - Discover content gaps - Run site audits for technical issues Rank Tracker shows click potential. how many actual clicks a keyword generates after SERP features take their share. More useful than search volume alone. [**Ahrefs doesn't provide daily ranking updates by default**](https://seranking.com/blog/ahrefs-vs-semrush/). manual refreshes cost credits. Still worth it for backlink database, the largest in the industry. Starts at $129/month (Lite plan). Use the $7 7-day trial first. h3. [SEMrush](#semrush) [**SEMrush**](https://www.semrush.com/) is better for [**full campaign tracking**](https://trafficthinktank.com/semrush-vs-ahrefs/). It sends daily ranking updates via email and includes: - Position tracking for 500 keywords (Pro plan) - PPC competitor analysis - Local rank tracking with heatmaps - Content marketing toolkit SEMrush's Map Rank Tracker beats Ahrefs for local SEO. see how you rank across cities, not just countries. Better value than Ahrefs if you need PPC data or manage multiple locations. Starts at $139.95/month. h3. [Screaming Frog](#screaming-frog) [**Screaming Frog**](https://www.screamingfrogseoseo.com/) crawls your site like Googlebot. Find broken links, duplicate content, missing meta tags, and redirect chains. Free up to 500 URLs. Paid version ($259/year) crawls unlimited URLs and integrates with GA4 and Search Console. Run audits monthly. Fix issues before Google does. h2. [What to Track Weekly](#what-to-track-weekly) Monitor these metrics every Monday: **From Search Console:** - Total clicks (is traffic growing?) - Coverage errors (new indexing issues?) - Core Web Vitals (any pages failing?) **From GA4 or Plausible:** - Organic sessions (week-over-week change) - Top landing pages (which content performs?) - Bounce rate (are users finding what they need?) **From Ahrefs/SEMrush (if paid):** - Keyword rankings (any big drops?) - New backlinks (who linked to you?) - Competitor rankings (are they gaining ground?) Don't obsess over daily fluctuations. Rankings shift constantly. weekly trends matter more. h2. [Setting Up Alerts](#setting-up-alerts) Alerts catch problems before you lose traffic. h3. [Search Console Alerts](#search-console-alerts) Search Console emails you automatically for: - Manual actions (penalties) - Security issues (hacked site) - Critical coverage errors Add team members in **Settings > Users and permissions** so they get alerts too. h3. [GA4 Custom Alerts](#ga4-custom-alerts) Create alerts for traffic drops. Go to **Admin > Custom insights** and set conditions: - If `Sessions from Organic Search` decreases by **30% week-over-week**, send email - If `Conversions` drops by **50%**, alert immediately Catch issues the day they happen, not two weeks later. h3. [Uptime Monitoring](#uptime-monitoring) If your site goes down, Search Console won't tell you. Use [**UptimeRobot**](https://uptimerobot.com/) (free) or [**Pingdom**](https://www.pingdom.com/) to check every 5 minutes. Get SMS alerts when your site breaks. h2. [Using Nuxt?](#using-nuxt) Nuxt adds [**built-in analytics modules**](https://nuxt.com/modules?category=Analytics) and automatic sitemap generation. Read the [**Nuxt SEO Monitoring guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/seo-monitoring/learn-seo/nuxt/launch-and-listen) for framework-specific setup. --- [**Indexing Issues** Fix "crawled currently not indexed" and other GSC coverage errors affecting your Vue site.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/seo-monitoring/learn-seo/vue/launch-and-listen/indexing-issues) [**Site Migration** Migrate domains, redesign URLs, or switch frameworks without losing search rankings.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/seo-monitoring/learn-seo/vue/launch-and-listen/site-migration) **On this page** - [Start with Free Tools](#start-with-free-tools) - [Privacy-Focused Alternatives](#privacy-focused-alternatives) - [Paid Tools (When You Need More)](#paid-tools-when-you-need-more) - [What to Track Weekly](#what-to-track-weekly) - [Setting Up Alerts](#setting-up-alerts) - [Using Nuxt?](#using-nuxt) --- ### Site Migration SEO for Vue Apps · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration Description: Migrate domains, redesign URLs, or switch frameworks without losing search rankings. h1. **Site Migration SEO for Vue Apps** Migrate domains, redesign URLs, or switch frameworks without losing search rankings. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - Well-executed migrations recover 90-95% of traffic within 30 days - Create 1:1 URL mapping spreadsheet before touching production - Keep redirects for at least one year. longer if they still get hits Site migrations lose search rankings when done wrong. [**Well-executed migrations recover 90-95% of traffic within 30 days**](https://www.matthewedgar.net/how-long-keep-redirects/). Poor migrations can take 6-12 months to recover or never reach previous levels. The difference is planning, proper redirects, and post-migration monitoring. h2. [Types of Migrations](#types-of-migrations) Each migration type affects SEO differently: ![Migration Decision Tree](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration/images/learn-seo/vue/migration-decision-tree.svg) **Domain change** (old.com → new.com): Requires [**Change of Address tool in Google Search Console**](https://support.google.com/webmasters/answer/9370220) and strict 1:1 URL mapping. Keep redirects for [**at least one year**](https://www.searchenginejournal.com/google-keep-301-redirects-in-place-for-a-year/428998/). **Protocol change** (HTTP → HTTPS): Update all canonical tags to HTTPS versions. Forgetting this creates redirect loops. redirects send Google to HTTPS, but canonicals point back to HTTP. **URL structure change** (/blog/post → /posts/post): Most prone to redirect chains. Map every old URL to exactly one new URL. No intermediates. **Platform/framework change**: Moving from WordPress to Vue, or Vue to Nuxt. URL structure usually changes. Full redirect mapping required. **Site redesign with same URLs**: Lowest risk if URLs stay identical. Still validate canonical tags and internal links. h2. [Pre-Migration Checklist](#pre-migration-checklist) Start here before touching production: 1. **Crawl old site completely**: Use Screaming Frog or similar to export all URLs. You need a full inventory. 2. **Export indexed URLs from Search Console**: Go to Coverage report → Valid → Export. These are URLs Google knows about. 3. **Document current rankings**: Screenshot Search Console Performance for top pages. You'll compare against this. 4. **Create redirect mapping spreadsheet**: Three columns: Old URL | New URL | Status (200, 301, 410). Map every single URL. No exceptions. For large sites (10,000+ pages), consider [**migrating in sections**](https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes). Test with a small section first, verify traffic holds, then migrate the rest. h2. [Redirect Mapping Strategy](#redirect-mapping-strategy) Mapping is where migrations succeed or fail. **1:1 mapping** (preferred): Each old URL redirects to exactly one new URL with identical or similar content. ``` /blog/vue-seo-guide → /guides/vue-seo /products/item-123 → /products/item-123 (unchanged) ``` **Pattern-based redirects**: For systematic URL changes. Use regex or route patterns to redirect entire sections. ``` /blog/:slug → /articles/:slug /category/:cat/page/:num → /c/:cat?page=:num ``` **Deleted pages**: Don't 404 pages that had traffic or backlinks. Redirect to the most relevant alternative. If truly no alternative exists, return 410 (Gone) instead of 404. signals permanent removal. h2. [Implementing Redirects in Vue](#implementing-redirects-in-vue) Server-side redirects are required for SEO. Client-side redirects (JavaScript) don't pass PageRank. h3. [Express Server](#express-server) ``` import express from 'express' const app = express() // Single redirect app.get('/old-url', (req, res) => { res.redirect(301, '/new-url') }) // Pattern redirect app.get('/blog/:slug', (req, res) => { res.redirect(301, \`/posts/${req.params.slug}\`) }) // Redirect map for bulk redirects const redirects = { '/old-page-1': '/new-page-1', '/old-page-2': '/new-page-2', '/company/about': '/about' } app.use((req, res, next) => { const redirect = redirects[req.path] if (redirect) { return res.redirect(301, redirect) } next() }) ``` h3. [Vite Server](#vite-server) ``` // vite.config.ts import { defineConfig } from 'vite' export default defineConfig({ server: { middleware: [ (req, res, next) => { const redirects = { '/old-path': '/new-path', '/blog': '/posts' } const redirect = redirects[req.url] if (redirect) { res.writeHead(301, { Location: redirect }) res.end() return } next() } ] } }) ``` h3. [H3 Server (Nitro/Nuxt)](#h3-server-nitronuxt) ``` // server/middleware/redirects.ts import { defineEventHandler, sendRedirect } from 'h3' const redirects = { '/old-url': '/new-url', '/blog': '/posts' } export default defineEventHandler((event) => { const redirect = redirects[event.path] if (redirect) { return sendRedirect(event, redirect, 301) } }) ``` For large redirect maps (1000+ URLs), load from JSON file: ``` import redirectData from './redirects.json' export default defineEventHandler((event) => { const redirect = redirectData[event.path] if (redirect) { return sendRedirect(event, redirect, 301) } }) ``` h2. [Avoid Redirect Chains](#avoid-redirect-chains) [**Redirect chains**](https://netpeaksoftware.com/blog/top-10-site-migration-mistakes) happen when URL A redirects to B, which redirects to C. This dilutes PageRank and slows crawling. Common scenario: You have old redirects (A → B) and add new migration redirects (B → C). Result: chain A → B → C. Fix: Update all redirects to point directly to final destination. Consolidate old and new redirects into single-hop redirects. ``` // Bad: creates chain // Old redirect: /page-v1 → /page-v2 // New redirect: /page-v2 → /page-v3 // Result: /page-v1 → /page-v2 → /page-v3 // Good: consolidate const redirects = { '/page-v1': '/page-v3', // direct to final '/page-v2': '/page-v3' } ``` Test redirects before launch: Each should return 301 status and resolve in one hop to 200 (OK). h2. [Update Canonical Tags](#update-canonical-tags) When URLs change, canonical tags must point to new URLs. [**Canonical pointing to redirect**](https://sitechecker.pro/site-audit-issues/canonical-points-redirect/) is a common migration mistake. After migration, canonical tags should reference new URL structure: ``` <!-- Bad: canonical points to old URL --> <link rel="canonical" href="https://example.com/old-url"> <!-- Good: canonical points to new URL --> <link rel="canonical" href="https://example.com/new-url"> ``` In Vue with Unhead: ``` useHead({ link: [ { rel: 'canonical', href: 'https://example.com/new-url' } ] }) ``` Scan staging site before launch to verify all canonicals point to new URLs, not old ones. h2. [Post-Migration Steps](#post-migration-steps) The hour after migration is critical. 1. **Update Search Console property**: For domain changes, use [**Change of Address tool**](https://support.google.com/webmasters/answer/9370220). This tells Google you've moved. 2. **Submit new sitemap**: Generate sitemap with new URLs. Submit in Search Console. Remove old sitemap from robots.txt. 3. **Request indexing of key pages**: In Search Console, request indexing for homepage and top 10-20 pages. Speeds up discovery. 4. **Monitor coverage report**: Check daily for errors. Look for 404s, soft 404s, redirect errors, or pages not followed. 5. **Watch server capacity**: [**Google crawls more after migrations**](https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes). Redirects from old URLs trigger crawls of new URLs. Monitor server load. 6. **Check Analytics segmentation**: Set up date comparison in Google Analytics. before migration vs after. Track organic traffic specifically. h2. [Recovery Timeline](#recovery-timeline) ![Migration Recovery Timeline](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration/images/learn-seo/vue/migration-recovery-gantt.svg) Expect these phases: **Week 1-2**: Traffic dip of [**10-25% is normal**](https://www.numentechnology.co.uk/blog/website-migration-seo-strategy). Google discovers redirects and starts reindexing. **Week 3-4**: [**Small sites recover**](https://socialander.com/how-long-to-do-website-migration/) 90%+ of traffic if migration was clean. Large sites still reindexing. **Month 2-3**: Most sites reach full recovery. Rankings stabilize. [**Typical range is 30-60 days**](https://www.localseoguide.com/how-long-does-it-take-to-recover-from-a-domain-migration/). **Month 4-6**: Large sites (100,000+ pages) continue recovery. [**Average across 892 migrations was 523 days**](https://www.rapidfireweb.com/post/domain-migration-its-impact-on-organic-traffic). but median is much lower. **Keep redirects for 1+ year**: [**Google recommends at least 12 months**](https://www.searchenginejournal.com/google-keep-301-redirects-in-place-for-a-year/428998/). Longer if you still see traffic to old URLs. Never remove redirects that still get hits. If traffic hasn't recovered after 3 months, audit for: - Missing redirects (404s in coverage report) - Redirect chains - Canonical tags still pointing to old URLs - Internal links still pointing to old URLs h2. [Common Migration Mistakes](#common-migration-mistakes) **Redirect chains**: As mentioned, these dilute authority. [**Consolidate old and new redirects**](https://www.tomcreweseo.co.uk/blog/6-mistakes-ive-seen-cause-a-website-migration-to-go-wrong/) into single-hop redirects. **Forgetting internal links**: Your site's internal links still point to old URLs, triggering unnecessary redirects. Update internal links to point directly to new URLs. **Not updating canonical URLs**: Canonical tags create loops when they point to redirected URLs. [**Update canonicals alongside redirects**](https://proranktracker.com/blog/seo-common-mistakes-canonical-tag-and-301-redirect/). **Removing redirects too early**: Traffic sources outside your control (old backlinks, bookmarks, third-party sites) use old URLs indefinitely. Keep redirects for years, not months. **Client-side redirects**: JavaScript redirects (`window.location`) don't pass PageRank. Google may not follow them. Server-side 301s are required. **No redirect testing**: Test redirects on staging before launch. Verify each returns 301 and resolves to 200 in one hop. **Forgetting mobile/AMP URLs**: If you had separate mobile URLs (m.example.com) or AMP versions, redirect those too. h2. [Using Nuxt?](#using-nuxt) Nuxt applications should use [**server middleware for redirects**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration/learn-seo/nuxt/controlling-crawlers/redirects) or [**Nuxt SEO module's redirect utilities**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration/docs/nuxt-seo/getting-started/introduction). For full Nuxt migration guides, see [**Launch & Listen section**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration/learn-seo/nuxt/launch-and-listen). **Site Migration Checklist** - Crawl old site and export all URLs - Export indexed URLs from Search Console Coverage report - Create redirect mapping spreadsheet (Old URL → New URL) - Implement server-side 301 redirects (not client-side) - Update all canonical tags to new URLs - Update internal links to point directly to new URLs - Submit new sitemap and remove old one - Use Change of Address tool (for domain changes) - Monitor Coverage report daily for first 2 weeks - Keep redirects active for at least 1 year --- [**SEO Monitoring** Set up analytics, rank tracking, and alerts to monitor your Vue site's search performance.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration/learn-seo/vue/launch-and-listen/seo-monitoring) [**IndexNow** Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/site-migration/learn-seo/vue/launch-and-listen/indexnow) **On this page** - [Types of Migrations](#types-of-migrations) - [Pre-Migration Checklist](#pre-migration-checklist) - [Redirect Mapping Strategy](#redirect-mapping-strategy) - [Implementing Redirects in Vue](#implementing-redirects-in-vue) - [Avoid Redirect Chains](#avoid-redirect-chains) - [Update Canonical Tags](#update-canonical-tags) - [Post-Migration Steps](#post-migration-steps) - [Recovery Timeline](#recovery-timeline) - [Common Migration Mistakes](#common-migration-mistakes) - [Using Nuxt?](#using-nuxt) --- ### IndexNow and Indexing APIs for Vue Sites · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexnow Description: Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing. h1. **IndexNow and Indexing APIs for Vue Sites** Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** **What you'll learn** - IndexNow works for Bing/Yandex. Google doesn't support it - Google Indexing API is only for JobPosting and BroadcastEvent schema types - Use RequestIndexing tool for bulk Google submissions after major updates Search engines discover content changes through regular crawling, which can take days or weeks. Indexing APIs let you notify search engines immediately when you publish, update, or delete content. h2. [IndexNow Protocol](#indexnow-protocol) IndexNow is an [**open-source protocol**](https://www.indexnow.org/) that notifies search engines instantly when URLs change. Instead of waiting for crawlers to discover updates, you push notifications directly to participating engines. Supported search engines: - **Microsoft Bing** - Primary supporter and developer - **Yandex** - Russian search engine - **Seznam.cz** - Czech search engine - **Naver** - Korean search engine - **Yep** - Privacy-focused search engine **Important:** Google does not support IndexNow. Use Google Search Console's manual "Request Indexing" feature or third-party tools (covered below). h3. [When to Use IndexNow](#when-to-use-indexnow) Use IndexNow when: - Publishing new content (blog posts, product pages) - Updating existing pages (price changes, corrections) - Deleting pages (discontinued products, removed content) - Fixing critical errors on important pages - Running time-sensitive content (news, events, flash sales) Don't use IndexNow for: - Minor text edits or typo fixes - Styling or layout changes - Pages already noindexed via robots meta tag - Private or staging content h3. [Setting Up IndexNow](#setting-up-indexnow) Generate an API key (8-128 alphanumeric characters): ``` openssl rand -hex 32 ``` Create a key file in your `public` directory: public/abc123def456.txt ``` abc123def456 ``` The key must be accessible at `https://yoursite.com/{key}.txt` for verification. h3. [Submitting URLs](#submitting-urls) Submit URLs after content changes. One notification reaches all IndexNow engines. ``` import express from 'express' const app = express() async function notifyIndexNow(urls: string[]) { await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ host: 'yoursite.com', key: 'abc123def456', keyLocation: 'https://yoursite.com/abc123def456.txt', urlList: urls }) }) } // Trigger after publishing app.post('/api/publish', async (req, res) => { // Save content to database await db.posts.create(req.body) // Notify search engines await notifyIndexNow([\`https://yoursite.com/blog/${req.body.slug}\`]) res.json({ success: true }) }) ``` ``` // vite-plugin-indexnow.ts import type { Plugin } from 'vite' export function indexNowPlugin(options: { key: string, host: string }): Plugin { return { name: 'vite-plugin-indexnow', async closeBundle() { const urls = [ \`https://${options.host}/\`, \`https://${options.host}/about\`, // Add your generated URLs ] await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ host: options.host, key: options.key, urlList: urls }) }) console.log(\`Notified IndexNow: ${urls.length} URLs\`) } } } ``` ``` import { defineEventHandler, readBody } from 'h3' export default defineEventHandler(async (event) => { const { urls } = await readBody(event) await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ host: 'yoursite.com', key: process.env.INDEXNOW_KEY, keyLocation: \`https://yoursite.com/${process.env.INDEXNOW_KEY}.txt\`, urlList: urls }) }) return { success: true, count: urls.length } }) ``` ``` // In your CMS save handler async function savePost(post) { await database.save(post) // Notify IndexNow if published if (post.status === 'published') { await fetch('https://api.indexnow.org/indexnow', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ host: 'yoursite.com', key: process.env.INDEXNOW_KEY, urlList: [post.url] }) }) } } ``` Submit up to 10,000 URLs per request. For single URLs, use GET: ``` https://api.indexnow.org/IndexNow?url=https://yoursite.com/page&key=abc123def456 ``` h2. [Google Indexing API](#google-indexing-api) [**Google Indexing API**](https://developers.google.com/search/apis/indexing-api/v3/quickstart) works differently than IndexNow. It's limited to **JobPosting** and **BroadcastEvent** schema types only. Don't use Google Indexing API for: - Blog posts - Product pages - General website content - News articles - Landing pages Only use it if your site has: - Job listings with JobPosting schema - Livestream videos with BroadcastEvent schema Using the API for other content types violates Google's terms and gets ignored. [**Google confirmed**](https://www.seroundtable.com/google-indexing-api-other-content-types-32957.html) it won't help or hurt. it's simply ignored. h3. [2025 API Changes](#_2025-api-changes) Google now requires approval for Indexing API access. The [**default quota is 200 requests**](https://dstribute.io/job-boards/google-jobs-shake-up-2025-navigating-the-new-indexing-api-restrictions/) for testing. Production use requires partner authorization. Smaller job boards and recruitment sites lost direct access. If you run a job board, follow the [**official quickstart**](https://developers.google.com/search/apis/indexing-api/v3/quickstart) and request quota increases through Google Search Console. h2. [RequestIndexing Tool](#requestindexing-tool) [**RequestIndexing**](https://requestindexing.com/) by [**@harlan_zw**](https://x.com/harlan_zw) automates bulk URL submission to Google Search Console. It uses your Search Console credentials to programmatically trigger the manual "Request Indexing" button for multiple URLs. This isn't an official API. it automates the manual workflow. Google allows requesting indexing for individual URLs; RequestIndexing does this in bulk. Use cases: - Bulk indexing after site migration - Re-indexing updated content - Submitting new pages when sitemaps aren't crawled quickly - Emergency indexing for time-sensitive content Limitations: - Requires Google Search Console access - Subject to Google's rate limits - Not instant (still requires Googlebot crawling) - Manual process, not integrated into your build h2. [Choosing the Right Method](#choosing-the-right-method) Pick based on search engine priority and content type: | **Method** | **Best For** | **Limitations** | | --- | --- | --- | | **IndexNow** | Bing/Yandex users, instant updates | Doesn't work for Google | | **Google Indexing API** | Job boards, livestreams | JobPosting/BroadcastEvent only | | **RequestIndexing** | Bulk Google submissions | Manual process, rate limited | | **Search Console Manual** | One-off important pages | Slow, not scalable | | **XML Sitemap** | Routine crawling | Passive, no urgency signal | **Recommendation:** Implement IndexNow for all content changes. Your Bing and Yandex traffic gets instant updates. For Google, use XML sitemaps for routine discovery and RequestIndexing for bulk submissions after major updates. Don't waste time on Google Indexing API unless you're a job board or livestream platform. h2. [Implementation Checklist](#implementation-checklist) 1. Generate IndexNow API key 2. Add key file to `/public/{key}.txt` 3. Create server endpoint to call IndexNow API 4. Trigger notifications in publish/update workflows 5. Submit sitemap to Google Search Console 6. Use RequestIndexing for bulk Google submissions 7. Monitor Search Console for indexing status h2. [Using Nuxt?](#using-nuxt) Nuxt SEO provides automatic IndexNow integration. See [**IndexNow documentation**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexnow/docs/nuxt-seo/integrations/indexnow) for zero-config setup with auto-detection and API key management. Learn more in the [**Nuxt Launch & Listen guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexnow/learn-seo/nuxt/launch-and-listen). --- [**Site Migration** Migrate domains, redesign URLs, or switch frameworks without losing search rankings.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexnow/learn-seo/vue/launch-and-listen/site-migration) [**Debugging** Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/indexnow/learn-seo/vue/launch-and-listen/debugging) **On this page** - [IndexNow Protocol](#indexnow-protocol) - [Google Indexing API](#google-indexing-api) - [RequestIndexing Tool](#requestindexing-tool) - [Choosing the Right Method](#choosing-the-right-method) - [Implementation Checklist](#implementation-checklist) - [Using Nuxt?](#using-nuxt) --- ### Debug Vue SEO Issues · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging Description: Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide. h1. **Debug Vue SEO Issues** Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)12 mins read Published **Dec 17, 2025** **What you'll learn** - View Source shows what Google sees first. Inspect Element shows post-JavaScript DOM - Hydration mismatches break meta tags and cause layout shifts - URL Inspection in Search Console shows exactly what Google renders If you can't see meta tags in View Source, neither can Google. Most Vue SEO bugs come from client-side rendering, hydration mismatches, or Unhead configuration mistakes. h2. [View Source vs Inspect Element](#view-source-vs-inspect-element) Two different ways to view HTML show different content: **View Source** (Right-click → View Page Source) shows the initial HTML your server sends. This is what Google's crawler sees first. **Inspect Element** (F12 → Elements tab) shows the live DOM after JavaScript executes. Meta tags added by JavaScript appear here but might not be indexed. Test: Open your site, right-click, select "View Page Source." Search for `<title>` or `<meta name="description"`. If you can't find your content in View Source, Google can't either. ``` <!-- ❌ BAD: Empty in View Source (SPA without SSR) --> <!DOCTYPE html> <html> <head> <title>Loading...
How to Debug Vue SEO | MySite

How to Debug Vue SEO

Fix meta tags not rendering...

``` Google [**renders JavaScript**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) but it's a two-wave process: crawl the HTML first, then render JavaScript days later. If critical content only appears after JavaScript executes, indexing gets delayed by weeks. h2. [Meta Tags Not Updating on Navigation](#meta-tags-not-updating-on-navigation) Single Page Applications change routes without full page reloads. Meta tags set during initial render might not update when users navigate. **Common mistake:** Using `document.title` instead of Unhead. ``` ``` Fix: Use `useHead()` from Unhead. It tracks navigation and updates meta tags correctly. ``` ``` Unhead synchronizes meta tags across SSR and client-side navigation. The [**Unhead documentation**](https://unhead.unjs.io/usage/composables/use-head) explains reactivity patterns. h3. [Debugging Navigation Updates](#debugging-navigation-updates) Open Chrome DevTools while navigating between pages. Watch the `` section in Elements tab: 1. Open DevTools (F12) 2. Select Elements tab 3. Find `` element 4. Navigate to different page 5. Watch for meta tag updates If meta tags don't change, you're not using `useHead()` or reactive values correctly. h2. [Hydration Mismatches Breaking Meta Tags](#hydration-mismatches-breaking-meta-tags) Hydration is when Vue takes server-rendered HTML and "activates" it with reactivity and event listeners. A hydration mismatch occurs when the HTML rendered on the client differs from the server-rendered HTML. Vue displays warnings like "Hydration completed but contains mismatches" or "Text content does not match server-rendered HTML." These warnings appear in browser console and indicate server/client HTML differences ([**Vue SSR Guide**](https://vuejs.org/guide/scaling-up/ssr.html)). h3. [Common Causes](#common-causes) **Invalid HTML nesting** triggers browser auto-correction, causing mismatches: ``` ``` **Browser-only APIs** don't exist during SSR: ``` ``` **Random values and timestamps** differ between server and client: ``` ``` **Third-party libraries** without SSR support cause mismatches ([**How to Fix Vue Hydration Mismatch**](https://dev.to/jacobandrewsky/how-to-fix-vue-hydration-mismatch-47eg)): ``` ``` h3. [Suppressing Mismatches in Vue 3.5+](#suppressing-mismatches-in-vue-35) For inevitable mismatches (like timestamps), use `data-allow-mismatch`: ``` ``` This tells Vue to expect differences and skip warnings. h3. [External Factors](#external-factors) **HTML minification** causes hydration mismatches. Most HTML minifiers must be disabled ([**Harlan Wilton - Nuxt 3 Hydration Mismatch**](https://harlanzw.com/blog/nuxt3-hydration-node-mismatch)). Cloudflare users: Disable Cloudflare's automatic HTML minifier in dashboard → Speed → Optimization → Auto Minify. **Browser cache** loads outdated JavaScript. Clear cache when debugging mismatches. h2. [Unhead Configuration Mistakes](#unhead-configuration-mistakes) Most Unhead bugs come from setup errors or missing SSR support. h3. [Missing createHead() Setup](#missing-createhead-setup) Unhead requires initialization. Without it, `useHead()` silently fails. ``` // ❌ Missing Unhead setup import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app') ``` Fix: Call `createHead()` and install plugin ([**Unhead Installation Guide**](https://unhead.unjs.io/docs/vue/head/guides/get-started/installation)): ``` import { createHead } from '@unhead/vue/client' // ✅ Correct Unhead setup import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) const head = createHead() app.use(head) app.mount('#app') ``` For SSR apps, import from `@unhead/vue/server` instead. h3. [Wrong Import Paths (Unhead v2)](#wrong-import-paths-unhead-v2) Unhead v2 changed import paths. Old imports fail silently or throw errors. ``` // ❌ Old Unhead v1 imports import { createHead } from '@unhead/vue' // ✅ Unhead v2 subpath imports import { createHead } from '@unhead/vue/client' // Client-side import { createHead } from '@unhead/vue/server' // Server-side ``` Check [**Unhead v2 migration guide**](https://unhead.unjs.io/docs/vue/head/guides/get-started/migration) for breaking changes. h3. [Not Using SSR](#not-using-ssr) Client-only Unhead won't help SEO. Meta tags appear in DevTools but not View Source. ``` // ❌ Client-only setup (no SEO benefit) import { createHead } from '@unhead/vue/client' // ✅ SSR setup (meta tags in initial HTML) import { createHead } from '@unhead/vue/server' ``` Without SSR, search engines see empty `
` containers ([**vue-meta GitHub issue #610**](https://github.com/nuxt/vue-meta/issues/610)). h3. [Reactive Values Not Updating](#reactive-values-not-updating) Passing `.value` instead of the ref breaks reactivity: ``` ``` For computed values, use getter functions: ``` ``` h3. [Duplicate Meta Tags](#duplicate-meta-tags) Multiple `useHead()` calls with same meta tags create duplicates: ``` ``` Unhead deduplicates by default but multiple calls can override unexpectedly. Consolidate meta tags into single `useHead()` call per component. h2. [Chrome DevTools SEO Workflow](#chrome-devtools-seo-workflow) DevTools helps debug meta tags, rendering issues, and JavaScript errors. h3. [1. Check Initial HTML Response](#_1-check-initial-html-response) Network panel shows server-sent HTML before JavaScript executes: 1. Open DevTools (F12) 2. Network tab → Clear (trash icon) 3. Reload page 4. Click first request (usually document) 5. Preview or Response tab shows initial HTML Look for meta tags in this response. If missing, your SSR isn't working. h3. [2. Search for Meta Tags](#_2-search-for-meta-tags) Elements panel lets you find all meta tags quickly: 1. Elements tab 2. Ctrl+F (Cmd+F on Mac) to search 3. Search for `` element - Document has meta description - Links have descriptive text - `` has `lang` attribute Lighthouse doesn't verify correctness. just presence. A title "undefined" passes but isn't useful. h2. [Google Search Console URL Inspection](#google-search-console-url-inspection) URL Inspection tool shows exactly what Google sees when crawling your page. h3. [How to Use URL Inspection](#how-to-use-url-inspection) 1. Open [**Google Search Console**](https://search.google.com/search-console) 2. Enter URL in search bar at top 3. Click "Test Live URL" for current version 4. View results Tool shows ([**Google URL Inspection documentation**](https://support.google.com/webmasters/answer/9012289)): - Whether page is indexed - How it was crawled - What resources were blocked - Structured data Google found - Rendered page screenshot h3. [Index Status](#index-status) "URL is on Google" means the URL is eligible to appear in search results (not guaranteed). "URL is not on Google" means the URL can't appear. check "Page indexing" section for why. h3. [View Crawled Page](#view-crawled-page) See how Google renders your page: 1. Click "View tested page" 2. Screenshot tab shows visual render 3. More info → HTML shows rendered HTML 4. More info → Console log shows JavaScript errors Compare screenshot to your actual page. Missing content indicates rendering problems. h3. [JavaScript Rendering Test](#javascript-rendering-test) Google renders JavaScript but [**processes happen in two waves**](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics): 1. **First wave:** Crawl initial HTML 2. **Second wave:** Render JavaScript (days later) If content only appears after JavaScript, it gets delayed. URL Inspection shows both: - "Live test" → Current rendered version - "Indexed version" → What's in Google's index h3. [Request Indexing](#request-indexing) After fixing issues: 1. URL Inspection → "Request Indexing" 2. Google queues URL for recrawl 3. Re-indexing takes 1-4 weeks [**Google doesn't guarantee indexing**](https://support.google.com/webmasters/answer/7440203). it still evaluates content quality. Daily quota limits requests. For bulk operations, use [**Google Indexing API**](https://developers.google.com/search/apis/indexing-api/v3/quickstart) (up to 2,000 URLs/day). h2. [Common Vue SEO Debugging Scenarios](#common-vue-seo-debugging-scenarios) h3. [Meta Tags Appear in DevTools But Not View Source](#meta-tags-appear-in-devtools-but-not-view-source) **Cause:** Client-side rendering without SSR. **Test:** ``` curl -s https://yoursite.com | grep "" ``` If curl shows empty title, you're not using SSR. **Fix:** Implement SSR using Vite SSR or a framework like Nuxt. See [**Vue rendering modes**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging/learn-seo/vue/routes-and-rendering/rendering) guide. h3. [Meta Tags Don't Update on Navigation](#meta-tags-dont-update-on-navigation) **Cause:** Not using Unhead, or using `document.title` directly. **Test:** Navigate between pages, watch `<title>` in DevTools Elements tab. **Fix:** ``` <script setup> import { useHead } from '@unhead/vue' import { useRoute } from 'vue-router' const route = useRoute() useHead({ title: () => route.meta.title as string }) </script> ``` h3. [Google Shows Wrong Title/Description](#google-shows-wrong-titledescription) **Cause:** [**Google rewrites 60-70% of meta descriptions**](https://dev.to/hmzas/vuejs-seo-in-2025-why-you-still-need-server-side-rendering-ssr-12b9) and titles it deems low-quality. **Test:** Search for `site:yoursite.com` in Google. Compare displayed titles to your actual meta tags. **Fix:** Write better titles and descriptions: - Titles: 50-60 characters, unique per page, include target keyword - Descriptions: 150-160 characters, compelling copy, match search intent - Avoid duplicates across pages - Make them relevant to page content Google still might rewrite. that's normal. If it rewrites everything, your meta tags are probably too generic or off-topic. h3. [Hydration Mismatch on Meta Tags](#hydration-mismatch-on-meta-tags) **Cause:** Server and client render different meta tag values (timestamps, random IDs, browser APIs). **Test:** Look for console warnings: "Hydration completed but contains mismatches." **Fix:** Use `ClientOnly` wrapper or `data-allow-mismatch`: ``` <script setup> import { onMounted, ref } from 'vue' const timestamp = ref('') onMounted(() => { timestamp.value = new Date().toISOString() }) useHead({ meta: [ { property: 'og:updated_time', content: timestamp } ] }) </script> ``` h3. [Content Loads After SSR](#content-loads-after-ssr) **Cause:** Data fetched in `onMounted()` instead of during SSR. **Test:** View Source shows "Loading..." instead of actual content. **Fix:** Fetch data before render: ``` <!-- ❌ Client-only fetch --> <script setup> import { onMounted, ref } from 'vue' </script> <!-- ✅ SSR-compatible fetch --> <script setup> const post = ref({ title: 'Loading...' }) onMounted(async () => { post.value = await fetch('/api/post').then(r => r.json()) }) useHead({ title: () => post.value.title }) const post = ref(await fetch('/api/post').then(r => r.json())) useHead({ title: () => post.value.title }) </script> ``` For client-only apps, consider [**prerendering critical pages**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging/learn-seo/vue/spa/prerendering). h2. [Testing Before Deployment](#testing-before-deployment) Don't wait for Google to tell you something's broken. Test locally: h3. [1. View Source Test](#_1-view-source-test) Right-click → View Page Source. Search for critical meta tags. If they're missing, fix SSR before deploying. h3. [2. Curl Test](#_2-curl-test) ``` h1. Check initial HTML curl -s https://yoursite.com | grep -A 5 "<head>" h1. Check specific meta tag curl -s https://yoursite.com | grep 'name="description"' ``` Should show full meta tags in output. h3. [3. Search Console Test Environment](#_3-search-console-test-environment) Set up staging site in Search Console. Test URL Inspection on staging before pushing to production. h3. [4. Lighthouse CI](#_4-lighthouse-ci) Automate SEO checks in CI/CD: ``` npm install -g @lhci/cli h1. Run Lighthouse lhci autorun --collect.url=http://localhost:3000 ``` Configure assertion for minimum SEO score. h2. [Using Nuxt?](#using-nuxt) Nuxt handles SSR and meta tag management automatically. Most debugging issues disappear with Nuxt's built-in SEO features. See [**Nuxt SEO debugging guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging/learn-seo/nuxt/launch-and-listen/debugging) for framework-specific solutions. --- [**IndexNow** Notify search engines instantly when content changes using IndexNow, Google Indexing API, and RequestIndexing.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging/learn-seo/vue/launch-and-listen/indexnow) [**AI Search Optimization** Make your Vue site visible in AI Overviews, ChatGPT, and other generative search engines with structured data and content strategies.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/debugging/learn-seo/vue/launch-and-listen/ai-optimized-content) **On this page** - [View Source vs Inspect Element](#view-source-vs-inspect-element) - [Meta Tags Not Updating on Navigation](#meta-tags-not-updating-on-navigation) - [Hydration Mismatches Breaking Meta Tags](#hydration-mismatches-breaking-meta-tags) - [Unhead Configuration Mistakes](#unhead-configuration-mistakes) - [Chrome DevTools SEO Workflow](#chrome-devtools-seo-workflow) - [Google Search Console URL Inspection](#google-search-console-url-inspection) - [Common Vue SEO Debugging Scenarios](#common-vue-seo-debugging-scenarios) - [Testing Before Deployment](#testing-before-deployment) - [Using Nuxt?](#using-nuxt) --- ### Releases · Nuxt SEO Source: https://nuxtseo.com/releases Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Nuxt SEO Releases** See what has been shipping recently. Last fetched: 1 minute ago. Please use GitHub to check for realtime updates, this list is only updated every 24 hours. Please check GitHub for releases further back. --- ### Users · Nuxt SEO Source: https://nuxtseo.com/admin/users Description: Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines. h1. **Admin Access Required ** Sign in with an admin GitHub account. [**Sign In with GitHub **](https://nuxtseo.com/admin/users/auth/github) --- ### Announcing Nuxt SEO Stable · Nuxt SEO Source: https://nuxtseo.com/announcement Description: Nuxt SEO v2 is here, learn about the journey of this milestone and what's next. h1. **Announcing Nuxt SEO Stable** Nuxt SEO v2 is here, learn about the journey of this milestone and what's next. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw) **5 mins read** h2. [Introduction](#introduction) Two years ago I set out to build a suite of SEO modules for Nuxt that would help you with your technical SEO. Today I'm excited to announce that Nuxt SEO has reached stable. In this post I'll tell you a bit about the history of building Nuxt SEO, what the stable means and what's next. h2. [Nuxt SEO History](#nuxt-seo-history) As a Nuxt v2 user, I wasn't entirely happy with the SEO modules available. My concerns with them can be summarised as: - **😔 Boilerplate heavy**: Relied on the end-user implementing a lot of boilerplate where data can be [**easily inferred**](https://nuxtseo.com/announcement/docs/sitemap/guides/data-sources). - **😔 No cross-module compatibility**: Modules did not work with eachother, they can and _should_ inform each other, consider adding a `/sitemap.xml` entry to your `./robots.txt` . - **😔 Poor documentation**: While the original authors did their best, it wasn't always clear how to do more complex things with the modules. For those who didn't go through the early days of Nuxt v3, it was a bit chaotic. The ecosystem was in a state of flux, many modules were not compatible with the new version and previous maintainers had moved on to other projects. This was the golden opportunity to build new modules specifically for Nuxt v3 that could opt-in to the new module system and take advantage of the new features that Nuxt v3 had to offer without being bogged down by legacy code. I set out by introducing two "simple" modules - Nuxt Simple Robots and Nuxt Simple Sitemap. [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/announcement/docs/robots/getting-started/introduction) [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/announcement/docs/sitemap/getting-started/introduction) My principals for building them were: - **✨ Immediate value**: Once installed the modules should immediately start providing value with minimal configuration. - **🔄 Cross-module compatibility**: SEO modules should talk to each other where possible. Core ecosystem modules should be supported out of the box: [**Nuxt I18n**](https://i18n.nuxtjs.org/), [**Nuxt Content**](https://content.nuxt.com/), [**Nuxt DevTools**](https://devtools.nuxt.com/), etc. - **🟢 Don't get in the way**: All modules expose hooks that give you the final say in the behavior of the module. We prefer hooks over a tedium of configuration options. As these modules gained traction it was clear there was a demand for these technical SEO modules. So I continued and built the following: [OG Image **v5.1.13** 2.9M 495 Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/announcement/docs/og-image/getting-started/introduction) [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/announcement/docs/schema-org/getting-started/introduction) [Link Checker **v4.3.9** 2.3M 95 Find and magically fix links that may be negatively effecting your SEO.](https://nuxtseo.com/announcement/docs/link-checker/getting-started/introduction) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/announcement/docs/seo-utils/getting-started/introduction) The final touch was to combine them all into a module you could install without installing each one individually and a module that would sync the config between all of the modules [Nuxt SEO **v3.3.0** 2.1M 1.3K The all-in-one module that brings it all together.](https://nuxtseo.com/announcement/docs/nuxt-seo/getting-started/introduction) [Site Config **v3.2.14** 8.9M 75 Powerful build and runtime shared site configuration for Nuxt modules.](https://nuxtseo.com/announcement/docs/site-config/getting-started/introduction) This was introduced as Nuxt SEO Kit at the time and was targeted at **only prerendered apps**. h2. [Stable Release: v2](#stable-release-v2) Nuxt SEO Kit was released as production-ready, however it was never marked as stable for non-prerendered apps as the submodules required several breaking changes as they evolved. As more and more use cases got handled, the module was renamed to Nuxt SEO, and we entered a lengthy RC and beta phase. To consider Nuxt SEO stable I wanted to support every possible way of building a Nuxt app: Single Page, Server-Side Generated, Server-Side Rendered, Multi-tenancy, Base URLs, Trailing Slashes, etc. I also wanted to make sure that the modules integrated with core community modules like Nuxt Content, Nuxt i18n, Nuxt DevTools, etc. As of today, the modules support all of these use cases and more with a full suite of tests to avoid regressions. As you can imagine this took a lot of work to get right. In fact, it took exactly **4.4k commits, 1.2k issues and 72 contributes** to pull off. In this time the project has grown significantly, with the modules cumulatively getting **1.6M downloads a month**. I won't bore you with all the minor details related to this [**releases**](https://nuxtseo.com/announcement/releases), but what you should know: - No breaking changes are planned for the foreseeable future unless they're unavoidable. - Most modules are considered "done", new features will only be introduced in exceptional cases. - The modules can be considered battle tested with a large number of users and use cases. The final thing to get the stable out was redoing all of the docs. The docs are the main interface between you and the module and they should reflect the care and attention that went into building the modules. h2. [New Docs](#new-docs) The new docs have been designed in a way to optimize for readability, giving whitespace the respect it deserves. They use the greenest of the greenfield: Nuxt v4, [**Nuxt UI v3**](https://ui3.nuxt.dev/) (Tailwind v4), Nuxt Content v3 You'll also find some new features: - [**Releases**](https://nuxtseo.com/announcement/releases) page to see what's new in each version. - New learning resources to help you get started with technical SEO, check out the [**Controlling Web Crawlers**](https://nuxtseo.com/announcement/learn-seo/nuxt/controlling-crawlers) and [**Page Titles**](https://nuxtseo.com/announcement/learn-seo/nuxt/mastering-meta/titles) guides. - The ability to switch majors on the sidebar, this will be possible for future major versions as the content structure has changed to support this. - Feedback buttons on every docs page, let me know how I'm doing! h2. [What's Next?](#whats-next) There are a few upcoming upstream updates that Nuxt SEO will need to support: - **Nuxt v4 / Nitro v3**: While Nuxt SEO has been tested with Nuxt v4, it has not been fully tested with Nitro v3 which is still in development. - **[**Nuxt Content v3**](https://content3.nuxt.dev/)**: Will likely require new module configuration and hooks. - **[**Nuxt I18n v9**](https://i18n.nuxtjs.org/docs/v9/guide/breaking-changes-in-v9)**: Should already be fully supported but will require more testing once stable. As the project is now officially stable I look forward to what else is missing from the Nuxt SEO ecosystem. h2. [Nuxt SEO Pro](#nuxt-seo-pro) I have some ambitious goals for new modules to build and content to create that will continue to help with your technical SEO but also help you in growing your site organically. To align incentives with myself and you, the community, I'm introducing a Pro version of Nuxt SEO to help fund the development. I will be working on a one-time payment Pro version of Nuxt SEO that will include access to new modules and learning resources that will be ready mid-2025. You can learn more and pre-purchase it for a discount on the [**Nuxt SEO Pro**](https://nuxtseo.com/announcement/pro) page. h2. [Conclusion](#conclusion) I'd like to thank the community for their patience and feedback to get the project to where it is today. My sponsors have also helped me immensely as it's difficult to justify working so many hours on an open-source project without financial support. Thanks for your time and I hope you enjoy Nuxt SEO! --- ### Nuxt SEO Pro: Community Feedback Analysis · Nuxt SEO Pro Source: https://nuxtseo.com/pro/feedback Description: A review of the feedback from early adopters of Nuxt SEO Pro. h1. **Nuxt SEO Pro: Community Feedback Analysis** A review of the feedback from early adopters of Nuxt SEO Pro. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw) **5 mins read** First of all, **thank you** to everyone who pre-ordered Nuxt SEO Pro! Your early support means a lot, and I'm excited to build something that truly meets your needs. I reached out to the early adopters with some questions about their SEO experience and what they're looking for in Nuxt SEO Pro. Here's what I learned from your responses. h2. [Who's Using Nuxt SEO Pro?](#whos-using-nuxt-seo-pro) Our early adopters represent a diverse mix of SEO experience levels: SEO Experience Levels Beginner 40% Intermediate 35% Experienced 25% This diverse audience means Nuxt SEO Pro needs to serve different needs: - **Beginners (40%)** want guidance and education alongside tools. Many of you expressed having "Z E R O" experience with SEO and are looking for Nuxt-specific technical education to get started. - **Intermediate (35%)** users want to save time and improve their implementation. You're "fairly technical with SEO" but looking for efficient ways to integrate SEO best practices into your development workflow. - **Experienced (25%)** SEO practitioners want deeper integration with your development workflow. Some of you have "over a decade of SEO experience" and are looking for tools that enhance your existing expertise. h2. [Module Ratings](#module-ratings) You rated each proposed module on a scale of 1-10. Here's how they ranked: Module Ratings (Average out of 10) SEO Analyze Google Search Console Internal Links 8.4/10 7.8/10 7.0/10 0 2 4 6 8 10 Redirects rated 5.4/10 (not shown) The feedback was clear: SEO Analyze (8.4/10) and Google Search Console integration (7.8/10) were the most in-demand modules, followed by Internal Links (7.0/10) and Redirects (5.4/10). h2. [Key Insights From Your Feedback](#key-insights-from-your-feedback) h3. [What You're Looking For](#what-youre-looking-for) 1. **Time-Saving Tools**: _"I have many other things to do other than SEO, so I thought your package would help me gain time."_ 2. **Developer-Focused Education**: _"I've found it hard to find tutorials that explain SEO from a developer's perspective at an intermediate level."_ 3. **Integrated Solutions**: _"I like the idea of starting a project, installing Nuxt SEO, and having everything I need in one place."_ Many of you mentioned that knowing "where to start" with Nuxt SEO is difficult and time-consuming. There's a strong desire for starter templates and recipes that can help implement SEO best practices efficiently. h3. [Top Feature Requests](#top-feature-requests) Top Feature Requests Favicon Module Schema.org Integration Multi-tenant SEO Content Tools The favicon generator emerged as a surprisingly popular request, with multiple users expressing the need for comprehensive favicon handling. This feedback has directly influenced the product roadmap. h3. [Educational Content Needs](#educational-content-needs) Your feedback was clear that educational content is just as important as the tools themselves. Here's what you asked for: - **Developer-centric SEO explanations**: _"Routing me to resources that demystify SEO for me"_ - **Code examples**: _"The more GitHub repos you have, the better. I can read the code."_ - **Strategy guidance**: _"What's relevant to do Off-Site, what are the steps to boost a page on launch"_ - **Video walkthroughs**: _"Video explainers are always welcome"_ This aligns perfectly with the feedback that 40% of you are new to SEO and are specifically looking for Nuxt-specific technical education for SEO. h2. [Detailed Module Insights](#detailed-module-insights) Let's look deeper at what you told me about each module: h3. [SEO Analyze (8.4/10)](#seo-analyze-8410) The clear winner across all experience levels. You value: - Build-time validation to prevent SEO mistakes - Clear guidance on improving existing pages - Automated checks that feel like "extra guardrails" _"SEO Analyze seems like more easy SEO build time wins and it's nice to have the extra guardrails."_ h3. [Google Search Console (7.8/10)](#google-search-console-7810) Strong interest particularly from those already using GSC: - Simplifying complex data into actionable insights - Connecting page performance to specific improvements - Integrating analytics into the development workflow _"Google search console is a bit sparse on info, but what is there often feels disjointed/nonsensical so maybe insights with a more direct connection to each page during build time will help."_ h3. [Internal Links (7.0/10)](#internal-links-7010) Moderate enthusiasm with interesting educational gaps: - Some users aren't familiar with internal linking importance - Others see high value in automated suggestions - All agree that implementation should be straightforward _"More easy SEO wins from the Link Checker at build time is always good."_ h3. [Redirects (5.4/10)](#redirects-5410) The most polarized module, with responses ranging from 2/10 to 10/10: - Many already have solution in place - Others see high value in automated 404 prevention - The "magic" factor is appealing to some _"I quite enjoy my redirects file on CF pages, not sure I'll get much use from this module."_ h2. [Challenges & Pain Points](#challenges-pain-points) Beyond feedback on specific modules, you shared several common challenges: h3. [SEO Knowledge Gap](#seo-knowledge-gap) Many of you expressed difficulty connecting development knowledge with SEO concepts: _"I don't know what I don't know and trying to unravel the stack between 'nuxt.config.js' and 'the theory of SEO' and 'my marketing team's wants and needs' is difficult."_ h3. [Technical Implementation Hurdles](#technical-implementation-hurdles) Even those with SEO knowledge struggle with implementation details: _"I often feel lost about routeRules."_ _"I found it hard to customize if I wanted to hide certain pages I'm not using in my links but exist as routes (in /pages) and don't want them indexed or mapped."_ h3. [Time Management](#time-management) Many of you purchased Nuxt SEO Pro specifically to save time: _"I have many other things to do other than SEO, so I thought your package would help me gain time."_ h2. [Conclusion](#conclusion) Thank you again for your support and feedback. This analysis has been incredibly valuable in shaping the future of Nuxt SEO Pro. ~ Harlan --- ### Updating Nuxt Modules · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/guides/updating-modules Description: Keep your Nuxt SEO modules up-to-date for bug fixes and new features. **Core Concepts** h1. **Updating Nuxt Modules** [Copy for LLMs h2. [Update Command](#update-command) Since `@nuxtjs/seo` bundles all submodules, you only need to update the main package: ``` pnpm update @nuxtjs/seo --latest ``` ``` npm update @nuxtjs/seo@latest ``` ``` yarn upgrade @nuxtjs/seo --latest ``` ``` bun update @nuxtjs/seo --latest ``` h2. [When Updates Get Stuck](#when-updates-get-stuck) Package managers can cache old versions. If updates aren't applying: ``` rm -rf node_modules pnpm-lock.yaml && pnpm install ``` ``` rm -rf node_modules package-lock.json && npm install ``` ``` rm -rf node_modules yarn.lock && yarn install ``` ``` rm -rf node_modules bun.lockb && bun install ``` h2. [Check for Breaking Changes](#check-for-breaking-changes) Before updating major versions, check the [**GitHub Releases**](https://github.com/nuxt-modules/seo/releases) for migration notes. Individual module changelogs: - [**Sitemap**](https://github.com/nuxt-modules/sitemap/releases) - [**Robots**](https://github.com/nuxt-modules/robots/releases) - [**OG Image**](https://github.com/nuxt-modules/og-image/releases) - [**Schema.org**](https://github.com/nuxt-modules/schema-org/releases) - [**Link Checker**](https://github.com/nuxt-modules/link-checker/releases) - [**SEO Utils**](https://github.com/nuxt-modules/seo-utils/releases) h2. [Verify the Update](#verify-the-update) After updating, check your installed version: ``` npx nuxi info ``` Look for `@nuxtjs/seo` in the modules list. [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/2.guides/1.updating-modules.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/guides/updating-modules/docs/nuxt-seo/guides/updating-modules.md) **Did this page help you? ** [**Quick Setup Guide** A quick guide on which features are available on activating Nuxt SEO.](https://nuxtseo.com/docs/nuxt-seo/guides/updating-modules/docs/nuxt-seo/guides/using-the-modules) [**Nuxt Content** Integrating Nuxt SEO with Nuxt Content.](https://nuxtseo.com/docs/nuxt-seo/guides/updating-modules/docs/nuxt-seo/guides/nuxt-content) **On this page** - [Update Command](#update-command) - [When Updates Get Stuck](#when-updates-get-stuck) - [Check for Breaking Changes](#check-for-breaking-changes) - [Verify the Update](#verify-the-update) --- ### LLMs.txt · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/guides/llms-txt Description: Help AI tools understand Nuxt SEO modules so they can assist you better. **Core Concepts** h1. **LLMs.txt** [Copy for LLMs AI tools work better when they understand your stack. LLMs.txt gives Cursor, Windsurf, ChatGPT, and Claude structured docs about Nuxt SEO so you spend less time explaining context and more time building. h2. [Available Routes](#available-routes) We provide two documentation formats optimized for AI consumption: - **/llms.txt** - Structured overview of all modules and docs links (~5K tokens) - **/llms-full.toon** - Complete documentation with examples in [**TOON format**](https://toonformat.dev), a token-efficient encoding that fits more context Start with `/llms.txt`. Use `/llms-full.toon` when you need comprehensive examples and your AI tool supports large contexts (200K+ tokens). h2. [Usage](#usage) h3. [Cursor](#cursor) Reference the docs directly in chat or add to project context with `@docs`: ``` @https://nuxtseo.com/llms.txt ``` [**Cursor Web and Docs Search →**](https://docs.cursor.com/en/context/@-symbols/@-docs) h3. [Windsurf](#windsurf) Use `@docs` to reference the URLs or add them to workspace rules: ``` @https://nuxtseo.com/llms.txt ``` [**Windsurf Web and Docs Search →**](https://docs.windsurf.com/windsurf/cascade/web-search) h3. [ChatGPT / Claude](#chatgpt-claude) Paste the URL in your prompt: ``` Using Nuxt SEO docs from https://nuxtseo.com/llms.txt, help me configure... ``` **Type @ manually** - In Cursor and Windsurf, the `@` symbol must be typed by hand. Copy-pasting breaks context recognition. h2. [TOON Format](#toon-format) The `/llms-full.toon` route uses [**TOON**](https://toonformat.dev) (Token-Oriented Object Notation), a format designed for LLMs that's more token-efficient than JSON. AI tools read it natively while fitting more documentation in context. h2. [Nuxt SEO Pro](#nuxt-seo-pro) Want AI tools with direct access to module APIs? [**Nuxt SEO Pro MCP**](https://nuxtseo.com/docs/nuxt-seo/guides/llms-txt/docs/nuxt-seo-pro/mcp/installation) provides tools and prompts AI agents can call directly. [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/2.guides/4.llms-txt.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/guides/llms-txt/docs/nuxt-seo/guides/llms-txt.md) **Did this page help you? ** [**MCP Server** Connect AI assistants to Nuxt SEO documentation using the Model Context Protocol.](https://nuxtseo.com/docs/nuxt-seo/guides/llms-txt/docs/nuxt-seo/guides/mcp) [**Understanding Site Config** Site Config shares your site URL, name, and metadata across all SEO modules. Set it once, use it everywhere.](https://nuxtseo.com/docs/nuxt-seo/guides/llms-txt/docs/nuxt-seo/guides/site-config) **On this page** - [Available Routes](#available-routes) - [Usage](#usage) - [TOON Format](#toon-format) - [Nuxt SEO Pro](#nuxt-seo-pro) --- ### Troubleshooting · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/getting-started/troubleshooting Description: Common issues and fixes for Nuxt SEO modules. **Getting Started** h1. **Troubleshooting** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs Questions? [**Ask in Discord**](https://discord.com/invite/5jDAMswWwX). h2. [FAQ](#faq) h3. [Can I use modules separately?](#can-i-use-modules-separately) Yes. Install any module individually instead of `@nuxtjs/seo`. h3. [Why is my production build so large?](#why-is-my-production-build-so-large) Server-side features add a few MB to the build. This doesn't affect client performance—modules lazy load. For serverless (<1MB workers), `nuxt-og-image` is the largest contributor. If targeting <1MB workers (Cloudflare Workers free tier), disable nuxt-og-image or switch to Zero Runtime mode to stay under the size limit. Options: 1. Disable it: `ogImage: { enabled: false }` 2. Use [**Zero Runtime**](https://nuxtseo.com/docs/nuxt-seo/getting-started/troubleshooting/docs/og-image/guides/zero-runtime) mode (build-time generation) h3. [What happened to Nuxt SEO Kit?](#what-happened-to-nuxt-seo-kit) Deprecated. It only worked for SSG sites. See the [**migration guide**](https://nuxtseo.com/docs/nuxt-seo/getting-started/troubleshooting/docs/nuxt-seo/migration-guide/nuxt-seo-kit). h2. [Create a Reproduction](#create-a-reproduction) When filing issues, include a minimal reproduction. See [**Debugging Modules**](https://nuxtseo.com/docs/nuxt-seo/getting-started/troubleshooting/docs/nuxt-seo/guides/debugging-modules#creating-a-minimal-reproduction) for StackBlitz playgrounds. [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/1.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/getting-started/troubleshooting/docs/nuxt-seo/getting-started/troubleshooting.md) **Did this page help you? ** [**Installation** Get started with Nuxt SEO by installing the dependency to your project.](https://nuxtseo.com/docs/nuxt-seo/getting-started/troubleshooting/docs/nuxt-seo/getting-started/installation) [**Community Videos** Learn from the Nuxt community in using Nuxt SEO.](https://nuxtseo.com/docs/nuxt-seo/getting-started/troubleshooting/docs/nuxt-seo/getting-started/community-videos) **On this page** - [FAQ](#faq) - [Create a Reproduction](#create-a-reproduction) --- ### Understanding Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/guides/site-config Description: Site Config shares your site URL, name, and metadata across all SEO modules. Set it once, use it everywhere. **Core Concepts** h1. **Understanding Site Config** [Copy for LLMs Every SEO module needs your site URL—sitemaps, OG images, Schema.org identifiers. Site Config lets you set it once instead of duplicating config across modules. h2. [Quick Setup](#quick-setup) For most sites, add this to `nuxt.config.ts`: ``` export default defineNuxtConfig({ site: { url: 'https://example.com', name: 'My Site' } }) ``` That's it. All modules now know your canonical URL and site name. h2. [Available Options](#available-options) | **Option** | **Purpose** | **Default** | | --- | --- | --- | | `url` | Canonical site URL (required for SEO) | Auto-detected in dev | | `name` | Site name for meta tags, Schema.org | - | | `description` | Default meta description | - | | `defaultLocale` | Language code (e.g., `en`) | Auto from i18n | | `indexable` | Allow search engine indexing | `true` in production | | `trailingSlash` | URLs end with `/` | `false` | See [**full config reference**](https://nuxtseo.com/docs/nuxt-seo/guides/site-config/docs/site-config/api/config) for all options. h2. [Environment-Specific Config](#environment-specific-config) Running staging or preview environments? Use environment variables so each deployment gets the right URL: ``` h1. .env.staging NUXT_SITE_URL=https://staging.example.com NUXT_SITE_ENV=staging ``` ``` h1. .env.production NUXT_SITE_URL=https://example.com NUXT_SITE_ENV=production ``` Non-production environments are automatically blocked from indexing. Non-production environments are automatically blocked from indexing via the robots module. No need to manually configure `noindex` for staging or preview deployments. h2. [Reading Site Config](#reading-site-config) Access your config anywhere with `useSiteConfig()`: ``` <script setup> const site = useSiteConfig() // site.url, site.name, site.description, etc. </script> ``` Works in components, composables, and server routes. h2. [Multi-Tenancy](#multi-tenancy) Serving multiple domains from one Nuxt app? Site Config handles this: ``` export default defineNuxtConfig({ site: { multiTenancy: [ { hosts: ['example.com', 'www.example.com'], config: { name: 'Example', url: 'https://example.com' } }, { hosts: ['foo.com', 'www.foo.com'], config: { name: 'Foo', url: 'https://foo.com' } } ] } }) ``` The correct config loads based on the incoming request hostname. See the [**Multi-Tenancy guide**](https://nuxtseo.com/docs/nuxt-seo/guides/site-config/docs/site-config/guides/multi-tenancy) for runtime configuration. [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/2.guides/5.site-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/guides/site-config/docs/nuxt-seo/guides/site-config.md) **Did this page help you? ** [**LLMs.txt** Help AI tools understand Nuxt SEO modules so they can assist you better.](https://nuxtseo.com/docs/nuxt-seo/guides/site-config/docs/nuxt-seo/guides/llms-txt) [**Debugging Modules** Disable modules and create minimal reproductions for Nuxt SEO issues.](https://nuxtseo.com/docs/nuxt-seo/guides/site-config/docs/nuxt-seo/guides/debugging-modules) **On this page** - [Quick Setup](#quick-setup) - [Available Options](#available-options) - [Environment-Specific Config](#environment-specific-config) - [Reading Site Config](#reading-site-config) - [Multi-Tenancy](#multi-tenancy) --- ### Nuxt SEO Pro · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/introduction Description: The futures SEO modules for todays Nuxters. Handle llms.txt, MCP, and version skew for your Nuxt site. **Getting Started** h1. **Nuxt SEO Pro** [Copy for LLMs h2. [Why This Exists](#why-this-exists) People ask ChatGPT instead of Google now. If AI can't parse your content, you're invisible. ChatGPT, Claude, and Perplexity pull answers from sources they can parse. If your content isn't structured for AI, you're invisible to a growing audience. h2. [What's Included](#whats-included) Three modules. One license. All work independently or together. h3. [AI Ready](#ai-ready) Drive organic traffic from AI. - **Auto-generated llms.txt** — Your entire site converted to AI-readable markdown, kept in sync automatically - **MCP for live queries** — AI agents fetch your latest content directly instead of relying on stale training data - **RAG-ready exports** — Token-optimized chunks ready for embedding and semantic search pipelines h3. [AI RAG](#ai-rag) AI chat for your site, without the lock-in. Users expect to ask questions, not dig through docs. Add conversational search that runs on your infrastructure, with the LLM of your choice. - **Own your data** — Embeddings stay on your vector DB. Switch providers without re-indexing - **Any LLM, any vector store** — OpenAI, Anthropic, Ollama. libSQL, pgvector, Upstash, Cloudflare Vectorize - **Nuxt UI components included** — Chat interface that matches your design system, ready to customize h3. [Skew Protection](#skew-protection) Never break a session. Every deployment breaks 2-5% of active sessions. Users hit ChunkLoadError, see blank screens, and leave. This catches it before they do. - **Catches stale chunks before they break** — Detects client/server mismatch and prompts users to refresh gracefully - **Persistent assets across deploys** — Old chunks stay accessible so crawlers and slow sessions don't 404 - **Zero config** — Works out of the box on Vercel, Netlify, Cloudflare h2. [Platform Support](#platform-support) Deploys where you deploy. Zero platform-specific config. Works with your existing Nuxt setup. Vercel, Netlify, Cloudflare, self-hosted. h2. [Pricing](#pricing) One payment. Everything included. - $119 early access (normally $249) - All three modules, one license - Lifetime updates, no recurring fees - Private GitHub repo access - 30-day refund, no questions asked Try free in dev. License required for production. h2. [Next Steps](#next-steps) Ready to get started? Head to the [**installation guide**](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/introduction/docs/nuxt-seo-pro/getting-started/installation). Already using the free Nuxt SEO modules? Pro modules complement them—no conflicts, just additional capabilities. [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/1.getting-started/0.introduction.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/introduction/docs/nuxt-seo-pro/getting-started/introduction.md) **Did this page help you? ** [**Installation** Get the Pro modules running locally in minutes. Free to try—license required for production.](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/introduction/docs/nuxt-seo-pro/getting-started/installation) **On this page** - [Why This Exists](#why-this-exists) - [What's Included](#whats-included) - [Platform Support](#platform-support) - [Pricing](#pricing) - [Next Steps](#next-steps) --- ### Community Videos · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/getting-started/community-videos Description: Learn from the Nuxt community in using Nuxt SEO. **Getting Started** h1. **Community Videos** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs ![](https://i.ytimg.com/vi_webp/OyVI8zmDqWU/hq720.webp) h2. Nuxt 3 SEO (intro to Nuxt SEO) ![](https://i.ytimg.com/vi_webp/CPZTMlarbKg/hq720.webp) h2. Easy SEO with Nuxt and Storyblok [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/1.getting-started/4.community-videos.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/getting-started/community-videos/docs/nuxt-seo/getting-started/community-videos.md) **Did this page help you? ** [**Troubleshooting** Common issues and fixes for Nuxt SEO modules.](https://nuxtseo.com/docs/nuxt-seo/getting-started/community-videos/docs/nuxt-seo/getting-started/troubleshooting) [**Quick Setup Guide** A quick guide on which features are available on activating Nuxt SEO.](https://nuxtseo.com/docs/nuxt-seo/getting-started/community-videos/docs/nuxt-seo/guides/using-the-modules) --- ### Nuxt Content · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content Description: Integrating Nuxt SEO with Nuxt Content. **Core Concepts** h1. **Nuxt Content** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs Most Nuxt SEO modules integrate with Nuxt Content out of the box. - Nuxt Robots: `robots` ([**docs**](https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content/docs/robots/guides/content)) - Nuxt Sitemap: `sitemap` ([**docs**](https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content/docs/sitemap/guides/content)) - Nuxt OG Image: `ogImage` ([**docs**](https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content/docs/og-image/integrations/content)) - Nuxt Schema.org: `schemaOrg` ([**docs**](https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content/docs/schema-org/guides/content)) - Nuxt Link Checker: Uses content APIs to check links For Nuxt Content v3 you would need to configure the modules to work with Nuxt Content individually, however, Nuxt SEO provides a way to configure all modules at once. For Nuxt Content v2, please see the individual module documentation for how to configure them. h2. [Setup Nuxt Content v3](#setup-nuxt-content-v3) In Nuxt Content v3 we need to use the `asSeoCollection()` function to augment any collections to be able to use the SEO modules. content.config.ts ``` import { defineCollection, defineContentConfig } from '@nuxt/content' import { asSeoCollection } from '@nuxtjs/seo/content' export default defineContentConfig({ collections: { content: defineCollection( asSeoCollection({ type: 'page', source: '**/*.md', }), ), }, }) ``` To ensure the tags actually gets rendered you need to ensure you're using the SEO composable. [...slug].vue ``` <script setup lang="ts"> import { queryCollection, useRoute } from '#imports' const route = useRoute() const { data: page } = await useAsyncData(\`page-${route.path}\`, () => { return queryCollection('content').path(route.path).first() }) if (page.value?.ogImage) { defineOgImage(page.value?.ogImage) // <-- Nuxt OG Image } // Ensure the schema.org is rendered useHead(page.value.head || {}) // <-- Nuxt Schema.org useSeoMeta(page.value.seo || {}) // <-- Nuxt Robots </script> ``` Due to current Nuxt Content v3 limitations, you must load the Nuxt SEO module before the content module. Module order is critical. `@nuxtjs/seo` must come before `@nuxt/content` in your modules array. Wrong order causes silent failures where SEO features appear to work but frontmatter isn't processed correctly. ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/seo', '@nuxt/content' // <-- Must be after @nuxtjs/seo ] }) ``` h2. [Usage](#usage) For the full options available for each module, please see the individual module documentation. ``` --- ogImage: component: HelloWorld props: title: "Hello World" description: "This is a description" image: "/hello-world.png" sitemap: lastmod: 2025-01-01 robots: index, nofollow schemaOrg: - "@type": "BlogPosting" headline: "How to Use Our Product" author: type: "Person" name: "Jane Smith" datePublished: "2023-10-01" --- h1. Hello World ``` [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/2.guides/2.nuxt-content.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content/docs/nuxt-seo/guides/nuxt-content.md) **Did this page help you? ** [**Updating Nuxt Modules** Keep your Nuxt SEO modules up-to-date for bug fixes and new features.](https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content/docs/nuxt-seo/guides/updating-modules) [**MCP Server** Connect AI assistants to Nuxt SEO documentation using the Model Context Protocol.](https://nuxtseo.com/docs/nuxt-seo/guides/nuxt-content/docs/nuxt-seo/guides/mcp) **On this page** - [Setup Nuxt Content v3](#setup-nuxt-content-v3) - [Usage](#usage) --- ### Quick Setup Guide · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules Description: A quick guide on which features are available on activating Nuxt SEO. **Core Concepts** h1. **Quick Setup Guide** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs Nuxt SEO includes 6 modules. Most work out-of-the-box. [**Try the StackBlitz demo**](https://stackblitz.com/edit/nuxt-starter-zycxux?file=nuxt.config.ts). h2. [Sitemap](#sitemap) [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/sitemap/getting-started/introduction) Generates a sitemap at `/sitemap.xml` so search engines can discover your pages. ``` npx nuxi module add @nuxtjs/sitemap ``` **Default behavior:** - Auto-generates sitemap from your app routes - Includes `lastmod` when available - Links to sitemap in `robots.txt` h3. [Examples](#examples) ``` export default defineNuxtConfig({ sitemap: { // fetch URLs from an API endpoint sources: ['/api/__sitemap__/urls'] } }) ``` ``` export default defineNuxtConfig({ sitemap: { // auto-configured when i18n detected // creates /en-sitemap.xml, /fr-sitemap.xml, etc. } }) ``` ``` --- h1. content/blog/my-post.md sitemap: lastmod: 2024-01-15 changefreq: weekly priority: 0.8 --- ``` See the [**Sitemap documentation**](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/sitemap/getting-started/introduction) for all options. h2. [Robots](#robots) [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/robots/getting-started/introduction) Controls search engine crawling via `robots.txt`, meta tags, and HTTP headers. ``` npx nuxi module add @nuxtjs/robots ``` **Default behavior:** - Generates `/robots.txt` allowing all routes - Blocks indexing in non-production environments - Adds `<meta name="robots">` and `X-Robots-Tag` header h3. [Examples](#examples-1) ``` h1. public/_robots.txt (recommended) User-agent: * Allow: / Disallow: /admin User-agent: GPTBot Disallow: / ``` ``` export default defineNuxtConfig({ robots: { disallow: ['/admin', '/private'], groups: [ { userAgent: ['GPTBot', 'ChatGPT-User'], disallow: ['/'] } ] } }) ``` ``` export default defineNuxtConfig({ robots: { // auto-prefixes rules with locale // /admin -> /en/admin, /fr/admin disallow: ['/admin'] } }) ``` ``` --- h1. content/internal/secret.md robots: noindex, nofollow --- ``` See the [**Robots documentation**](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/robots/getting-started/introduction) for all options. h2. [OG Image](#og-image) [OG Image **v5.1.13** 2.9M 495 Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/og-image/getting-started/introduction) Generates dynamic Open Graph images for social sharing. ``` npx nuxi module add nuxt-og-image ``` **Default behavior:** - Opt-in, does nothing until configured - Preview in Nuxt DevTools OG Image tab - Supports Satori (edge) or browser rendering h3. [Examples](#examples-2) ``` export default defineNuxtConfig({ ogImage: { defaults: { component: 'OgImageTemplate', // pass props to your template title: 'My Site' } } }) ``` ``` export default defineNuxtConfig({ ogImage: { fonts: ['Inter:400', 'Inter:700'], // prerender all images at build runtimeBrowser: false } }) ``` ``` --- h1. content/blog/my-post.md title: My Blog Post description: A great article ogImage: component: OgImageBlog props: author: John Doe --- ``` See the [**OG Image documentation**](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/og-image/getting-started/introduction) for all options. h2. [Schema.org](#schemaorg) [Schema.org **v5.0.10** 3.3M 178 The quickest and easiest way to build Schema.org graphs.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/schema-org/getting-started/introduction) Adds structured data (JSON-LD) for rich search results. ``` npx nuxi module add nuxt-schema-org ``` **Default behavior:** - Adds `WebSite` and `WebPage` schema automatically - Integrates with Site Config for organization data - Skips schema on non-indexable pages h3. [Examples](#examples-3) ``` export default defineNuxtConfig({ schemaOrg: { identity: { type: 'Organization', name: 'My Company', logo: '/logo.png', sameAs: ['https://twitter.com/mycompany'] } } }) ``` ``` export default defineNuxtConfig({ schemaOrg: { identity: { type: 'Person', name: 'John Doe', image: '/avatar.jpg', url: 'https://johndoe.com' } } }) ``` ``` --- h1. content/blog/my-post.md title: My Blog Post description: A great article author: John Doe datePublished: 2024-01-15 dateModified: 2024-02-01 --- ``` See the [**Schema.org documentation**](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/schema-org/getting-started/introduction) for all options. h2. [Link Checker](#link-checker) [Link Checker **v4.3.9** 2.3M 95 Find and magically fix links that may be negatively effecting your SEO.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/link-checker/getting-started/introduction) Finds broken links before they hurt your SEO. ``` npx nuxi module add nuxt-link-checker ``` **Default behavior:** - Checks links during build - Reports in DevTools Link Checker tab - Warns but doesn't fail build h3. [Examples](#examples-4) ``` export default defineNuxtConfig({ linkChecker: { failOnError: true, // generate reports report: { html: true, markdown: true } } }) ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'external-if-timeout', 'missing-hash' ], // exclude paths from checking excludeLinks: ['/api/**'] } }) ``` See the [**Link Checker documentation**](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/link-checker/getting-started/introduction) for all options. h2. [SEO Utils](#seo-utils) [SEO Utils **v7.0.19** 1.5M 119 SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/seo-utils/getting-started/introduction) Extra SEO utilities: breadcrumbs, default meta, file metadata. ``` npx nuxi module add nuxt-seo-utils ``` **Default behavior:** - Auto-detects `favicon.ico`, `apple-touch-icon.png` in public folder - Falls back to `site.name` for missing page titles - Provides `useBreadcrumbs()` composable h3. [Examples](#examples-5) ``` export default defineNuxtConfig({ routeRules: { '/blog/**': { seoMeta: { ogType: 'article' } }, '/products/**': { seoMeta: { ogType: 'product' } } } }) ``` ``` export default defineNuxtConfig({ seoUtils: { automaticBreadcrumbs: true } // adds Schema.org BreadcrumbList automatically }) ``` See the [**SEO Utils documentation**](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/seo-utils/getting-started/introduction) for all options. h2. [Shared Configuration](#shared-configuration) [Site Config **v3.2.14** 8.9M 75 Powerful build and runtime shared site configuration for Nuxt modules.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/site-config/getting-started/introduction) Central config shared across all SEO modules. Set once, used everywhere. **Default behavior:** - Auto-installed with any SEO module - Detects production vs development environment - Reads from `NUXT_SITE_URL` env var h3. [Examples](#examples-6) ``` export default defineNuxtConfig({ site: { url: 'https://example.com', name: 'My Site', description: 'Welcome to my site', defaultLocale: 'en' } }) ``` ``` export default defineNuxtConfig({ site: { multiTenancy: [ { hosts: ['example.com', 'www.example.com'], config: { name: 'Example', url: 'https://example.com' } }, { hosts: ['foo.com', 'www.foo.com'], config: { name: 'Foo', url: 'https://foo.com' } } ] } }) ``` ``` export default defineNuxtConfig({ site: { // auto-syncs with i18n locale // url and name update per locale } }) ``` See the [**Site Config documentation**](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/site-config/getting-started/introduction) for all options. [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/2.guides/0.using-the-modules.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/nuxt-seo/guides/using-the-modules.md) **Did this page help you? ** [**Community Videos** Learn from the Nuxt community in using Nuxt SEO.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/nuxt-seo/getting-started/community-videos) [**Updating Nuxt Modules** Keep your Nuxt SEO modules up-to-date for bug fixes and new features.](https://nuxtseo.com/docs/nuxt-seo/guides/using-the-modules/docs/nuxt-seo/guides/updating-modules) **On this page** - [Sitemap](#sitemap) - [Robots](#robots) - [OG Image](#og-image) - [Schema.org](#schemaorg) - [Link Checker](#link-checker) - [SEO Utils](#seo-utils) - [Shared Configuration](#shared-configuration) --- ### Debugging Modules · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/guides/debugging-modules Description: Disable modules and create minimal reproductions for Nuxt SEO issues. **Core Concepts** h1. **Debugging Modules** [Copy for LLMs h2. [Disabling Modules](#disabling-modules) Since Nuxt SEO installs and enables modules for you, you may run into a situation where you want to disable a module. The modules have these config keys: - `nuxt-og-image` - `ogImage` - `@nuxtjs/sitemap` - `sitemap` - `@nuxtjs/robots` - `robots` - `nuxt-seo-utils` - `seo` - `nuxt-schema-org` - `schemaOrg` - `nuxt-link-checker` - `linkChecker` You can disable any of these modules by setting the module's `enabled` value to `false` in your `nuxt.config.ts` file. nuxt.config.ts ``` export default defineNuxtConfig({ ogImage: { enabled: false }, sitemap: { enabled: false }, robots: { enabled: false }, seo: { // seo utils enabled: false }, schemaOrg: { enabled: false }, linkChecker: { enabled: false } }) ``` h2. [Creating a Minimal Reproduction](#creating-a-minimal-reproduction) When submitting a GitHub issue, a minimal reproduction helps maintainers debug your problem quickly. Use one of these StackBlitz playgrounds as a starting point: h3. [Nuxt SEO](#nuxt-seo) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-zycxux?file=nuxt.config.ts) - [**i18n**](https://stackblitz.com/edit/nuxt-starter-pnej8lvb?file=nuxt.config.ts) h3. [Robots](#robots) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-zycxux?file=nuxt.config.ts) - [**i18n**](https://stackblitz.com/edit/nuxt-starter-pnej8lvb?file=nuxt.config.ts) h3. [Sitemap](#sitemap) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-dyraxc?file=server%2Fapi%2F_sitemap-urls.ts) - [**i18n**](https://stackblitz.com/edit/nuxt-starter-jwuie4?file=app.vue) - [**Manual Chunking**](https://stackblitz.com/edit/nuxt-starter-umyso3?file=nuxt.config.ts) - [**Nuxt Content**](https://stackblitz.com/edit/nuxt-starter-a5qk3s?file=nuxt.config.ts) h3. [OG Image](#og-image) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-pxs3wk?file=pages/index.vue) - [**i18n**](https://stackblitz.com/edit/nuxt-starter-uw7pqmxg?file=nuxt.config.ts) - [**Nuxt Content v2**](https://stackblitz.com/edit/github-hgunsf?file=package.json) - [**Nuxt Content v3**](https://stackblitz.com/edit/github-hgunsf-wd8esdec) h3. [Schema.org](#schemaorg) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-z9np1t?file=app.vue) h3. [Link Checker](#link-checker) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-r2wzt1?file=nuxt.config.ts) h3. [SEO Utils](#seo-utils) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-vbay3q?file=app.vue) h3. [Site Config](#site-config) - [**Basic**](https://stackblitz.com/edit/nuxt-starter-zycxux?file=nuxt.config.ts) Once you've reproduced the issue, fork the StackBlitz and include the link in your [**GitHub issue**](https://github.com/harlan-zw/nuxt-seo/issues/new). [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/2.guides/6.debugging-modules.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/guides/debugging-modules/docs/nuxt-seo/guides/debugging-modules.md) **Did this page help you? ** [**Understanding Site Config** Site Config shares your site URL, name, and metadata across all SEO modules. Set it once, use it everywhere.](https://nuxtseo.com/docs/nuxt-seo/guides/debugging-modules/docs/nuxt-seo/guides/site-config) [**v2 RC to v2 Stable** Migrate from the Nuxt SEO v2 RC to the v2 stable.](https://nuxtseo.com/docs/nuxt-seo/guides/debugging-modules/docs/nuxt-seo/migration-guide/rc-to-stable) **On this page** - [Disabling Modules](#disabling-modules) - [Creating a Minimal Reproduction](#creating-a-minimal-reproduction) --- ### v5.0.0 · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/releases/v5 Description: Release notes for Nuxt Schema.org v5. **Releases** h1. **v5.0.0** Last updated **Mar 13, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: typechecking](https://github.com/harlan-zw/nuxt-schema-org/commit/c5f3fc52bb0899174a2cdd441890ccafe4072262). [Copy for LLMs h2. [Breaking Features](#breaking-features) h3. [Nuxt v3.16](#nuxt-v316) To avoid hoisting issues with the new Unhead v2, the Nuxt Schema.org v5 requires Nuxt v3.16. Please upgrade your Nuxt to continue using OG Image. ``` nuxi upgrade --force ``` [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/4.releases/3.v5.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/releases/v5/docs/schema-org/releases/v5.md) **Did this page help you? ** [**Nuxt Hooks** Learn how to use Nuxt hooks to modify your Schema.org output.](https://nuxtseo.com/docs/schema-org/releases/v5/docs/schema-org/api/nuxt-hooks) [**v4.0.0** Release notes for v4.0.0 of Nuxt Schema.org.](https://nuxtseo.com/docs/schema-org/releases/v5/docs/schema-org/releases/v4) --- ### Nuxt Content · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/guides/content Description: How to use the Nuxt Schema.org module with Nuxt Content. **Core Concepts** h1. **Nuxt Content** Last updated **Jan 20, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: lint](https://github.com/harlan-zw/nuxt-schema-org/commit/78258982de5c61a5caa18ebe334c701a2eb9d401). [Copy for LLMs h2. [Introduction](#introduction) Nuxt Schema.org integrates with Nuxt Content out of the box, supporting a `schemaOrg` frontmatter key that can be used to configure your pages schema.org data. You can see a demo of this integration in the [**Nuxt OG Image & Nuxt Content Playground**](https://stackblitz.com/edit/github-hgunsf?file=package.json). h2. [Setup Nuxt Content v3](#setup-nuxt-content-v3) In Nuxt Content v3 we need to use the `asSchemaOrgCollection()` function to augment any collections to be able to use the `schemaOrg` frontmatter key. content.config.ts ``` import { defineCollection, defineContentConfig } from '@nuxt/content' import { asSchemaOrgCollection } from 'nuxt-schema-org/content' export default defineContentConfig({ collections: { content: defineCollection( asSchemaOrgCollection({ type: 'page', source: '**/*.md', }), ), }, }) ``` To ensure the tags actually gets rendered you need to ensure you're using the `useHead()` composable with the `head` key. [...slug].vue ``` <script setup lang="ts"> import { queryCollection, useRoute } from '#imports' const route = useRoute() const { data: page } = await useAsyncData(\`page-${route.path}\`, () => { return queryCollection('content').path(route.path).first() }) // Ensure the schema.org is rendered useHead(page.value.head || {}) useSeoMeta(page.value.seo || {}) </script> ``` h2. [Setup Nuxt Content v2](#setup-nuxt-content-v2) No extra set up is required, simply add the `schemaOrg` key to your frontmatter. h2. [Usage](#usage) It's recommended to provide an array of Schema.org nodes, otherwise you will be extending the [`WebPage`](https://unhead.unjs.io/schema-org/schema/webpage) node with the provided data. ``` --- schemaOrg: # Define new nodes - "@type": "BlogPosting" headline: "How to Use Our Product" author: type: "Person" name: "Jane Smith" datePublished: "2023-10-01" --- ``` ``` --- schemaOrg: # Augment WebPage to an AboutPage "@type": "AboutPage" --- ``` h2. [Markdown Recipes](#markdown-recipes) h3. [Blog Post](#blog-post) ``` --- title: 'My Blog Post' schemaOrg: - type: "BlogPosting" headline: "How to Use Our Product" author: type: "Person" name: "Jane Smith" datePublished: "2023-10-01" --- ``` h3. [FAQ Page](#faq-page) ``` --- title: 'FAQ' schemaOrg: type: FaqPage mainEntity: - "@type": "Question" name: "What is your return policy?" acceptedAnswer: "@type": "Answer" text: "You can return any item within 30 days of purchase." - "@type": "Question" name: "Do you offer technical support?" acceptedAnswer: "@type": "Answer" text: "Yes, we offer 24/7 technical support." --- ``` h3. [About Page](#about-page) ``` --- title: 'About' schemaOrg: "@type": "AboutPage" --- ``` h3. [Contact Page](#contact-page) ``` --- title: 'Contact' schemaOrg: "@type": "ContactPage" mainEntity: "@type": "ContactPoint" contactType: "Customer Service" telephone: "+1-800-555-5555" email: "support@example.com" --- ``` h3. [Product Page](#product-page) ``` --- title: 'Product' schemaOrg: - "@type": "Product" name: "Product XYZ" description: "A high-quality product that meets your needs." offers: "@type": "Offer" price: "29.99" priceCurrency: "USD" --- ``` [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/2.guides/1.content.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/guides/content/docs/schema-org/guides/content.md) **Did this page help you? ** [**How It Works** Learn more about how Nuxt Schema.org works.](https://nuxtseo.com/docs/schema-org/guides/content/docs/schema-org/guides/how-it-works) [**Default Schema.org** The default Schema.org setup for Nuxt Schema.org.](https://nuxtseo.com/docs/schema-org/guides/content/docs/schema-org/guides/default-schema-org) **On this page** - [Introduction](#introduction) - [Setup Nuxt Content v3](#setup-nuxt-content-v3) - [Setup Nuxt Content v2](#setup-nuxt-content-v2) - [Usage](#usage) - [Markdown Recipes](#markdown-recipes) --- ### Install Nuxt Schema.org · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/getting-started/installation Description: Get started with Nuxt Schema.org by installing the dependency to your project. **Getting Started** h1. **Install Nuxt Schema.org** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#98) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/98). [Copy for LLMs h2. [Setup Module](#setup-module) `npx nuxt module add schema-org` `npm i nuxt-schema-org` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-schema-org', ], }) ``` `yarn add nuxt-schema-org` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-schema-org', ], }) ``` `pnpm i nuxt-schema-org` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-schema-org', ], }) ``` `bun i nuxt-schema-org` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-schema-org', ], }) ``` This module requires SSR for automatic schema generation. For SPA/CSR apps, manually add schema: ``` useHead({ script: [{ type: 'application/ld+json', innerHTML: JSON.stringify(yourSchema) }] }) ``` h2. [Verifying Installation](#verifying-installation) After you've set up the module, you should be able to visit your home page and inspect the Schema.org. You'll find the `<script type="application/ld+json">` tag with the default Schema.org nodes near the `</body>` tag. This is generated by the [**defaults Schema.org**](https://nuxtseo.com/docs/schema-org/getting-started/installation/docs/schema-org/guides/default-schema-org) and you can modify the output if it's not what you need. You can debug this further in Nuxt DevTools under the Schema.org tab. h2. [Next Steps](#next-steps) It's recommended to use this module with Nuxt Robots so that the non-indexable paths are automatically excluded from adding Schema.org. [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/schema-org/getting-started/installation/docs/robots/getting-started/introduction) Other suggestions: - [**Setup Your Identity**](https://nuxtseo.com/docs/schema-org/getting-started/installation/docs/schema-org/guides/setup-identity) [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/0.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/getting-started/installation/docs/schema-org/getting-started/installation.md) **Did this page help you? ** [**Introduction** The quickest and easiest way to build Schema.org graphs for Nuxt.](https://nuxtseo.com/docs/schema-org/getting-started/installation/docs/schema-org/getting-started/introduction) [**Troubleshooting** Create minimal reproductions for Nuxt Schema.org or just experiment with the module.](https://nuxtseo.com/docs/schema-org/getting-started/installation/docs/schema-org/getting-started/troubleshooting) **On this page** - [Setup Module](#setup-module) - [Verifying Installation](#verifying-installation) - [Next Steps](#next-steps) --- ### useSchemaOrg() · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/api/use-schema-org Description: Define Schema.org structured data for your pages using the useSchemaOrg composable. **Nuxt API** h1. **useSchemaOrg()** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix wrong descriptions and add schema validator links (#97) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/97). [Copy for LLMs h2. [Introduction](#introduction) **Type:** `function useSchemaOrg(nodes: SchemaOrgNode[]): ActiveHeadEntry` Insert Schema.org structured data into your page. In development mode your Schema.org will be reactive, but once you deploy you'll notice that it's static. This is because the composable only works in a server-side context by default outside of development for performance reasons. You'll need to use enable the [**reactive**](https://nuxtseo.com/docs/schema-org/api/use-schema-org/docs/schema-org/api/config#reactive) module configuration if you'd like to use this client-side. h2. [Usage](#usage) ``` import { useSchemaOrg } from '#imports' useSchemaOrg([ defineWebPage({ name: 'Hello World' }) ]) ``` [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/4.api/0.use-schema-org.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/api/use-schema-org/docs/schema-org/api/use-schema-org.md) **Did this page help you? ** [**Nuxt Config** Configure the Nuxt Schema.org module.](https://nuxtseo.com/docs/schema-org/api/use-schema-org/docs/schema-org/api/config) [**Nuxt Hooks** Learn how to use Nuxt hooks to modify your Schema.org output.](https://nuxtseo.com/docs/schema-org/api/use-schema-org/docs/schema-org/api/nuxt-hooks) **On this page** - [Introduction](#introduction) - [Usage](#usage) --- ### Troubleshooting Nuxt Schema.org · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/getting-started/troubleshooting Description: Create minimal reproductions for Nuxt Schema.org or just experiment with the module. **Getting Started** h1. **Troubleshooting Nuxt Schema.org** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix wrong descriptions and add schema validator links (#97) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/97). [Copy for LLMs h2. [Debugging](#debugging) h3. [Nuxt DevTools](#nuxt-devtools) The best tool for debugging is the Nuxt DevTools integration with Nuxt Schema.org. This will give you your Schema.org graph and handy links to test it. This is enabled by default in development, simply navigate to the Schema.org tab. h3. [Debug Config](#debug-config) You can enable the [**debug**](https://nuxtseo.com/docs/schema-org/getting-started/troubleshooting/docs/schema-org/api/config#debug) option which will give you more granular output. This is enabled by default in development mode. This will allow you to access `/__schema-org__/debug.json` which contains the raw config used to generate the Schema.org nodes. h3. [External Debugging Tools](#external-debugging-tools) You can test your Schema.org using the following tools: - [**Google Rich Results Test**](https://search.google.com/test/rich-results) - [**Schema.org Validator**](https://validator.schema.org/) h2. [Debugging Tools](#debugging-tools) - [**Schema.org Validator**](https://nuxtseo.com/docs/schema-org/getting-started/troubleshooting/tools/schema-validator) - Validate JSON-LD and Microdata markup, find errors, and ensure proper implementation for rich snippets h2. [Submitting an Issue](#submitting-an-issue) When submitting an issue, it's important to provide as much information as possible. The easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds: - [**Basic**](https://stackblitz.com/edit/nuxt-starter-z9np1t?file=app.vue) [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/0.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/getting-started/troubleshooting/docs/schema-org/getting-started/troubleshooting.md) **Did this page help you? ** [**Installation** Get started with Nuxt Schema.org by installing the dependency to your project.](https://nuxtseo.com/docs/schema-org/getting-started/troubleshooting/docs/schema-org/getting-started/installation) [**How It Works** Learn more about how Nuxt Schema.org works.](https://nuxtseo.com/docs/schema-org/getting-started/troubleshooting/docs/schema-org/guides/how-it-works) **On this page** - [Debugging](#debugging) - [Debugging Tools](#debugging-tools) - [Submitting an Issue](#submitting-an-issue) --- ### Default Schema.org · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/guides/default-schema-org Description: The default Schema.org setup for Nuxt Schema.org. **Core Concepts** h1. **Default Schema.org** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix wrong descriptions and add schema validator links (#97) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/97). [Copy for LLMs h2. [Introduction](#introduction) Nuxt Schema.org uses Schema.org graphs to provide structured data to search engines. These graphs have root nodes that describe the site and the page. ``` { "@context": "https://schema.org", "@graph": [] } ``` The module automatically sets up two default nodes for you: [**WebSite**](https://unhead.unjs.io/schema-org/schema/website) and [**WebPage**](https://unhead.unjs.io/schema-org/schema/webpage). In this example we have configured our site using [**Site Config**](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/docs/site-config/getting-started/how-it-works): ``` export default defineNuxtConfig({ siteConfig: { title: 'Nuxt SEO', description: 'Nuxt SEO is a collection of hand-crafted Nuxt Modules to help you rank higher in search engines.', url: 'https://nuxtseo.com' } }) ``` [Site Config **v3.2.14** 8.9M 75 Powerful build and runtime shared site configuration for Nuxt modules.](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/docs/site-config/getting-started/introduction) h2. [Configuring Your Defaults](#configuring-your-defaults) If you'd like to change any of the data on the `WebPage` or `WebSite` nodes, you can do so by using `useSchemaOrg` in your app. This will merge in your configuration with the default configuration. app.vue ``` <script lang="ts" setup> useSchemaOrg([ defineWebPage({ name: 'My Page' }), defineWebSite({ name: 'My Site' }) ]) </script> ``` h2. [Opt-out](#opt-out) If you don't want to use the default setup, you can opt-out by setting `defaults: false` in your `nuxt.config`: nuxt.config.ts ``` export default defineNuxtConfig({ schemaOrg: { defaults: false } }) ``` [](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/tools/schema-validator)**Check default nodes** - Validate your WebPage and WebSite schema with our [**Schema.org Validator**](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/tools/schema-validator). h2. [Configuring Identity](#configuring-identity) Please see the [**Setup Identity**](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/docs/schema-org/guides/setup-identity) guide for more information on configuring your identity. [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/2.guides/1.default-schema-org.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/docs/schema-org/guides/default-schema-org.md) **Did this page help you? ** [**Nuxt Content** How to use the Nuxt Schema.org module with Nuxt Content.](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/docs/schema-org/guides/content) [**Setup Identity** Improve your Schema.org by providing the identity of your site.](https://nuxtseo.com/docs/schema-org/guides/default-schema-org/docs/schema-org/guides/setup-identity) **On this page** - [Introduction](#introduction) - [Configuring Your Defaults](#configuring-your-defaults) - [Opt-out](#opt-out) - [Configuring Identity](#configuring-identity) --- ### How It Works · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/guides/how-it-works Description: Learn more about how Nuxt Schema.org works. **Core Concepts** h1. **How It Works** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#98) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/98). [Copy for LLMs h2. [Introduction](#introduction) Nuxt Schema.org will generate structured data for your site based on the input you provide and the content of your pages. This is used to help search engines understand your content better and provide more relevant search results. Nuxt Schema.org is built on [**Unhead Schema.org**](https://unhead.unjs.io/docs/typescript/schema-org/guides/get-started/overview), which powers the underlying schema generation engine. h2. [LD+JSON tag](#ldjson-tag) The schema is injected within a `<script type="application/ld+json">` tag at the end of your document's `<body>`. h2. [Production Static data](#production-static-data) When running in development this schema will be reactive to page changes, however when in production it will be static. This is because robots will only ever parse the initial SSR response and not any client-side changes. To avoid the extra bundle size required to generate schema.org on the frontend, it is only generated statically on the SSR response. If you really need this behavior you can enable the `reactive` module config. h2. [Data Inferencing](#data-inferencing) To avoid much of the boilerplate associated with schema.org, Nuxt Schema.org will infer data from your pages. For example, it will infer data from your head tags such as `title`, `description`, `keywords`, `author`, `date`, `image`, etc. [](https://nuxtseo.com/docs/schema-org/guides/how-it-works/tools/schema-validator)**See your output** - Use our [**Schema.org Validator**](https://nuxtseo.com/docs/schema-org/guides/how-it-works/tools/schema-validator) to inspect the generated structured data on your pages. h2. [Key Points](#key-points) - Schema is generated server-side and injected as JSON-LD in `<body>` - In production, schema is static (not reactive) to reduce bundle size - Data is inferred from your page's head tags (title, description, image, etc.) - Use Nuxt DevTools Schema.org tab to debug during development [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/2.guides/0.how-it-works.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/guides/how-it-works/docs/schema-org/guides/how-it-works.md) **Did this page help you? ** [**Troubleshooting** Create minimal reproductions for Nuxt Schema.org or just experiment with the module.](https://nuxtseo.com/docs/schema-org/guides/how-it-works/docs/schema-org/getting-started/troubleshooting) [**Nuxt Content** How to use the Nuxt Schema.org module with Nuxt Content.](https://nuxtseo.com/docs/schema-org/guides/how-it-works/docs/schema-org/guides/content) **On this page** - [Introduction](#introduction) - [LD+JSON tag](#ldjson-tag) - [Production Static data](#production-static-data) - [Data Inferencing](#data-inferencing) - [Key Points](#key-points) --- ### Setup Identity · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/guides/setup-identity Description: Improve your Schema.org by providing the identity of your site. **Core Concepts** h1. **Setup Identity** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#98) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/98). [Copy for LLMs Setting up your site identity connects your Schema.org to Google's Knowledge Graph, enabling your logo and social links to appear in search results. Providing an identity links [**Default Schema.org**](https://nuxtseo.com/docs/schema-org/guides/setup-identity/docs/schema-org/guides/default-schema-org) to your site author. `Organization` or `LocalBusiness` nodes can trigger [**Rich Results**](https://developers.google.com/search/docs/appearance/structured-data/organization). ![Schema.org Identity](https://nuxtseo.com/docs/schema-org/guides/setup-identity/schema-org/identity.png) h2. [Which identity type should I use?](#which-identity-type-should-i-use) Choose based on your site type: - [`Person`](#person) - [`Organization`](#organization) - [`OnlineStore`](#onlinestore) - [`LocalBusiness`](#localbusiness) h3. [When should I use Person?](#when-should-i-use-person) Use `Person` when your website is about a person, personal brand, or personal blog. Example: [**harlanzw.com**](https://harlanzw.com), [**antfu.me**](https://antfu.me/) ``` import { definePerson } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: definePerson({ name: 'Harlan Wilton', // Profile Information, if applicable image: '/profile-photo.jpg', description: 'Software engineer and open-source contributor', url: 'harlanzw.com', sameAs: [ 'https://twitter.com/harlan_zw', 'https://github.com/harlan-zw' ], }) } }) ``` ``` import { definePerson } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: definePerson({ // Basic Information, if applicable name: 'Dr. Sarah Chen', givenName: 'Sarah', familyName: 'Chen', additionalName: 'J.', // middle name or other additional names alternateName: 'Sarah J. Chen', // Profile Information, if applicable image: '/profile-photo.jpg', description: 'AI researcher and technical author specializing in machine learning and neural networks', jobTitle: 'Principal AI Researcher', // Contact & Social, if applicable email: 'sarah.chen@example.com', url: 'https://sarahchen.dev', sameAs: [ 'https://twitter.com/sarahchen', 'https://github.com/sarahchen', 'https://linkedin.com/in/sarahchen', 'https://scholar.google.com/citations?user=sarahchen' ], // Professional Details, if applicable worksFor: { '@type': 'Organization', 'name': 'Tech Research Labs', 'url': 'https://techresearchlabs.com' }, }) } }) ``` h3. [When should I use Organization?](#when-should-i-use-organization) Use `Organization` for companies, brands, non-profits, or communities. Also use it as a fallback when other options don't fit. ``` import { defineOrganization } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: defineOrganization({ // Basic Information name: 'TechCorp Solutions', logo: '/logo.png', }) } }) ``` ``` import { defineOrganization } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: defineOrganization({ // Basic Information name: 'TechCorp Solutions', alternateName: 'TechCorp', description: 'Leading provider of enterprise software solutions and cloud services', url: 'https://techcorp.com', logo: '/logo.png', // Address Information, if applicable address: { '@type': 'PostalAddress', 'streetAddress': '100 Innovation Drive, Suite 400', 'addressLocality': 'Silicon Valley', 'addressRegion': 'CA', 'postalCode': '94025', 'addressCountry': 'US' }, // Contact Information, if applicable email: 'info@techcorp.com', telephone: '+1-650-555-0123', contactPoint: { '@type': 'ContactPoint', 'telephone': '+1-650-555-0124', 'email': 'support@techcorp.com' }, // Business Details, if applicable foundingDate: '2010-01-15', numberOfEmployees: { '@type': 'QuantitativeValue', 'minValue': 500, 'maxValue': 999 }, // Social and External Links, if applicable sameAs: [ 'https://twitter.com/techcorp', 'https://www.linkedin.com/company/techcorp', 'https://www.facebook.com/techcorp' ], // Business Identifiers, if applicable legalName: 'TechCorp Solutions Inc.', taxID: '12-3456789', vatID: 'GB123456789', duns: '12-345-6789', iso6523Code: '0060:123456789', naics: '541512', // Return Policy, if applicable hasMerchantReturnPolicy: { '@type': 'MerchantReturnPolicy', 'name': 'Standard Return Policy', 'inStoreReturnsOffered': true, 'returnPolicyCategory': 'https://schema.org/MerchantReturnFiniteReturnWindow', 'returnPolicyCountry': 'US', 'returnWindow': { '@type': 'BusinessDaysSpecification', 'numberOfDays': 30 } } }) } }) ``` h3. [When should I use LocalBusiness?](#when-should-i-use-localbusiness) Use `LocalBusiness` for local businesses, stores, restaurants, or services with a physical address. Some examples of `LocalBusiness`{lang="ts}: : `Restaurant`, `HealthAndBeautyBusiness`, `ProfessionalService`, `FinancialService`, `MedicalBusiness`, etc... Google recommends using the most specific type of `LocalBusiness` that fits your business, check the list of [**subtypes**](https://schema.org/LocalBusiness#subtypes) to find the most appropriate. If you need to use dynamic data, you can use the `defineLocalBusiness` function to define the identity within your app.vue. ``` import { defineLocalBusiness } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: defineLocalBusiness({ '@type': '...', // Choose from https://schema.org/LocalBusiness#subtypes // Basic Information (Required) 'name': 'The Coastal Kitchen', 'description': 'Farm-to-table restaurant specializing in sustainable seafood and seasonal ingredients', 'url': 'https://thecoastalkitchen.com', // Location (Required) 'address': { streetAddress: '742 Oceanview Boulevard, Suite 100', addressLocality: 'Santa Cruz', addressRegion: 'CA', postalCode: '95060', addressCountry: 'US' }, }), } }) ``` ``` import { defineLocalBusiness } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: defineLocalBusiness({ '@type': '...', // Choose from https://schema.org/LocalBusiness#subtypes // Basic Information (Required) 'name': 'The Coastal Kitchen', 'description': 'Farm-to-table restaurant specializing in sustainable seafood and seasonal ingredients', 'url': 'https://thecoastalkitchen.com', // Location (Required) 'address': { streetAddress: '742 Oceanview Boulevard, Suite 100', addressLocality: 'Santa Cruz', addressRegion: 'CA', postalCode: '95060', addressCountry: 'US' }, // Precise Geographic Location, if applicable 'geo': { '@type': 'GeoCoordinates', 'latitude': '36.9741', 'longitude': '-122.0308' }, // Contact Information, if applicable 'telephone': '+1-831-555-0123', 'email': 'hello@thecoastalkitchen.com', // Hours of Operation, if applicable 'openingHoursSpecification': [ { dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday'], opens: '11:30:00', closes: '22:00:00' }, { dayOfWeek: ['Friday', 'Saturday'], opens: '11:30:00', closes: '23:00:00' }, { dayOfWeek: 'Sunday', opens: '10:00:00', // Sunday Brunch closes: '21:00:00' } ], // Business Details, if applicable 'priceRange': '$$$', // $, $$, $$$, or $$$$ 'servesCuisine': [ 'Seafood', 'California', 'Farm-to-table' ], // Menu (for restaurants) 'menu': 'https://thecoastalkitchen.com/menu', // Images, if applicable 'image': [ 'https://thecoastalkitchen.com/images/storefront.jpg', 'https://thecoastalkitchen.com/images/interior.jpg', 'https://thecoastalkitchen.com/images/food.jpg' ], 'logo': '/logo.png', // Payment Options, if applicable 'paymentAccepted': [ 'Cash', 'Credit Card', 'Cryptocurrency' ], 'currenciesAccepted': 'USD', // Additional Business Details, if applicable 'isAccessibleForDisabled': true, 'amenityFeature': [ { '@type': 'LocationFeatureSpecification', 'name': 'Parking', 'value': true }, { '@type': 'LocationFeatureSpecification', 'name': 'Wheelchair Accessible', 'value': true }, { '@type': 'LocationFeatureSpecification', 'name': 'Outdoor Seating', 'value': true } ], // Social Links, if applicable 'sameAs': [ 'https://www.facebook.com/coastalkitchen', 'https://instagram.com/thecoastalkitchen', 'https://twitter.com/coastalkitchen' ] }), } }) ``` ``` <script lang="ts" setup> // app.vue import { defineLocalBusiness, useSchemaOrg } from '#imports' const reviews = useFetch('/api/reviews') useSchemaOrg([ defineLocalBusiness({ // ... reviews: reviews.data.value }) ]) </script> ``` h3. [When should I use OnlineStore?](#when-should-i-use-onlinestore) Use `OnlineStore` for ecommerce sites selling products online. ``` import { defineOrganization } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: defineOrganization({ '@type': ['Organization', 'Store', 'OnlineStore'], // Basic Information 'name': 'ModernHome', 'logo': '/logo.png', }), } }) ``` ``` import { defineOrganization } from 'nuxt-schema-org/schema' export default defineNuxtConfig({ schemaOrg: { identity: defineOrganization({ '@type': ['Organization', 'Store', 'OnlineStore'], // Basic Information 'name': 'ModernHome', 'alternateName': 'Modern Home Decor', 'description': 'Contemporary furniture and home decor with worldwide shipping. Specializing in minimalist Scandinavian design.', 'url': 'https://modernhome.com', 'logo': '/logo.png', // Contact Information, if applicable 'email': 'support@modernhome.com', 'telephone': '+1-888-555-0123', 'contactPoint': [ { '@type': 'ContactPoint', 'contactType': 'customer service', 'telephone': '+1-888-555-0123', 'email': 'support@modernhome.com', 'availableLanguage': ['English', 'Spanish'], 'hoursAvailable': { '@type': 'OpeningHoursSpecification', 'dayOfWeek': ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], 'opens': '09:00:00', 'closes': '18:00:00' } }, { '@type': 'ContactPoint', 'contactType': 'sales', 'telephone': '+1-888-555-0124', 'email': 'sales@modernhome.com' } ], // Business Details, if applicable 'foundingDate': '2015-01-01', 'numberOfEmployees': { '@type': 'QuantitativeValue', 'value': 85 }, // Legal Information, if applicable 'legalName': 'ModernHome Inc.', 'taxID': '47-1234567', 'vatID': 'EU123456789', // Business Address (headquarters/returns), if applicable 'address': { '@type': 'PostalAddress', 'streetAddress': '100 Commerce Way, Suite 300', 'addressLocality': 'Portland', 'addressRegion': 'OR', 'postalCode': '97201', 'addressCountry': 'US' }, // Return Policy, if applicable 'hasMerchantReturnPolicy': { '@type': 'MerchantReturnPolicy', 'name': 'Standard Return Policy', 'inStoreReturnsOffered': false, 'merchantReturnDays': '30', 'returnPolicyCategory': 'https://schema.org/MerchantReturnFiniteReturnWindow', 'returnMethod': ['ReturnByMail'], 'returnFees': 'https://schema.org/FreeReturn', 'returnPolicyCountry': { '@type': 'Country', 'name': ['US', 'CA', 'GB', 'AU', 'NZ'] } }, // Shipping Policy, if applicable 'shippingDetails': { '@type': 'OfferShippingDetails', 'shippingRate': { '@type': 'MonetaryAmount', 'value': '0', 'currency': 'USD' }, 'shippingDestination': { '@type': 'DefinedRegion', 'addressCountry': ['US', 'CA', 'GB', 'AU', 'NZ'] }, 'deliveryTime': { '@type': 'ShippingDeliveryTime', 'handlingTime': { '@type': 'QuantitativeValue', 'minValue': 1, 'maxValue': 2, 'unitCode': 'DAY' }, 'transitTime': { '@type': 'QuantitativeValue', 'minValue': 3, 'maxValue': 7, 'unitCode': 'DAY' } } }, // Payment Methods, if applicable 'paymentAccepted': [ 'Credit Card', 'PayPal', 'Apple Pay', 'Google Pay', 'Shop Pay' ], 'currenciesAccepted': ['USD', 'EUR', 'GBP', 'CAD', 'AUD'], // Social Media & External Links, if applicable 'sameAs': [ 'https://facebook.com/modernhome', 'https://instagram.com/modernhome', 'https://pinterest.com/modernhome', 'https://twitter.com/modernhome' ], // Trust Indicators, if applicable 'hasCredential': [ { '@type': 'EducationalOccupationalCredential', 'credentialCategory': 'BBB Rating A+', 'url': 'https://www.bbb.org/modernhome' }, { '@type': 'EducationalOccupationalCredential', 'credentialCategory': 'Certified B Corporation', 'url': 'https://www.bcorporation.net/modernhome' } ], // Aggregate Ratings, if applicable 'aggregateRating': { '@type': 'AggregateRating', 'ratingValue': '4.8', 'reviewCount': '12459', 'bestRating': '5', 'worstRating': '1' }, // Customer Service Features, if applicable 'hasOfferCatalog': { '@type': 'OfferCatalog', 'name': 'ModernHome Product Catalog', 'url': 'https://modernhome.com/products' }, // Additional Business Properties, if applicable 'slogan': 'Design for Modern Living', 'keywords': [ 'modern furniture', 'scandinavian design', 'home decor', 'minimalist furniture', 'contemporary home' ], // Business Hours (Customer Service), if applicable 'openingHoursSpecification': [ { '@type': 'OpeningHoursSpecification', 'dayOfWeek': ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], 'opens': '09:00:00', 'closes': '18:00:00' } ] }), } }) ``` [](https://nuxtseo.com/docs/schema-org/guides/setup-identity/tools/schema-validator)**Verify your identity** - Check your Organization or Person schema is correct with our [**Schema.org Validator**](https://nuxtseo.com/docs/schema-org/guides/setup-identity/tools/schema-validator). h2. [Recipes](#recipes) It's recommended to provide as much information about your identity as possible, here are some recipes. h3. [Social Media Profiles](#social-media-profiles) [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/2.guides/1.setup-identity.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/guides/setup-identity/docs/schema-org/guides/setup-identity.md) **Did this page help you? ** [**Default Schema.org** The default Schema.org setup for Nuxt Schema.org.](https://nuxtseo.com/docs/schema-org/guides/setup-identity/docs/schema-org/guides/default-schema-org) [**Nuxt I18n** How to use the Nuxt Schema.org module with Nuxt I18n.](https://nuxtseo.com/docs/schema-org/guides/setup-identity/docs/schema-org/guides/i18n) **On this page** - [Which identity type should I use?](#which-identity-type-should-i-use) - [Recipes](#recipes) --- ### Supported Nodes · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/guides/nodes Description: The nodes available for Nuxt Schema.org. **Core Concepts** h1. **Supported Nodes** Last updated **May 4, 2025** by [Buzut](https://github.com/Buzut) in [docs: unhead link in nodes.md (#85) Co-authored-by: Harlan Wilton <harlan@harlanzw.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/85). [Copy for LLMs The module exposes the officially supported nodes from [**Unhead Schema.org**](https://unhead.unjs.io/docs/nuxt/schema-org/guides/core-concepts/nodes). Official nodes are ones that have a direct impact on Google Rich Results. h2. [Custom Nodes](#custom-nodes) If you need to add a node that isn't implemented, then you can provide it yourself. Custom nodes are just plain objects that follow the [**Schema.org specification**](https://schema.org/docs/full.html). If you'd like to add types, you can use [**schema-dts**](https://github.com/google/schema-dts). ``` <script lang="ts" setup> useSchemaOrg([ { '@type': 'DefinedTerm', 'name': 'Nuxt Schema.org', 'description': 'Nuxt Schema.org is a Nuxt module for adding Schema.org to your Nuxt app.', 'inDefinedTermSet': { '@type': 'DefinedTermSet', 'name': 'Nuxt Modules', }, } ]) </script> ``` ``` <script lang="ts" setup> import type { DefinedTerm } from 'schema-dts' const NuxtSchemaOrgDefinedTerm: DefinedTerm = { '@type': 'DefinedTerm', 'name': 'Nuxt Schema.org', 'description': 'Nuxt Schema.org is a Nuxt module for adding Schema.org to your Nuxt app.', 'inDefinedTermSet': { '@type': 'DefinedTermSet', 'name': 'Nuxt Modules', }, } useSchemaOrg([NuxtSchemaOrgDefinedTerm]) </script> ``` h2. [Official nodes](#official-nodes) The scope for officially supported nodes is those that provide Rich Results within Google. - [Nodes](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/nodes) - [Article](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/article) - [Book](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/book) - [Breadcrumb](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/breadcrumb) - [Comment](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/comment) - [Course](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/course) - [Event](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/event) - [Food Establishment](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/food-establishment) - [How To](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/how-to) - [Image](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/image) - [Item List](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/item-list) - [Job Posting](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/job-posting) - [Local Business](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/local-business) - [Movie](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/movie) - [Organization](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/organization) - [Person](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/person) - [Product](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/product) - [Question](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/question) - [Recipe](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/recipe) - [Software App](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/software-app) - [Video](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/video) - [Webpage](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/webpage) - [Website](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/website) [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/2.guides/2.nodes.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/guides/nodes/docs/schema-org/guides/nodes.md) **Did this page help you? ** [**Nuxt I18n** How to use the Nuxt Schema.org module with Nuxt I18n.](https://nuxtseo.com/docs/schema-org/guides/nodes/docs/schema-org/guides/i18n) [**Full Documentation** Links to the full unhead-schema-org documentation for advanced usage.](https://nuxtseo.com/docs/schema-org/guides/nodes/docs/schema-org/guides/full-documentation) **On this page** - [Custom Nodes](#custom-nodes) - [Official nodes](#official-nodes) --- ### Nuxt I18n · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/guides/i18n Description: How to use the Nuxt Schema.org module with Nuxt I18n. **Core Concepts** h1. **Nuxt I18n** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix wrong descriptions and add schema validator links (#97) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/97). [Copy for LLMs h2. [I18n Defaults](#i18n-defaults) When using the [**defaults**](https://nuxtseo.com/docs/schema-org/guides/i18n/docs/schema-org/api/config#defaults) configuration, the module will automatically integrate with Nuxt I18n. It will read your configuration, adding unique `WebSite` entities for each locale and connecting them with `translationOfWork` and `workTranslation` properties. As an example, the following would be generated when visitng the default `en` route when your site supports both `ja` and `zh`. ``` { "@context": "https://schema.org", "@graph": [ { "@id": "https://nuxtseo.com/en#website", "@type": "WebSite", "description": "The quickest and easiest way to build Schema.org graphs for Nuxt.", "inLanguage": "en-US", "name": "nuxt-schema-org", "publisher": { "@id": "https://nuxtseo.com/#identity" }, "url": "https://nuxtseo.com/en", "workTranslation": [ { "@id": "https://nuxtseo.com/ja#website" }, { "@id": "https://nuxtseo.com/zh#website" } ] }, { "@id": "https://nuxtseo.com/en/#webpage", "@type": "WebPage", "about": { "@id": "https://nuxtseo.com/#identity" }, "description": "The quickest and easiest way to build Schema.org graphs for Nuxt.", "isPartOf": { "@id": "https://nuxtseo.com/en#website" }, "name": "Welcome", "potentialAction": [ { "@type": "ReadAction", "target": [ "https://nuxtseo.com/en" ] } ], "url": "https://nuxtseo.com/en" }, { "@id": "https://nuxtseo.com/#identity", "@type": "Organization", "name": "nuxt-schema-org", "url": "https://nuxtseo.com" } ] } ``` [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/2.guides/2.i18n.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/guides/i18n/docs/schema-org/guides/i18n.md) **Did this page help you? ** [**Setup Identity** Improve your Schema.org by providing the identity of your site.](https://nuxtseo.com/docs/schema-org/guides/i18n/docs/schema-org/guides/setup-identity) [**Supported Nodes** The nodes available for Nuxt Schema.org.](https://nuxtseo.com/docs/schema-org/guides/i18n/docs/schema-org/guides/nodes) --- ### Full Documentation · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/guides/full-documentation Description: Links to the full unhead-schema-org documentation for advanced usage. **Core Concepts** h1. **Full Documentation** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix wrong descriptions and add schema validator links (#97) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-schema-org/pull/97). [Copy for LLMs The Nuxt Schema.org package is a simple wrapper around [**Unhead Schema.org**](https://unhead.unjs.io/schema-org/recipes/identity), you should consult the official documentation for full details. h2. [Popular Nodes](#popular-nodes) - [**Organization**](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/organization) - [**Person**](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/person) - [**WebSite**](https://unhead.unjs.io/docs/typescript/schema-org/api/schema/website) - [**WebPage**](https://unhead.unjs.io/docs/typescript/schema-org/api/schema/webpage) - [**Article**](https://unhead.unjs.io/docs/nuxt/schema-org/api/schema/article) h2. [Popular Recipes](#popular-recipes) - [**Identity**](https://unhead.unjs.io/schema-org/recipes/identity) - [**Blog**](https://unhead.unjs.io/schema-org/recipes/blog) - [**Breadcrumb**](https://unhead.unjs.io/docs/typescript/schema-org/guides/recipes/breadcrumbs) - [**FAQ**](https://unhead.unjs.io/schema-org/recipes/faq) [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/2.guides/3.full-documentation.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/guides/full-documentation/docs/schema-org/guides/full-documentation.md) **Did this page help you? ** [**Supported Nodes** The nodes available for Nuxt Schema.org.](https://nuxtseo.com/docs/schema-org/guides/full-documentation/docs/schema-org/guides/nodes) [**Nuxt Config** Configure the Nuxt Schema.org module.](https://nuxtseo.com/docs/schema-org/guides/full-documentation/docs/schema-org/api/config) **On this page** - [Popular Nodes](#popular-nodes) - [Popular Recipes](#popular-recipes) --- ### Config · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/api/config Description: Configure the link checker module. **Nuxt API** h1. **Config** Last updated **Dec 4, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix: ignore all `/_*` paths](https://github.com/harlan-zw/nuxt-link-checker/commit/1e5dd5e66211ebfa39c83d395ad5be7a44ebafe7). [Copy for LLMs h2. [`enabled`](#enabled) - Type: `boolean` - Default: `true` Whether to scan links. h2. [`skipInspections`](#skipinspections) - Type: `string[]` - Default: `[]` An array of inspection names to skip. See [**Link Checking Rules**](https://nuxtseo.com/docs/link-checker/api/config/docs/link-checker/guides/rules) for a list of inspections. h2. [`fetchTimeout`](#fetchtimeout) - Type: `number` - Default: `5000` How long to wait for a response before timing out. h2. [`report`](#report) - Type: `{ html?: boolean; markdown?: boolean; json?: boolean; publish?: boolean }` - Default: `undefined` Reports to generate on build. See the [**Generate Reports**](https://nuxtseo.com/docs/link-checker/api/config/docs/link-checker/guides/generating-reports) guide for more details. h2. [`showLiveInspections`](#showliveinspections) - Type: `boolean` - Default: `true` Show inspections as they are run. h2. [`runOnBuild`](#runonbuild) - Type: `boolean` - Default: `true` Whether to run the link checker on build. h2. [`failOnError`](#failonerror) - Type: `boolean` - Default: `true` If set to `true`, the build will fail if any broken links are found. h2. [`excludeLinks`](#excludelinks) - Type: `(string | RegExp)[]` - Default: `[/^\/_.*$/]` Links to exclude from inspection. Supports exact matches, wildcard patterns (using radix3), and regular expressions. **Pattern Types:** - Exact match: `/about` - Wildcard: `/admin/**` (all admin routes), `/api/*` (direct children only) - RegExp: `/^\/blog\/\d+$/` (blog posts with numeric IDs) - External: `https://example.com/**` Useful for routes that aren't prerendered but are known to be valid, or external links you don't want checked. h2. [`debug`](#debug) - Type: `boolean` - Default: `false` Enable to see debug logs. h2. [`host`](#host) **Deprecated ** - Type: `string` - Default: `runtimeConfig.public.siteUrl || localhost` The host of your site. This is required to validate absolute URLs which may be internal. This is now handled by the [**nuxt-site-config**](https://github.com/harlan-zw/nuxt-site-config) module. h2. [`trailingSlash`](#trailingslash) ** Deprecated ** - Type: `boolean` - Default: `false` Whether internal links should have a trailing slash or not. This is now handled by the [**nuxt-site-config**](https://github.com/harlan-zw/nuxt-site-config) module. [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/4.api/0.config.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/api/config/docs/link-checker/api/config.md) **Did this page help you? ** [**Generate Reports** See your link checker results when you build.](https://nuxtseo.com/docs/link-checker/api/config/docs/link-checker/guides/generating-reports) [**v4.0.0** Release notes for v4.0.0. of Nuxt Link Checker.](https://nuxtseo.com/docs/link-checker/api/config/docs/link-checker/releases/v4) **On this page** - [enabled](#enabled) - [skipInspections](#skipinspections) - [fetchTimeout](#fetchtimeout) - [report](#report) - [showLiveInspections](#showliveinspections) - [runOnBuild](#runonbuild) - [failOnError](#failonerror) - [excludeLinks](#excludelinks) - [debug](#debug) - [host](#host) - [trailingSlash](#trailingslash) --- ### v4.0.0 · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/releases/v4 Description: Release notes for v4.0.0. of Nuxt Link Checker. **Releases** h1. **v4.0.0** Last updated **Dec 4, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clean up](https://github.com/harlan-zw/nuxt-link-checker/commit/ab7259f2b55f7e5da6ecd02075fac93df6ec3772). [Copy for LLMs h2. [Introduction](#introduction) The v4 major of Nuxt Link Checker is a simple release to remove deprecations and add support for the [**Nuxt SEO v2 stable**](https://nuxtseo.com/docs/link-checker/releases/v4/announcement). h2. [Breaking Features](#breaking-features) h3. [Site Config v3](#site-config-v3) Nuxt Site Config is a module used internally by Nuxt Link Checker. The major update to v3.0.0 shouldn't have any direct effect on your site, however, you may want to double-check the [**breaking changes**](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v3.0.0). [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/4.releases/4.v4.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/releases/v4/docs/link-checker/releases/v4.md) **Did this page help you? ** [**Config** Configure the link checker module.](https://nuxtseo.com/docs/link-checker/releases/v4/docs/link-checker/api/config) [**v3.0.0** Release notes for v3.0.0. of Nuxt Link Checker.](https://nuxtseo.com/docs/link-checker/releases/v4/docs/link-checker/releases/v3) **On this page** - [Introduction](#introduction) - [Breaking Features](#breaking-features) --- ### Install Nuxt Link Checker · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/getting-started/installation Description: Get started with Nuxt Link Checker by installing the dependency to your project. **Getting Started** h1. **Install Nuxt Link Checker** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#69) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-link-checker/pull/69). [Copy for LLMs h2. [Setup Module](#setup-module) `npx nuxt module add link-checker` `npm i nuxt-link-checker` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-link-checker', ], }) ``` `yarn add nuxt-link-checker` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-link-checker', ], }) ``` `pnpm i nuxt-link-checker` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-link-checker', ], }) ``` `bun i nuxt-link-checker` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-link-checker', ], }) ``` h2. [Verifying Installation](#verifying-installation) Nuxt Link Checker provides a visualize integration in development through the Link Checker DevTools tab. Open up DevTools in your Nuxt site and navigate to the `Link Checker` tab to see live inspections of your links. See the [**Link Checker DevTools**](https://nuxtseo.com/docs/link-checker/getting-started/installation/docs/link-checker/guides/live-inspections) guide for more information. When building your site, the module will check links of all prerendered pages, it's recommended to prerender any pages that you'd like to have the links checked on. h2. [Next Steps](#next-steps) You've successfully installed Nuxt Link Checker and configured it for your site. 1. Check which [**Inspection Rules**](https://nuxtseo.com/docs/link-checker/getting-started/installation/docs/link-checker/guides/rules) are enabled by default 2. Use [**Nuxt Sitemap**](https://nuxtseo.com/docs/link-checker/getting-started/installation/docs/sitemap/getting-started/installation) so that all your page URLs are discovered automatically during link checking 3. Learn more about how [**Controlling Crawlers**](https://nuxtseo.com/docs/link-checker/getting-started/installation/learn-seo/nuxt/controlling-crawlers) interact with your site [Sitemap **v7.5.0** 9.2M 410 Powerfully flexible XML Sitemaps that integrate seamlessly.](https://nuxtseo.com/docs/link-checker/getting-started/installation/docs/sitemap/getting-started/introduction) [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/0.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/getting-started/installation/docs/link-checker/getting-started/installation.md) **Did this page help you? ** [**Introduction** Find and magically fix links that may be negatively affecting your Nuxt sites SEO.](https://nuxtseo.com/docs/link-checker/getting-started/installation/docs/link-checker/getting-started/introduction) [**Troubleshooting** Solve common link checking issues and create reproductions for bug reports.](https://nuxtseo.com/docs/link-checker/getting-started/installation/docs/link-checker/getting-started/troubleshooting) **On this page** - [Setup Module](#setup-module) - [Verifying Installation](#verifying-installation) - [Next Steps](#next-steps) --- ### Exclude Links · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/guides/exclude-links Description: Exclude links from being checked by the Nuxt Link Checker. **Core Concepts** h1. **Exclude Links** Last updated **Dec 4, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: lint](https://github.com/harlan-zw/nuxt-link-checker/commit/97e1b52090475b38d29aa2f47f49c2724bf742ab). [Copy for LLMs Exclude URLs from inspection by adding them to the `excludeLinks` array. h2. [Pattern Types](#pattern-types) h3. [Exact Matches](#exact-matches) ``` export default defineNuxtConfig({ linkChecker: { excludeLinks: [ '/about', '/contact', ], }, }) ``` h3. [Wildcards](#wildcards) Using radix3 route matching: ``` export default defineNuxtConfig({ linkChecker: { excludeLinks: [ '/admin/**', // All admin routes '/api/*', // Direct children only (/api/users, not /api/users/1) '/blog/*/comments', // Nested wildcards ], }, }) ``` h3. [Regular Expressions](#regular-expressions) ``` export default defineNuxtConfig({ linkChecker: { excludeLinks: [ /^\/blog\/\d+$/, // Blog posts with numeric IDs /\.(pdf|zip)$/, // File downloads ], }, }) ``` h3. [External Links](#external-links) ``` export default defineNuxtConfig({ linkChecker: { excludeLinks: [ 'https://example.com/**', /^https:\/\/.*\.example\.com/, ], }, }) ``` h2. [Common Use Cases](#common-use-cases) h3. [Exclude Dynamic Routes](#exclude-dynamic-routes) If you have dynamic routes that aren't prerendered: ``` export default defineNuxtConfig({ linkChecker: { excludeLinks: [ '/user/**', '/products/**', ], }, }) ``` h3. [Exclude Auth-Protected Routes](#exclude-auth-protected-routes) ``` export default defineNuxtConfig({ linkChecker: { excludeLinks: [ '/dashboard/**', '/profile/**', ], }, }) ``` h3. [Exclude Hash Links](#exclude-hash-links) ``` export default defineNuxtConfig({ linkChecker: { excludeLinks: [ /#.*/, // All hash links ], }, }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/2.guides/2.exclude-links.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/guides/exclude-links/docs/link-checker/guides/exclude-links.md) **Did this page help you? ** [**Checking Links on Build** Configure link checking during Nuxt build process and CI integration.](https://nuxtseo.com/docs/link-checker/guides/exclude-links/docs/link-checker/guides/build-scans) [**Generate Reports** See your link checker results when you build.](https://nuxtseo.com/docs/link-checker/guides/exclude-links/docs/link-checker/guides/generating-reports) **On this page** - [Pattern Types](#pattern-types) - [Common Use Cases](#common-use-cases) --- ### Troubleshooting Nuxt Link Checker · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/getting-started/troubleshooting Description: Solve common link checking issues and create reproductions for bug reports. **Getting Started** h1. **Troubleshooting Nuxt Link Checker** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, expand troubleshooting, and add tool links (#68) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-link-checker/pull/68). [Copy for LLMs h2. [Common Issues](#common-issues) h3. [Links Not Being Detected](#links-not-being-detected) If links aren't being detected during scans: 1. Ensure `runOnBuild` is not set to `false` in your config 2. Check that the page is being prerendered (SSG) or SSR'd 3. Verify links use standard `<a>` or `<NuxtLink>` elements h3. [False Positives on Valid Links](#false-positives-on-valid-links) Some links may be incorrectly flagged: - **Rate limiting**: External services may block rapid requests during scans - **Auth-protected pages**: Links requiring authentication will fail validation - **Dynamic routes**: Routes with parameters need proper prerendering To exclude specific links, see the [**Exclude Links**](https://nuxtseo.com/docs/link-checker/getting-started/troubleshooting/docs/link-checker/guides/exclude-links) guide. h3. [Build Failing Due to Broken Links](#build-failing-due-to-broken-links) If your CI is failing: 1. Review the generated report to identify actual broken links 2. Use `failOnError: false` temporarily while fixing issues 3. Consider using `skipInspections` for rules causing false positives ``` export default defineNuxtConfig({ linkChecker: { failOnError: false, // Temporarily disable to unblock builds }, }) ``` h3. [DevTools Integration Not Working](#devtools-integration-not-working) If live inspections aren't appearing: 1. Ensure Nuxt DevTools is enabled in your config 2. Check that `showLiveInspections` is not set to `false` 3. Restart your dev server after config changes h2. [Debugging Tools](#debugging-tools) - [**Meta Tag Checker**](https://nuxtseo.com/docs/link-checker/getting-started/troubleshooting/tools/meta-tag-checker) - Verify page metadata after fixing broken links - [**XML Sitemap Validator**](https://nuxtseo.com/docs/link-checker/getting-started/troubleshooting/tools/xml-sitemap-validator) - Cross-reference broken links with your sitemap h2. [Submitting an Issue](#submitting-an-issue) When submitting an issue, it's important to provide as much information as possible. The easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds: - [**Basic**](https://stackblitz.com/edit/nuxt-starter-r2wzt1?file=nuxt.config.ts) [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/0.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/getting-started/troubleshooting/docs/link-checker/getting-started/troubleshooting.md) **Did this page help you? ** [**Installation** Get started with Nuxt Link Checker by installing the dependency to your project.](https://nuxtseo.com/docs/link-checker/getting-started/troubleshooting/docs/link-checker/getting-started/installation) [**Inspection Rules** Find out what rules are run when checking your links and why they exist.](https://nuxtseo.com/docs/link-checker/getting-started/troubleshooting/docs/link-checker/guides/rules) **On this page** - [Common Issues](#common-issues) - [Debugging Tools](#debugging-tools) - [Submitting an Issue](#submitting-an-issue) --- ### Inspection Rules · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/guides/rules Description: Find out what rules are run when checking your links and why they exist. **Core Concepts** h1. **Inspection Rules** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#69) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-link-checker/pull/69). [Copy for LLMs The module runs 13 inspections covering broken links, SEO best practices, and accessibility. To disable any rule, add it to `skipInspections` in your config. h2. [Rule Categories](#rule-categories) | **Category** | **Rules** | | --- | --- | | Broken Links | `no-error-response`, `missing-hash` | | SEO | `no-uppercase-chars`, `no-underscores`, `trailing-slash`, `no-whitespace`, `no-non-ascii-chars` | | URL Structure | `absolute-site-urls`, `no-baseless`, `no-double-slashes`, `no-duplicate-query-params` | | Accessibility | `link-text`, `no-missing-href`, `no-javascript` | h2. [Disabling Rules](#disabling-rules) To disable any rule, add it to `skipInspections`: ``` export default defineNuxtConfig({ linkChecker: { skipInspections: ['rule-name'] } }) ``` h2. [Available Rules](#available-rules) Rules are based on the [**URL structure best practices for Google**](https://developers.google.com/search/docs/crawling-indexing/url-structure). | **Rule** | **Description** | | --- | --- | | [`absolute-site-urls`](#absolute-site-urls) | Checks for absolute links that are internal. | | [`link-text`](#link-text) | Ensures link text is descriptive and meaningful. | | [`missing-hash`](#missing-hash) | Checks for missing hashes in internal links. | | [`no-baseless`](#no-baseless) | Checks for document relative links. | | [`no-double-slashes`](#no-double-slashes) | Checks for double slashes in URLs. | | [`no-duplicate-query-params`](#no-duplicate-query-params) | Checks for duplicate query parameters in URLs. | | [`no-error-response`](#no-error-response) | Checks for error responses (4xx, 5xx) on internal links. | | [`no-javascript`](#no-javascript) | Checks for JavaScript links. | | [`no-missing-href`](#no-missing-href) | Ensures that `a` tags have an `href` attribute. | | [`no-non-ascii-chars`](#no-non-ascii-chars) | Checks for non-ASCII characters. | | [`no-underscores`](#no-underscores) | Checks for underscores. | | [`no-uppercase-chars`](#no-uppercase-chars) | Checks for uppercase characters. | | [`no-whitespace`](#no-whitespace) | Checks for whitespace. | | [`trailing-slash`](#trailing-slash) | Checks for trailing slashes on internal links. | h3. [`absolute-site-urls`](#absolute-site-urls) Checks for absolute links that are internal Using relative paths is recommended as it makes your site more portable and easier to maintain. ``` <NuxtLink to="https://example.com/about">my page</NuxtLink> ``` ``` <NuxtLink to="/about">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'absolute-site-urls' ], }, }) ``` h3. [`link-text`](#link-text) Ensures link text is descriptive and meaningful. Descriptive link text [**provide improved accessibility**](https://usability.yale.edu/web-accessibility/articles/links#link-text), allowing screen readers to better understand the context of the link. It includes link text such as "my page" or "Read more". ``` <NuxtLink to="/about">click here</NuxtLink> ``` ``` <NuxtLink to="/about">About</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'link-text' ], }, }) ``` h3. [`missing-hash`](#missing-hash) Checks for missing [**Document Fragments**](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Creating_hyperlinks#document_fragments) for internal links. Having valid hashes ensures that users are navigated to the correct section of the page, improving the user experience and accessibility. ``` <div id="valid-anchor"></div> <NuxtLink to="#valid">my page</NuxtLink> ``` ``` <div id="valid"></div> <NuxtLink to="#valid">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'missing-hash' ], }, }) ``` h3. [`no-baseless`](#no-baseless) Document relative links are valid but can cause SEO maintenance issues when moving around your content. It's recommended to use root relative links to avoid these issues. ``` <NuxtLink to="link">my page</NuxtLink> ``` ``` <NuxtLink to="/link">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-baseless' ], }, }) ``` h3. [`no-double-slashes`](#no-double-slashes) Checks for double slashes in URLs. Double slashes in URLs can cause canonicalization issues and can lead to duplicate content. ``` <NuxtLink to="/link//">my page</NuxtLink> ``` ``` <NuxtLink to="/link/">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-double-slashes' ], }, }) ``` h3. [`no-duplicate-query-params`](#no-duplicate-query-params) Checks for duplicate query parameters in URLs. Duplicate query parameters can cause issues with caching and can lead to duplicate content. ``` <NuxtLink to="/link?param=1¶m=2">my page</NuxtLink> ``` ``` <NuxtLink to="/link?param=1">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-duplicate-query-params' ], }, }) ``` h3. [`no-error-response`](#no-error-response) Checks for error responses [**(4xx, 5xx)**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) on internal links. Ensuring that internal links do not lead to error responses improves the user experience and improves your sites crawlability. Note: This does not scan external links due to unpredictable network conditions. ``` <NuxtLink to="/broken-link">my page</NuxtLink> ``` ``` <NuxtLink to="/valid-link">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-error-response' ], }, }) ``` h3. [`no-javascript`](#no-javascript) Checks for JavaScript links. JavaScript links provide poor user experience as they override the default browser behaviour. It's recommended to use a `<button type="button">` if you need an on click event. ``` <a href="javascript:doSomething()">my page</a> ``` ``` <button type="button" @click="doSomething()">my page</button> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-javascript' ], }, }) ``` h3. [`no-missing-href`](#no-missing-href) Ensures that links have an `href` attribute. Having an `href` attribute is required for links to be accessible and usable. If you need a an anchor that doesn't navigate, use a `<button>` instead or the `role="button"` attribute. ``` <a>my page</a> ``` ``` <a href="/link">my page</a> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-missing-href' ], }, }) ``` h3. [`no-non-ascii-chars`](#no-non-ascii-chars) Checks for non-ASCII characters in URLs. Non-ASCII characters can cause issues with encoding and can lead to broken links. ``` <NuxtLink to="/my-ページ">my page</NuxtLink> ``` ``` <NuxtLink to="/my-page">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-non-ascii-chars' ], }, }) ``` h3. [`no-underscores`](#no-underscores) Checks for underscores in URLs. Underscores are not recommended in URLs as they can cause issues with readability and SEO. ``` <NuxtLink to="/my_page">my page</NuxtLink> ``` ``` <NuxtLink to="/my-page">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-underscores' ], }, }) ``` h3. [`no-uppercase-chars`](#no-uppercase-chars) Checks for uppercase characters in URLs. Uppercase characters are not recommended in URLs as they can cause issues with readability and SEO. ``` <NuxtLink to="/MyPage">my page</NuxtLink> ``` ``` <NuxtLink to="/my-page">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-uppercase-chars' ], }, }) ``` h3. [`no-whitespace`](#no-whitespace) Checks for whitespace in URLs. Whitespace in URLs can cause issues with encoding and can lead to broken links. ``` <NuxtLink to="/my page">my page</NuxtLink> ``` ``` <NuxtLink to="/my-page">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'no-whitespace' ], }, }) ``` h3. [`trailing-slash`](#trailing-slash) Checks that internal links all either have or don't have a trailing slash depending on your configuration. Inconsistent trailing slashes can cause issues with canonicalization and can lead to duplicate content. ``` <NuxtLink to="/my-page/">my page</NuxtLink> ``` ``` <NuxtLink to="/my-page">my page</NuxtLink> ``` ``` export default defineNuxtConfig({ linkChecker: { skipInspections: [ 'trailing-slash' ], }, }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/2.guides/0.rules.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/guides/rules/docs/link-checker/guides/rules.md) **Did this page help you? ** [**Troubleshooting** Solve common link checking issues and create reproductions for bug reports.](https://nuxtseo.com/docs/link-checker/guides/rules/docs/link-checker/getting-started/troubleshooting) [**Link Checker DevTools** See link validation errors in real-time during development with Nuxt DevTools integration.](https://nuxtseo.com/docs/link-checker/guides/rules/docs/link-checker/guides/live-inspections) **On this page** - [Rule Categories](#rule-categories) - [Disabling Rules](#disabling-rules) - [Available Rules](#available-rules) --- ### Link Checker DevTools · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/guides/live-inspections Description: See link validation errors in real-time during development with Nuxt DevTools integration. **Core Concepts** h1. **Link Checker DevTools** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#69) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-link-checker/pull/69). [Copy for LLMs Live inspections require [**Nuxt DevTools**](https://devtools.nuxt.com/) to be enabled. h2. [Introduction](#introduction) Live Inspections are a feature of the Nuxt Link Checker that allows you to see the results of inspections on the associated link in real time. Red squiggles indicate a broken link, while yellow squiggles indicate a link that has a warning. h2. [Enabling Live Inspections](#enabling-live-inspections) Live Inspections are enabled by default when you have Nuxt Dev Tools enabled. nuxt.config.ts ``` export default defineNuxtConfig({ devtools: { enabled: true, }, }) ``` Clicking on a link inspection will open the inspection results in Nuxt Dev Tools. h2. [Disabling Live Inspections](#disabling-live-inspections) You can disable live inspections by setting `showLiveInspections` to `false`. nuxt.config.ts ``` export default defineNuxtConfig({ linkChecker: { showLiveInspections: false, }, }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/2.guides/1.live-inspections.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/guides/live-inspections/docs/link-checker/guides/live-inspections.md) **Did this page help you? ** [**Inspection Rules** Find out what rules are run when checking your links and why they exist.](https://nuxtseo.com/docs/link-checker/guides/live-inspections/docs/link-checker/guides/rules) [**Checking Links on Build** Configure link checking during Nuxt build process and CI integration.](https://nuxtseo.com/docs/link-checker/guides/live-inspections/docs/link-checker/guides/build-scans) **On this page** - [Introduction](#introduction) - [Enabling Live Inspections](#enabling-live-inspections) - [Disabling Live Inspections](#disabling-live-inspections) --- ### useRobotsRule() · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/api/use-robots-rule Description: A reactive way to access and set the robots rule. **Nuxt API** h1. **useRobotsRule()** Last updated **Jul 10, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: broken tests](https://github.com/nuxt-modules/robots/commit/6fb9544c63b419ae07dd74d1f1f280f5d00ed2be). [Copy for LLMs h2. [Introduction](#introduction) **Type:** `function useRobotsRule(rule?: MaybeRef<boolean | string | Partial<RobotDirectives>>): Ref<string>` View and control the robots rule using a simple reactivity API. Supports standard directives (index, noindex, follow, nofollow) and non-standard directives like noai and noimageai. It's recommended to use this composable when you need to dynamically change the robots rule at runtime. For example when a user changes their profile from private to public. Note: This does not modify the `/robots.txt` file, only the `X-Robots-Tag` header and the `robots` meta tag. h3. [Server Side Behavior](#server-side-behavior) In a server-side context, this can be used to change the rule used for `X-Robots-Tag` header and the `robots` meta tag. Providing a `boolean` will either enable or disable indexing for the current path using the default rules. ``` import { useRobotsRule } from '#imports' const rule = useRobotsRule(true) // modifies the rules ``` h3. [Client Side Behavior](#client-side-behavior) In a client-side context you can only read the value of the rule, modifying it will have no effect. This is due to robots only respecting the initial SSR response. ``` import { useRobotsRule } from '#imports' const rule = useRobotsRule(true) // does not do anything, just returns the value ``` h2. [Available Directives](#available-directives) When using the object syntax, you can use the following directives: h3. [Standard Directives](#standard-directives) - `index`: Allow search engines to index the page - `noindex`: Prevent search engines from indexing the page - `follow`: Allow search engines to follow links on the page - `nofollow`: Prevent search engines from following links on the page - `none`: Equivalent to `noindex, nofollow` - `all`: Equivalent to `index, follow` h3. [Non-Standard Directives](#non-standard-directives) - `noai`: Request AI crawlers not to use content for training - `noimageai`: Request AI crawlers not to use images for training h3. [Preview Control Directives](#preview-control-directives) - `max-image-preview`: Controls image preview size (`'none'`, `'standard'`, or `'large'`) - `max-snippet`: Controls text snippet length in characters (use `-1` for no limit) - `max-video-preview`: Controls video preview length in seconds (use `-1` for no limit) h2. [Usage](#usage) **Accessing the rule:** ``` import { useRobotsRule } from '#imports' const rule = useRobotsRule() // Ref<'noindex, nofollow'> ``` **Setting the rule - argument:** ``` import { useRobotsRule } from '#imports' useRobotsRule('index, nofollow') // Ref<'index, nofollow'> useRobotsRule(false) // Ref<'noindex, nofollow'> ``` **Setting the rule - reactive:** ``` import { useRobotsRule } from '#imports' const rule = useRobotsRule() rule.value = 'index, nofollow' // Ref<'index, nofollow'> ``` **Setting the rule - object syntax:** ``` import { useRobotsRule } from '#imports' // Using object syntax for directives useRobotsRule({ noindex: true, nofollow: true }) // Ref<'noindex, nofollow'> // Combining standard and non-standard directives useRobotsRule({ index: true, noai: true, noimageai: true }) // Ref<'index, noai, noimageai'> // Only true values are included useRobotsRule({ index: true, follow: false, noai: true }) // Ref<'index, noai'> // Empty object defaults to enabled value useRobotsRule({}) // Ref<'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1'> ``` __Setting the rule - with max-_ directives:_* ``` import { useRobotsRule } from '#imports' // Control search result preview settings useRobotsRule({ 'index': true, 'max-image-preview': 'large', // 'none', 'standard', or 'large' 'max-snippet': 150, // number of characters 'max-video-preview': 30 // seconds of video preview }) // Ref<'index, max-image-preview:large, max-snippet:150, max-video-preview:30'> // Disable all previews useRobotsRule({ 'index': true, 'max-image-preview': 'none', 'max-snippet': 0, 'max-video-preview': 0 }) // Ref<'index, max-image-preview:none, max-snippet:0, max-video-preview:0'> ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/3.api/0.use-robots-rule.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/api/use-robots-rule/docs/robots/api/use-robots-rule.md) **Did this page help you? ** [**Nuxt I18n** How to use the Nuxt Robots module with Nuxt I18n.](https://nuxtseo.com/docs/robots/api/use-robots-rule/docs/robots/advanced/i18n) [**Nuxt Config** Learn how to configure Nuxt Robots using nuxt.config.](https://nuxtseo.com/docs/robots/api/use-robots-rule/docs/robots/api/config) **On this page** - [Introduction](#introduction) - [Available Directives](#available-directives) - [Usage](#usage) --- ### Checking Links on Build · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/guides/build-scans Description: Configure link checking during Nuxt build process and CI integration. **Core Concepts** h1. **Checking Links on Build** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, expand troubleshooting, and add tool links (#68) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-link-checker/pull/68). [Copy for LLMs h2. [Introduction](#introduction) By default, links will be scanned when you run your build. h2. [Throwing Build Errors](#throwing-build-errors) When building and deploying your app in a CI, you may like to disable the deployment if there are any broken links. To do so you can enable the `failOnError` option. This will exit the process with a non-zero exit code. ``` export default defineNuxtConfig({ linkChecker: { failOnError: true, }, }) ``` h2. [Generating Reports](#generating-reports) Check the [**Generate Reports**](https://nuxtseo.com/docs/link-checker/guides/build-scans/docs/link-checker/guides/generating-reports) guide for more details. h2. [Disabling Build Scans](#disabling-build-scans) If you want to disable build link scanning, you can set `runOnBuild` to `false` in your `nuxt.config`: ``` export default defineNuxtConfig({ linkChecker: { runOnBuild: false, }, }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/2.guides/2.build-scans.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/guides/build-scans/docs/link-checker/guides/build-scans.md) **Did this page help you? ** [**Link Checker DevTools** See link validation errors in real-time during development with Nuxt DevTools integration.](https://nuxtseo.com/docs/link-checker/guides/build-scans/docs/link-checker/guides/live-inspections) [**Exclude Links** Exclude links from being checked by the Nuxt Link Checker.](https://nuxtseo.com/docs/link-checker/guides/build-scans/docs/link-checker/guides/exclude-links) **On this page** - [Introduction](#introduction) - [Throwing Build Errors](#throwing-build-errors) - [Generating Reports](#generating-reports) - [Disabling Build Scans](#disabling-build-scans) --- ### v5.0.0 · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/releases/v5 Description: Release notes for Nuxt Robots v5.0.0. **Releases** h1. **v5.0.0** Last updated **Dec 2, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clean up](https://github.com/nuxt-modules/robots/commit/859edb2b931d6aa4ee50b856f4df07a6446b7045). [Copy for LLMs h2. [Introduction](#introduction) The v5 major of Nuxt Robots is a simple release to remove deprecations and add support for the [**Nuxt SEO v2 stable**](https://nuxtseo.com/announcement). h2. [Breaking Features](#breaking-features) h3. [Site Config v3](#site-config-v3) Nuxt Site Config is a module used internally by Nuxt Robots. It's major update to v3.0.0 shouldn't have any direct affect your site, however, you may want to double-check the [**breaking changes**](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v3.0.0). h3. [Removed `rules` config](#removed-rules-config) The v4 of Nuxt Robots provided a backward compatibility `rules` config. As it was deprecated, this is no longer supported. If you're using `rules`, you should migrate to the `groups` config or use a robots.txt file. ``` export default defineNuxtConfig({ robots: { - rules: {}, + groups: {} } }) ``` h3. [Removed `defineRobotMeta` composable](#removed-definerobotmeta-composable) This composable didn't do anything in v4 as the robots meta tag is enabled by default. If you'd like to control the robot meta tag rule, use the [`useRobotsRule()`](https://nuxtseo.com/docs/robots/api/use-robots-rule) composable. ``` - defineRobotMeta(true) + useRobotsRule(true) ``` h3. [Removed `RobotMeta` component](#removed-robotmeta-component) This component was a simple wrapper for `defineRobotMeta`, you should use [`useRobotsRule()`](https://nuxtseo.com/docs/robots/api/use-robots-rule) if you wish to control the robots rule. h3. [Removed `index`, `indexable` config](#removed-index-indexable-config) When configuring robots using route rules or [**Nuxt Content**](https://nuxtseo.com/docs/robots/advanced/content) you could control the robot's behavior by providing `index` or `indexable` rules. These are no longer supported and you should use `robots` key. ``` export default defineNuxtConfig({ routeRules: { // use the \`index\` shortcut for simple rules - '/secret/**': { index: false }, + '/secret/**': { robots: false }, } }) ``` h2. [Features](#features) h3. [Config `blockAiBots`](#config-blockaibots) AI crawlers can be beneficial as they can help users finding your site, but for some educational sites or those not interested in being indexed by AI crawlers, you can block them using the `blockAIBots` option. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { blockAiBots: true } }) ``` This will block the following AI crawlers: `GPTBot`, `ChatGPT-User`, `Claude-Web`, `anthropic-ai`, `Applebot-Extended`, `Bytespider`, `CCBot`, `cohere-ai`, `Diffbot`, `FacebookBot`, `Google-Extended`, `ImagesiftBot`, `PerplexityBot`, `OmigiliBot`, `Omigili` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/4.releases/4.v5.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/releases/v5/docs/robots/releases/v5.md) **Did this page help you? ** [**Nitro Hooks** Learn how to use Nitro hooks to modify the robots final output.](https://nuxtseo.com/docs/robots/releases/v5/docs/robots/nitro-api/nitro-hooks) [**v4.0.0** Release notes for Nuxt Robots v4.0.0.](https://nuxtseo.com/docs/robots/releases/v5/docs/robots/releases/v4) **On this page** - [Introduction](#introduction) - [Breaking Features](#breaking-features) - [Features](#features) --- ### Generate Reports · Nuxt Link Checker · Nuxt SEO Source: https://nuxtseo.com/docs/link-checker/guides/generating-reports Description: See your link checker results when you build. **Core Concepts** h1. **Generate Reports** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, expand troubleshooting, and add tool links (#68) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-link-checker/pull/68). [Copy for LLMs h2. [Introduction](#introduction) The Nuxt Link Checker module can generate reports of the broken links in your application. This is useful for CI environments where you want to see the results of the link checker without having to run it in your local environment. h2. [Generating Reports](#generating-reports) There are three reports available: `html`, `markdown` and `json`. - `html`: a human readable report that can be opened in your browser. - `markdown`: can be consumed by LLMs tools or embedded within GitHub pull requests. - `json`: a machine readable report that can be used in your CI To generate them, you can provide the `report` option: ``` export default defineNuxtConfig({ linkChecker: { report: { // pick and choose which reports you want to generate html: true, markdown: true, json: true, } }, }) ``` The reports will be output in the following paths: - `html`: `./output/link-checker-report.html` - `markdown`: `./output/link-checker-report.md` - `json`: `./output/link-checker-report.json` h2. [Publishing Public Reports](#publishing-public-reports) Keeping your links healthy can be a lot of effort and be frustrating when you you are blocked in your CI pipeline due to them. For this reason, you may want to publish your link checker reports as publicly accessible files after your deployment. These will be non-indexable but directly accessible by anyone. You can make your link checker reports accessible after deployment by using the publish flag: nuxt.config.ts ``` export default defineNuxtConfig({ linkChecker: { report: { publish: true } }, }) ``` When the publish flag is set to true, the reports will be: 1. Generated during the build process 2. Copied to your public directory 3. Available at the following paths once deployed: - HTML report: [**https://nuxtseo.com/****link-checker****/link-checker-report.html**](https://nuxtseo.com/docs/link-checker/guides/generating-reports/__link-checker__/link-checker-report.html) - Markdown report: [**https://nuxtseo.com/****link-checker****/link-checker-report.md**](https://nuxtseo.com/docs/link-checker/guides/generating-reports/__link-checker__/link-checker-report.md) - JSON report: [**https://nuxtseo.com/****link-checker****/link-checker-report.json**](https://nuxtseo.com/docs/link-checker/guides/generating-reports/__link-checker__/link-checker-report.json) [](https://nuxtseo.com/docs/link-checker/guides/generating-reports/tools/xml-sitemap-validator)**Cross-reference with sitemap** - Use our [**XML Sitemap Validator**](https://nuxtseo.com/docs/link-checker/guides/generating-reports/tools/xml-sitemap-validator) to ensure broken links aren't in your sitemap. [Edit this page](https://github.com/harlan-zw/nuxt-link-checker/edit/main/docs/content/2.guides/3.generating-reports.md) [Markdown For LLMs](https://nuxtseo.com/docs/link-checker/guides/generating-reports/docs/link-checker/guides/generating-reports.md) **Did this page help you? ** [**Exclude Links** Exclude links from being checked by the Nuxt Link Checker.](https://nuxtseo.com/docs/link-checker/guides/generating-reports/docs/link-checker/guides/exclude-links) [**Config** Configure the link checker module.](https://nuxtseo.com/docs/link-checker/guides/generating-reports/docs/link-checker/api/config) **On this page** - [Introduction](#introduction) - [Generating Reports](#generating-reports) - [Publishing Public Reports](#publishing-public-reports) --- ### Disable Page Indexing · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/disable-page-indexing Description: Learn how to disable indexing for specific pages on your app. **Core Concepts** h1. **Disable Page Indexing** Last updated **Jul 10, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: broken tests](https://github.com/nuxt-modules/robots/commit/6fb9544c63b419ae07dd74d1f1f280f5d00ed2be). [Copy for LLMs h2. [Introduction](#introduction) As not all sites are the same, it's important for you to have a flexible way to disable indexing for specific pages. The best options to choose are either: - [**Robots.txt**](#robotstxt) - Great for blocking robots from accessing specific pages that haven't been indexed yet. - [**useRobotsRule**](#userobotsrule) - Controls the `<meta name="robots" content="...">` meta tag and `X-Robots-Tag` HTTP Header. Useful for dynamic pages where you may not know if it should be indexed at build time and when you need to remove pages from search results. For example, a user profile page that should only be indexed if the user has made their profile public. If you're still unsure about which option to choose, make sure you read the [**Controlling Web Crawlers**](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/learn/conquering-crawlers) guide. [Conquering Web Crawlers 10 min read Being able to tell crawlers what to do can help with your SEO strategy, learn how to do it in Vue and Nuxt.](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/learn/controlling-crawlers) [**Route Rules**](#route-rules) and [**Nuxt Config**](#nuxt-config) are also available for more complex scenarios. h2. [Robots.txt](#robotstxt) Please follow the [**Config using Robots.txt**](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/robots/guides/robots-txt) guide to configure your `robots.txt` file. You'll be able to use the `Disallow` directive within a `robots.txt` file to block specific URLs. public/_robots.txt ``` User-agent: * Disallow: /my-page Disallow: /secret/* ``` h2. [useRobotsRule](#userobotsrule) The [**useRobotsRule**](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/robots/api/use-robots-rule) composable provides a reactive way to access and set the robots rule at runtime. ``` import { useRobotsRule } from '#imports' // Using string syntax const rule = useRobotsRule() rule.value = 'noindex, nofollow' // Using object syntax (recommended) useRobotsRule({ noindex: true, nofollow: true }) // Combining with AI-specific directives useRobotsRule({ noindex: true, noai: true, // Prevent AI crawlers from using content noimageai: true // Prevent AI crawlers from using images }) ``` h2. [Route Rules](#route-rules) If you have a static page that you want to disable indexing for, you can use [**defineRouteRules**](https://nuxt.com/docs/api/utils/define-route-rules) (requires enabling the experimental `inlineRouteRules`). This is a build-time configuration that will generate the appropriate rules in the `/robots.txt` file and is integrated with the [**Sitemap**](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/sitemap/guides/robots) module. pages/about.vue ``` <script lang="ts" setup> defineRouteRules({ robots: false, }) </script> ``` For more complex scenarios see the [**Route Rules**](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/robots/guides/route-rules) guide. h2. [Nuxt Config](#nuxt-config) If you need finer programmatic control, you can configure the module using nuxt.config. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { disallow: ['/secret', '/admin'], } }) ``` See the [**Nuxt Config**](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/robots/guides/nuxt-config) guide for more details. [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/1.disable-page-indexing.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/robots/guides/disable-page-indexing.md) **Did this page help you? ** [**Disabling Site Indexing** Learn how to disable indexing for different environments and conditions to avoid crawling issues.](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/robots/guides/disable-indexing) [**How Nuxt Robots Works** Learn more about how Nuxt Robots works.](https://nuxtseo.com/docs/robots/guides/disable-page-indexing/docs/robots/guides/how-it-works) **On this page** - [Introduction](#introduction) - [Robots.txt](#robotstxt) - [useRobotsRule](#userobotsrule) - [Route Rules](#route-rules) - [Nuxt Config](#nuxt-config) --- ### Install Nuxt Robots · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/getting-started/installation Description: Get started with Nuxt Robots by installing the dependency to your project. **Getting Started** h1. **Install Nuxt Robots** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#257) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/257). [Copy for LLMs h2. [Setup Module](#setup-module) Want to know why you need this module? Check out the [**introduction**](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/getting-started/introduction). To get started with Nuxt Robots, you need to install the dependency and add it to your Nuxt config. `npx nuxt module add robots` `npm i @nuxtjs/robots` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/robots', ], }) ``` `yarn add @nuxtjs/robots` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/robots', ], }) ``` `pnpm i @nuxtjs/robots` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/robots', ], }) ``` `bun i @nuxtjs/robots` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/robots', ], }) ``` h2. [Verifying Installation](#verifying-installation) To ensure the module is behaving as expected, you should first check [`/robots.txt`](http://localhost:3000/robots.txt) is being generated. It should show that the site is disallowed from indexing, this is good as development environments should not be indexed by search engines. However, we want to see what a production environment would look like. For this, it's recommended to use the Nuxt DevTools Robots tab to see the current configuration and how it's being applied. The DevTools will show you that in production we're just serving a minimal robots.txt file. robots.txt ``` User-agent: * Disallow: ``` This allows all search engines to index the site. h2. [Configuration](#configuration) Every site is different and will require their own unique configuration, to give you a head start you may consider the following areas to configure. - [**Disabling Site Indexing**](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/guides/disable-indexing) - If you have non-production environments you should disable indexing for these environments, while this works out-of-the-box for most providers, it's good to verify this is working as expected. - [**Disable Page Indexing**](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/guides/disable-page-indexing) - You should consider excluding pages that are not useful to search engines, for example any routes which require authentication should be ignored. Make sure you understand the differences between robots.txt vs robots meta tag with the [**Controlling Web Crawlers**](https://nuxtseo.com/docs/robots/getting-started/installation/learn/controlling-crawlers) guide. [Controlling Web Crawlers 10 min read Being able to tell crawlers what to do can help with your SEO strategy, learn how to do it in Vue and Nuxt.](https://nuxtseo.com/docs/robots/getting-started/installation/learn/controlling-crawlers) h2. [Next Steps](#next-steps) You've successfully installed Nuxt Robots and configured it for your project. Documentation is provided for module integrations, check them out if you're using them. - [**Nuxt I18n**](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/advanced/i18n) - Disallows are automatically expanded to your configured locales. - [**Nuxt Content**](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/advanced/content) - Configure robots from your markdown files. Next check out the [**robots.txt recipes**](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/guides/robot-recipes) guide for some inspiration. [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/1.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/getting-started/installation.md) **Did this page help you? ** [**Introduction** Nuxt Robots manages the robots crawling your site with minimal config and best practice defaults.](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/getting-started/introduction) [**Troubleshooting** Debug Nuxt Robots issues using DevTools, config options, and minimal reproductions.](https://nuxtseo.com/docs/robots/getting-started/installation/docs/robots/getting-started/troubleshooting) **On this page** - [Setup Module](#setup-module) - [Verifying Installation](#verifying-installation) - [Configuration](#configuration) - [Next Steps](#next-steps) --- ### Config Using Route Rules · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/route-rules Description: Learn how to configure robots through route rules. **Core Concepts** h1. **Config Using Route Rules** Last updated **Jul 10, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: broken tests](https://github.com/nuxt-modules/robots/commit/6fb9544c63b419ae07dd74d1f1f280f5d00ed2be). [Copy for LLMs If you prefer, you can use route rules to configure how your routes are indexed by search engines. You can provide the following rules: - `{ robots: false }` - Will disable the route from being indexed using the [**robotsDisabledValue**](https://nuxtseo.com/docs/robots/guides/route-rules/docs/robots/api/config#robotsdisabledvalue) config. - `{ robots: '<rule>' }` - Will add the provided string as the robots rule - `{ robots: { /* directives */ } }` - Will use object syntax to define robot directives The rules are applied using the following logic: - `X-Robots-Tag` header - SSR only, - `<meta name="robots">` - `/robots.txt` disallow entry - When [**disallowNonIndexableRoutes**](https://nuxtseo.com/docs/robots/guides/route-rules/docs/robots/api/config#robotsdisabledvalue) is enabled h2. [Inline Route Rules](#inline-route-rules) Requires enabling the experimental `inlineRouteRules`, see the [**defineRouteRules**](https://nuxt.com/docs/api/utils/define-route-rules) documentation to learn more. ``` <script lang="ts" setup> defineRouteRules({ robots: false, }) </script> ``` h2. [Nuxt Config](#nuxt-config) nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // use the \`index\` shortcut for simple rules '/secret/**': { robots: false }, // add exceptions for individual routes '/secret/visible': { robots: true }, // use the \`robots\` rule if you need finer control '/custom-robots': { robots: 'index, follow' }, // use object syntax for more complex rules '/ai-protected': { robots: { index: true, noai: true, noimageai: true } }, // control search result previews '/limited-preview': { robots: { 'index': true, 'max-image-preview': 'standard', 'max-snippet': 100, 'max-video-preview': 15 } } } }) ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/2.route-rules.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/route-rules/docs/robots/guides/route-rules.md) **Did this page help you? ** [**Config using Nuxt Config** Learn how to configure the module programmatically using nuxt.config.](https://nuxtseo.com/docs/robots/guides/route-rules/docs/robots/guides/nuxt-config) [**Bot Detection** Detect and classify bots with server-side header analysis and client-side browser fingerprinting.](https://nuxtseo.com/docs/robots/guides/route-rules/docs/robots/guides/bot-detection) **On this page** - [Inline Route Rules](#inline-route-rules) - [Nuxt Config](#nuxt-config) --- ### Disabling Site Indexing · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/disable-indexing Description: Learn how to disable indexing for different environments and conditions to avoid crawling issues. **Core Concepts** h1. **Disabling Site Indexing** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#257) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/257). [Copy for LLMs h2. [Introduction](#introduction) Disabling certain environments of your site from being indexed is an important practice to avoid SEO issues. For example, you don't want your staging or preview environments to be indexed by search engines as they will cause duplicate content issues as well as confuse end-users. If you need to disable specific pages from being indexed, refer to the [**Disabling Page Indexing**](https://nuxtseo.com/docs/robots/guides/disable-indexing/docs/robots/guides/disable-page-indexing) guide. h2. [Disable Indexing Completely](#disable-indexing-completely) In some cases, such as internal business tools, or sites that are not ready for the public, you may want to disable indexing completely. You can achieve this by setting the `indexable` option to `false` in your site config. ``` export default defineNuxtConfig({ site: { indexable: false } }) ``` h2. [Handling Staging Environments](#handling-staging-environments) Staging environments are great for testing out code before it goes to production. However, we definitely don't want search engines to index them. To control the indexing of these sites we will make use of the `env` Site Config, which defaults to `production`. .env ``` NUXT_SITE_ENV=staging ``` Nuxt Robots will disable indexing for any sites which don't have a production environment, so feel free to set this to whatever makes sense for your project. h2. [Verifying Indexing](#verifying-indexing) To verify that your site is not being indexed, you can check the generated `robots.txt` file, it should look something like this. ``` User-agent: * Disallow: / ``` A robots meta tag should also be generated that looks like: ``` <meta name="robots" content="noindex, nofollow"> ``` For full confidence you can inspect the URL within Google Search Console to see if it's being indexed. **Key points:** Use `site.indexable: false` for complete blocking, `NUXT_SITE_ENV` for staging environments. [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/1.disable-indexing.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/disable-indexing/docs/robots/guides/disable-indexing.md) **Did this page help you? ** [**Troubleshooting** Debug Nuxt Robots issues using DevTools, config options, and minimal reproductions.](https://nuxtseo.com/docs/robots/guides/disable-indexing/docs/robots/getting-started/troubleshooting) [**Disable Page Indexing** Learn how to disable indexing for specific pages on your app.](https://nuxtseo.com/docs/robots/guides/disable-indexing/docs/robots/guides/disable-page-indexing) **On this page** - [Introduction](#introduction) - [Disable Indexing Completely](#disable-indexing-completely) - [Handling Staging Environments](#handling-staging-environments) - [Verifying Indexing](#verifying-indexing) --- ### Troubleshooting Nuxt Robots · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/getting-started/troubleshooting Description: Debug Nuxt Robots issues using DevTools, config options, and minimal reproductions. **Getting Started** h1. **Troubleshooting Nuxt Robots** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, broken links, and add robots.txt tool links (#256) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/256). [Copy for LLMs h2. [Debugging](#debugging) h3. [Nuxt DevTools](#nuxt-devtools) The best tool for debugging is the Nuxt DevTools integration with Nuxt Robots. This will show you the current robot rules and your robots.txt file. h3. [Debug Config](#debug-config) You can enable the [**debug**](https://nuxtseo.com/docs/robots/getting-started/troubleshooting/docs/robots/api/config#debug) option which will give you more granular output. This is enabled by default in development mode. h2. [Debugging Tools](#debugging-tools) - [**Robots.txt Generator & Tester**](https://nuxtseo.com/docs/robots/getting-started/troubleshooting/tools/robots-txt-generator) - Validate your robots.txt output and test against Googlebot, GPTBot, ClaudeBot and 50+ user agents h2. [Submitting an Issue](#submitting-an-issue) When submitting an issue, it's important to provide as much information as possible. The easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds: - [**Basic**](https://stackblitz.com/edit/nuxt-starter-zycxux?file=public%2F_robots.txt) - [**I18n**](https://stackblitz.com/edit/nuxt-starter-pnej8lvb?file=public%2F_robots.txt) [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/1.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/getting-started/troubleshooting/docs/robots/getting-started/troubleshooting.md) **Did this page help you? ** [**Installation** Get started with Nuxt Robots by installing the dependency to your project.](https://nuxtseo.com/docs/robots/getting-started/troubleshooting/docs/robots/getting-started/installation) [**Disabling Site Indexing** Learn how to disable indexing for different environments and conditions to avoid crawling issues.](https://nuxtseo.com/docs/robots/getting-started/troubleshooting/docs/robots/guides/disable-indexing) **On this page** - [Debugging](#debugging) - [Debugging Tools](#debugging-tools) - [Submitting an Issue](#submitting-an-issue) --- ### How Nuxt Robots Works · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/how-it-works Description: Learn more about how Nuxt Robots works. **Core Concepts** h1. **How Nuxt Robots Works** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#257) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/257). [Copy for LLMs Nuxt Robots tells robots (crawlers) how to behave by creating a `robots.txt` file for you, adding a `X-Robots-Tag` header and `<meta name="robots">` tag to your site where appropriate. One important behaviour to control is blocking Google from indexing pages to: - Prevent [**duplicate content issues**](https://moz.com/learn/seo/duplicate-content) - Prevent wasting [**crawl budget**](https://developers.google.com/search/docs/crawling-indexing/large-site-managing-crawl-budget) h2. [Robots.txt](#robotstxt) For robots to understand how they can access your site, they will first check for a [**robots.txt file**](https://nuxtseo.com/docs/robots/guides/how-it-works/docs/robots/guides/robots-txt). ``` public └── robots.txt ``` This file is generated differently depending on the environment: - When deploying using `nuxi generate` or the `nitro.prerender.routes` rule, this is a static file. - Otherwise, it's handled by the server and generated at runtime when requested. When indexing is disabled a `robots.txt` will be generated with the following content: robots.txt ``` User-agent: * Disallow: / ``` This blocks all bots from indexing your site. h2. [`X-Robots-Tag` Header and `<meta name="robots">`](#x-robots-tag-header-and-meta-namerobots) In some situations, the robots.txt becomes too restrictive to provide the level of control you need to manage your site's indexing. For this reason, the module by default will provide a `X-Robots-Tag` header and `<meta name="robots">` tag. These are applied using the following logic: - `X-Robots-Tag` header - Route Rules are implemented for all modes, otherwise SSR only. This will only be added when indexing has been disabled for the route. - `<meta name="robots">` - SSR only, will always be added h2. [Robot Rules](#robot-rules) Default values for the `robots` rule depending on the mode. For indexable routes the following is used: ``` <meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"> ``` Besides giving robots the go-ahead, this also requests that Google: > *Choose the snippet length that it believes is most effective to help users discover your content and direct users to your site."* You can learn more on the [**Robots Meta Tag**](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag) documentation, feel free to change this to suit your needs using `robotsEnabledValue`. For non-indexable routes the following is used: ``` <meta name="robots" content="noindex, nofollow"> ``` This will tell robots to not index the page. h2. [Development Environment](#development-environment) The module by default will disable indexing in development environments. This is for safety, as you don't want your development environment to be indexed by search engines. robots.txt ``` h1. Block all bots User-agent: * Disallow: / ``` h2. [Production Environments](#production-environments) For production environments, the module will generate a `robots.txt` file that allows all bots. Out-of-the-box, this will be the following: robots.txt ``` User-agent: * Disallow: ``` This tells all bots that they can index your entire site. [](https://nuxtseo.com/docs/robots/guides/how-it-works/tools/robots-txt-generator)**Test your rules** - Validate your robots.txt with our [**Robots.txt Generator & Tester**](https://nuxtseo.com/docs/robots/guides/how-it-works/tools/robots-txt-generator). [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/1.how-it-works.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/how-it-works/docs/robots/guides/how-it-works.md) **Did this page help you? ** [**Disable Page Indexing** Learn how to disable indexing for specific pages on your app.](https://nuxtseo.com/docs/robots/guides/how-it-works/docs/robots/guides/disable-page-indexing) [**Robots.txt Recipes** Common robots.txt patterns including blocking bad bots, AI crawlers, and search results.](https://nuxtseo.com/docs/robots/guides/how-it-works/docs/robots/guides/robot-recipes) **On this page** - [Robots.txt](#robotstxt) - [X-Robots-Tag Header and <meta name="robots">](#x-robots-tag-header-and-meta-namerobots) - [Robot Rules](#robot-rules) - [Development Environment](#development-environment) - [Production Environments](#production-environments) --- ### Robots.txt Recipes · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/robot-recipes Description: Common robots.txt patterns including blocking bad bots, AI crawlers, and search results. **Core Concepts** h1. **Robots.txt Recipes** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#257) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/257). [Copy for LLMs h2. [Introduction](#introduction) As a minimum the only recommended configuration for robots is to [**disable indexing for non-production environments**](https://nuxtseo.com/docs/robots/guides/robot-recipes/docs/robots/guides/disable-indexing). Many sites will never need to configure their [`robots.txt`](https://nuxtseo.com/learn/controlling-crawlers/robots-txt) or [`robots`** meta tag**](https://nuxtseo.com/learn/controlling-crawlers/meta-tags) beyond this, as the [**controlling web crawlers**](https://nuxtseo.com/docs/robots/guides/robot-recipes/learn/controlling-crawlers) is an advanced use case and topic. However, if you're looking to get the best SEO and performance results, you may consider some of the recipes on this page for your site. h2. [Robots.txt recipes](#robotstxt-recipes) h3. [Blocking Bad Bots](#blocking-bad-bots) If you're finding your site is getting hit with a lot of bots, you may consider enabling the `blockNonSeoBots` option. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { blockNonSeoBots: true } }) ``` This will block mostly web scrapers, the full list is: `Nuclei`, `WikiDo`, `Riddler`, `PetalBot`, `Zoominfobot`, `Go-http-client`, `Node/simplecrawler`, `CazoodleBot`, `dotbot/1.0`, `Gigabot`, `Barkrowler`, `BLEXBot`, `magpie-crawler`. h3. [Blocking AI Crawlers](#blocking-ai-crawlers) AI crawlers can be beneficial as they can help users finding your site, but for some educational sites or those not interested in being indexed by AI crawlers, you can block them using the `blockAIBots` option. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { blockAiBots: true } }) ``` This will block the following AI crawlers: `GPTBot`, `ChatGPT-User`, `Claude-Web`, `anthropic-ai`, `Applebot-Extended`, `Bytespider`, `CCBot`, `cohere-ai`, `Diffbot`, `FacebookBot`, `Google-Extended`, `ImagesiftBot`, `PerplexityBot`, `OmigiliBot`, `Omigili` h3. [Blocking Privileged Pages](#blocking-privileged-pages) If you have pages that require authentication or are only available to certain users, you should block these from being indexed. public/_robots.txt ``` User-agent: * Disallow: /admin Disallow: /dashboard ``` See [**Config using Robots.txt**](https://nuxtseo.com/docs/robots/guides/robot-recipes/docs/robots/guides/robots-txt) for more information. h3. [Whitelisting Open Graph Tags](#whitelisting-open-graph-tags) If you have certain pages that you don't want indexed but you still want their [**Open Graph Tags**](https://nuxtseo.com/docs/robots/guides/robot-recipes/learn/mastering-meta/open-graph) to be crawled, you can target the specific user-agents. public/_robots.txt ``` h1. Block search engines User-agent: Googlebot User-agent: Bingbot Disallow: /user-profiles h1. Allow social crawlers User-agent: facebookexternalhit User-agent: Twitterbot Allow: /user-profiles ``` See [**Config using Robots.txt**](https://nuxtseo.com/docs/robots/guides/robot-recipes/docs/robots/guides/robots-txt) for more information. h3. [Blocking Search Results](#blocking-search-results) You may consider blocking search results from being indexed, as they can be seen as duplicate content and can be a poor user experience. public/_robots.txt ``` User-agent: * h1. block search results Disallow: /*?query= h1. block pagination Disallow: /*?page= h1. block sorting Disallow: /*?sort= h1. block filtering Disallow: /*?filter= ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/1.robot-recipes.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/robot-recipes/docs/robots/guides/robot-recipes.md) **Did this page help you? ** [**How Nuxt Robots Works** Learn more about how Nuxt Robots works.](https://nuxtseo.com/docs/robots/guides/robot-recipes/docs/robots/guides/how-it-works) [**Config using Robots.txt** Configure your generated robots.txt file with a robots.txt file.](https://nuxtseo.com/docs/robots/guides/robot-recipes/docs/robots/guides/robots-txt) **On this page** - [Introduction](#introduction) - [Robots.txt recipes](#robotstxt-recipes) --- ### Config using Robots.txt · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/robots-txt Description: Configure your generated robots.txt file with a robots.txt file. **Core Concepts** h1. **Config using Robots.txt** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#257) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/257). [Copy for LLMs h2. [Introduction](#introduction) The [**robots.txt standard**](https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt) is important for search engines to understand which pages to crawl and index on your site. New to robots.txt? Check out the [**Robots.txt Guide**](https://nuxtseo.com/docs/robots/guides/robots-txt/learn/controlling-crawlers/robots-txt) to learn more. To match closer to the robots standard, Nuxt Robots recommends configuring the module by using a `robots.txt`, which will be parsed, validated, configuring the module. If you need programmatic control, you can configure the module using [**nuxt.config.ts**](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/robots/guides/nuxt-config), [**Route Rules**](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/robots/guides/route-rules) and [**Nitro hooks**](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/robots/nitro-api/nitro-hooks). h2. [Creating a `robots.txt` file](#creating-a-robotstxt-file) You can place your file in any location; the easiest is to use: `<rootDir>/public/_robots.txt`. Additionally, the following paths are supported by default: Example File Structure ``` h1. root directory robots.txt h1. asset folders assets/ ├── robots.txt h1. pages folder pages/ ├── robots.txt ├── _dir/ │ └── robots.txt h1. public folder public/ ├── _robots.txt ├── _dir/ │ └── robots.txt ``` h3. [Custom paths](#custom-paths) If you find this too restrictive, you can use the `mergeWithRobotsTxtPath` config to load your `robots.txt` file from any path. ``` export default defineNuxtConfig({ robots: { mergeWithRobotsTxtPath: 'assets/custom/robots.txt' } }) ``` h2. [Parsed robots.txt](#parsed-robotstxt) The following rules are parsed from your `robots.txt` file: - `User-agent` - The user-agent to apply the rules to. - `Disallow` - An array of paths to disallow for the user-agent. - `Allow` - An array of paths to allow for the user-agent. - `Sitemap` - An array of sitemap URLs to include in the generated [**sitemap**](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/sitemap/getting-started/introduction). - `Content-Usage` / `Content-Signal` - Directives for expressing AI usage preferences (see [**AI Directives**](#ai-directives) below). This parsed data will be shown for environments that are `indexable`. h2. [AI Directives](#ai-directives) AI Directives allow you to control how AI systems, search engines, and automated tools interact with your content. Two standards are supported: - **Content-Usage** - IETF standard (`bots`, `train-ai`, `ai-output`, `search`) with `y`/`n` values - **Content-Signal** - Cloudflare implementation (`search`, `ai-input`, `ai-train`) with `yes`/`no` values h3. [Quick Example](#quick-example) robots.txt ``` User-agent: * Allow: / Content-Usage: bots=y, train-ai=n Content-Signal: ai-train=no, search=yes ``` See the [**AI Directives Guide**](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/robots/guides/ai-directives) for complete documentation, examples, validation rules, and best practices. [](https://nuxtseo.com/docs/robots/guides/robots-txt/tools/robots-txt-generator)**Validate your output** - Test your robots.txt rules with our [**Robots.txt Tester**](https://nuxtseo.com/docs/robots/guides/robots-txt/tools/robots-txt-generator). h2. [Conflicting `public/robots.txt`](#conflicting-publicrobotstxt) To ensure other modules can integrate with your generated robots file, you must not have a `robots.txt` file in your `public` folder. If you do, it will be moved to `<rootDir>/public/_robots.txt` and merged with the generated file. [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/1.robots-txt.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/robots/guides/robots-txt.md) **Did this page help you? ** [**Robots.txt Recipes** Common robots.txt patterns including blocking bad bots, AI crawlers, and search results.](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/robots/guides/robot-recipes) [**Config using Nuxt Config** Learn how to configure the module programmatically using nuxt.config.](https://nuxtseo.com/docs/robots/guides/robots-txt/docs/robots/guides/nuxt-config) **On this page** - [Introduction](#introduction) - [Creating a robots.txt file](#creating-a-robotstxt-file) - [Parsed robots.txt](#parsed-robotstxt) - [AI Directives](#ai-directives) - [Conflicting public/robots.txt](#conflicting-publicrobotstxt) --- ### Config using Nuxt Config · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/nuxt-config Description: Learn how to configure the module programmatically using nuxt.config. **Core Concepts** h1. **Config using Nuxt Config** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, broken links, and add robots.txt tool links (#256) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/256). [Copy for LLMs If you need programmatic control, you can configure the module using nuxt.config. h2. [Simple Configuration](#simple-configuration) The simplest configuration is to provide an array of paths to disallow for the `*` user-agent. If needed you can provide `allow` paths as well. - `disallow` - An array of paths to disallow for the `*` user-agent. - `allow` - An array of paths to allow for the `*` user-agent. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { // provide simple disallow rules for all robots \`user-agent: *\` disallow: ['/secret', '/admin'], allow: '/admin/login' } }) ``` This will generate the following output: robots.txt ``` User-agent: * Disallow: /secret Disallow: /admin Allow: /admin/login ``` h2. [Group Configuration](#group-configuration) When targeting specific robots, you can use the `groups` option to provide granular control. - `groups` - A stack of objects to provide granular control (see below). nuxt.config.ts ``` export default defineNuxtConfig({ // add more granular rules robots: { groups: [ // block specific robots from specific pages { userAgent: ['AdsBot-Google-Mobile', 'AdsBot-Google-Mobile-Apps'], disallow: ['/admin'], allow: ['/admin/login'], comments: 'Allow Google AdsBot to index the login page but no-admin pages' }, ] } }) ``` This will generate the following output: robots.txt ``` h1. Allow Google AdsBot to index the login page but no-admin pages User-agent: AdsBot-Google-Mobile User-agent: AdsBot-Google-Mobile-Apps Disallow: /admin Allow: /admin/login ``` h2. [AI Directives Configuration](#ai-directives-configuration) Configure AI usage preferences using `contentUsage` and `contentSignal`: ``` export default defineNuxtConfig({ robots: { groups: [ { userAgent: '*', allow: '/', contentUsage: { 'bots': 'y', 'train-ai': 'n' }, contentSignal: { 'ai-train': 'no', 'search': 'yes' } } ] } }) ``` ``` export default defineNuxtConfig({ robots: { groups: [ { userAgent: '*', allow: '/', contentUsage: ['bots=y, train-ai=n'], contentSignal: ['ai-train=no, search=yes'] } ] } }) ``` - **`contentUsage`** - IETF standard: `bots`, `train-ai`, `ai-output`, `search` with `y`/`n` - **`contentSignal`** - Cloudflare: `search`, `ai-input`, `ai-train` with `yes`/`no` **Object format** provides type safety and autocomplete. **String format** supports path-specific rules. See the [**AI Directives Guide**](https://nuxtseo.com/docs/robots/guides/nuxt-config/docs/robots/guides/ai-directives) for complete documentation. [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/2.nuxt-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/nuxt-config/docs/robots/guides/nuxt-config.md) **Did this page help you? ** [**Config using Robots.txt** Configure your generated robots.txt file with a robots.txt file.](https://nuxtseo.com/docs/robots/guides/nuxt-config/docs/robots/guides/robots-txt) [**Config Using Route Rules** Learn how to configure robots through route rules.](https://nuxtseo.com/docs/robots/guides/nuxt-config/docs/robots/guides/route-rules) **On this page** - [Simple Configuration](#simple-configuration) - [Group Configuration](#group-configuration) - [AI Directives Configuration](#ai-directives-configuration) --- ### Yandex: Clean-param · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/advanced/yandex Description: Learn how to use the `clean-param` directive to remove query parameters from URLs with Yandex. **Advanced** h1. **Yandex: Clean-param** Last updated **Dec 2, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clean up](https://github.com/nuxt-modules/robots/commit/859edb2b931d6aa4ee50b856f4df07a6446b7045). [Copy for LLMs Nuxt Robots is built around first-party robots.txt specifications from Google and Bing. Some users may want to configure Yandex, a popular search engine in Russia, and find that rules aren't working. To use Yandex you will need to provide alternative directives. h2. [Clean-param](#clean-param) The `clean-param` directive is used to remove query parameters from URLs. This is useful for SEO as it prevents duplicate content and consolidates page rank. It can either be configured directly through robots.txt when targeting Yandex or through the module configuration. h3. [Robots.txt](#robotstxt) To configure the `clean-param` directive in your `robots.txt` file, you can use the following syntax: robots.txt ``` User-agent: Yandex Clean-param: param1 param2 ``` This will remove the `param1` and `param2` query parameters from URLs. h3. [Module Configuration](#module-configuration) To configure the `clean-param` directive in your `nuxt.config.ts` file, you can use the following syntax: ``` export default defineNuxtConfig({ robots: { groups: [ { userAgent: ['Yandex'], cleanParam: ['param1', 'param2'] } ] } }) ``` h2. [Host & Crawl-delay](#host-crawl-delay) These directives are deprecated and should not be used. All search engines will ignore them. [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/3.advanced/1.yandex.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/advanced/yandex/docs/robots/advanced/yandex.md) **Did this page help you? ** [**AI Directives** Control how AI systems interact with your content using Content-Usage and Content-Signal directives.](https://nuxtseo.com/docs/robots/advanced/yandex/docs/robots/guides/ai-directives) [**Nuxt Content** How to use the Nuxt Robots module with Nuxt Content.](https://nuxtseo.com/docs/robots/advanced/yandex/docs/robots/advanced/content) **On this page** - [Clean-param](#clean-param) - [Host & Crawl-delay](#host-crawl-delay) --- ### Nitro Hooks · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/nitro-api/nitro-hooks Description: Learn how to use Nitro hooks to modify the robots final output. **Nitro API** h1. **Nitro Hooks** Last updated **Nov 1, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: refreshed doc](https://github.com/nuxt-modules/robots/commit/63fdd34124e5c9b1b2de8831be60d28598fce57a). [Copy for LLMs h2. [`'robots:config'`](#robotsconfig) **Type:** `(ctx: HookContext) => void | Promise<void>` ``` interface HookContext { groups: RobotsGroupResolved[] sitemaps: string[] context: 'robots.txt' | 'init' event?: H3Event // undefined on \`init\` } ``` Modify the robots config before it's used to generate the indexing rules. This is called when Nitro starts `init` as well as when generating the robots.txt `robots.txt`. server/plugins/robots-ignore-routes.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('robots:config', async (ctx) => { // extend the robot.txt rules at runtime if (ctx.context === 'init') { // probably want to cache this const ignoredRoutes = await $fetch('/api/ignored-routes') ctx.groups[0].disallow.push(...ignoredRoutes) } }) }) ``` h2. [`'robots:robots-txt'`](#robotsrobots-txt) **Type:** `(ctx: HookContext) => void | Promise<void>` ``` export interface HookRobotsTxtContext { robotsTxt: string e: H3Event } ``` This hook allows you to modify the robots.txt content before it is sent to the client. server/plugins/robots-remove-comments.ts ``` export default defineNitroPlugin((nitroApp) => { if (!process.dev) { nitroApp.hooks.hook('robots:robots-txt', async (ctx) => { // remove comments from robotsTxt in production ctx.robotsTxt = ctx.robotsTxt.replace(/^#.*$/gm, '').trim() }) } }) ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/3.nitro-api/2.nitro-hooks.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/nitro-api/nitro-hooks/docs/robots/nitro-api/nitro-hooks.md) **Did this page help you? ** [**getBotDetection()** Server-side composable to access bot detection state in Nitro routes and middleware.](https://nuxtseo.com/docs/robots/nitro-api/nitro-hooks/docs/robots/nitro-api/get-bot-detection) [**v5.0.0** Release notes for Nuxt Robots v5.0.0.](https://nuxtseo.com/docs/robots/nitro-api/nitro-hooks/docs/robots/releases/v5) **On this page** - ['robots:config'](#robotsconfig) - ['robots:robots-txt'](#robotsrobots-txt) --- ### Nuxt I18n · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/advanced/i18n Description: How to use the Nuxt Robots module with Nuxt I18n. **Advanced** h1. **Nuxt I18n** Last updated **Dec 2, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clean up](https://github.com/nuxt-modules/robots/commit/859edb2b931d6aa4ee50b856f4df07a6446b7045). [Copy for LLMs Out of the box, the robots module will integrate directly with [**@nuxtjs/i18n**](https://i18n.nuxtjs.org/). You will need to use v8+ of the i18n module. h2. [Auto-localised Allow / Disallow](#auto-localised-allow-disallow) The module will automatically localise the `allow` and `disallow` paths based on your i18n configuration. If you provide a `allow` or `disallow` path that is not localised, it will be localised for you, if your i18n configuration allows it. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { disallow: ['/secret', '/admin'], }, i18n: { locales: ['en', 'fr'], defaultLocale: 'en', strategy: 'prefix', } }) ``` This will generate the following output: robots.txt ``` User-agent: * Disallow: /en/secret Disallow: /en/admin Disallow: /fr/secret Disallow: /fr/admin ``` h2. [Opting-out of localisation](#opting-out-of-localisation) If you want to opt-out of localisation, there are two options: h3. [Opt-out for a group](#opt-out-for-a-group) You can provide the `_skipI18n` option to a group to disable localisation just for that group. ``` export default defineNuxtConfig({ robots: { groups: [ { disallow: [ '/docs/en/v*', '/docs/zh/v*', '/forum/admin/', '/forum/auth/', ], _skipI18n: true, }, ], }, }) ``` h3. [Opt-out i18n globally](#opt-out-i18n-globally) By providing the `autoI18n: false` option you will disable all i18n localisation splitting. ``` export default defineNuxtConfig({ robots: { autoI18n: false, } }) ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/3.advanced/3.i18n.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/advanced/i18n/docs/robots/advanced/i18n.md) **Did this page help you? ** [**Nuxt Content** How to use the Nuxt Robots module with Nuxt Content.](https://nuxtseo.com/docs/robots/advanced/i18n/docs/robots/advanced/content) [**useRobotsRule()** A reactive way to access and set the robots rule.](https://nuxtseo.com/docs/robots/advanced/i18n/docs/robots/api/use-robots-rule) **On this page** - [Auto-localised Allow / Disallow](#auto-localised-allow-disallow) - [Opting-out of localisation](#opting-out-of-localisation) --- ### Bot Detection · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/bot-detection Description: Detect and classify bots with server-side header analysis and client-side browser fingerprinting. **Core Concepts** h1. **Bot Detection** Last updated **Jul 1, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [feat: bot detection (#210)](https://github.com/nuxt-modules/robots/pull/210). [Copy for LLMs h2. [Introduction](#introduction) The modern web is full of [**bots**](https://nuxtseo.com/learn/controlling-crawlers). Start detecting them to better understand your traffic and optimize your Nuxt app for both human users and automated agents. Nuxt Robots provides bot detection that works on both server and client side, from simple heuristics using HTTP header `User-Agent` checks to advanced client-side detection using [**BotD**](https://github.com/fingerprintjs/BotD). h2. [Bot Categories](#bot-categories) The module classifies bots into categories based on their intended purpose and trustworthiness. **Trusted bots** are legitimate services that respect robots.txt and provide value to websites, while **untrusted bots** include automation tools, scrapers, and potentially malicious crawlers. h3. [Search Engine Bots (trusted)](#search-engine-bots-trusted) Search engines that index content for public search results and respect standard web protocols. - **Google**: `googlebot`, `google.com/bot.html` - **Bing**: `bingbot`, `msnbot` h3. [Social Media Bots (trusted)](#social-media-bots-trusted) Social platforms that crawl content for link previews, cards, and social sharing features. - **Facebook**: `facebookexternalhit`, `facebook.com` - **Twitter**: `twitterbot`, `twitter` h3. [SEO & Analytics Bots (trusted)](#seo-analytics-bots-trusted) Professional SEO and analytics services that provide legitimate website analysis and insights. - **Ahrefs**: `ahrefsbot`, `ahrefs.com` - **Majestic**: `mj12bot`, `majestic12.co.uk/bot` h3. [AI & ML Bots (trusted)](#ai-ml-bots-trusted) AI companies and research organizations training models or providing AI-powered services. - **OpenAI**: `gptbot`, `openai.com` - **Anthropic**: `anthropic` h3. [Automation Tools (untrusted)](#automation-tools-untrusted) Browser automation and testing frameworks that may be used for legitimate testing or malicious scraping. - **Selenium**: `selenium`, `webdriver` - **Playwright**: `playwright` h3. [HTTP Tools (untrusted)](#http-tools-untrusted) Command-line HTTP clients and programmatic request libraries often used for automated data extraction. - **cURL**: `curl` - **Python Requests**: `python-requests`, `python` h3. [Security Scanners (untrusted)](#security-scanners-untrusted) Network scanning and vulnerability assessment tools that may indicate malicious reconnaissance. - **Nmap**: `nmap`, `insecure.org` - **Nikto**: `nikto` h3. [Scraping Tools (untrusted)](#scraping-tools-untrusted) Dedicated web scraping frameworks designed for automated data collection. - **Scrapy**: `scrapy`, `scrapy.org` - **Generic Scraper**: `scraper` Missing a bot? Submit a quick PR :) [**View and contribute to bot definitions →**](https://github.com/nuxt-modules/robots/blob/main/src/const-bots.ts) h2. [Nitro Bot Detection](#nitro-bot-detection) Since server-side detection only uses HTTP headers, detection can only work for bots that correctly identify themselves in the `User-Agent` header. You can detect bots inside a Nitro route, middleware, or API handler. ``` import { getBotDetection } from '#robots/server/composables/getBotDetection' export default defineEventHandler((e) => { const detection = getBotDetection(e) if (detection.isBot) { return { message: \`${detection.botName} bot detected\`, category: detection.botCategory } } return { message: 'Human user' } }) ``` For full behavior, please consult the [`getBotDetection`](https://nuxtseo.com/docs/robots/guides/bot-detection/docs/robots/nitro-api/get-bot-detection) API docs. h2. [Nuxt Bot Detection](#nuxt-bot-detection) When using bot detection in Nuxt, it will use the `User-Agent` header by default. You can optionally use the [**BotD**](https://github.com/fingerprintjs/BotD) fingerprinting library to detect advanced automation tools by setting `fingerprint: true`. ``` <script setup lang="ts"> import { useBotDetection } from '#robots/app/composables/useBotDetection' const { isBot, botName, botCategory, trusted } = useBotDetection({ fingerprint: true, // detects using botd }) </script> <template> <div v-if="isBot"> Bot detected: {{ botName }} ({{ botCategory }}) </div> </template> ``` See the [`useBotDetection()`](https://nuxtseo.com/docs/robots/guides/bot-detection/docs/robots/api/use-bot-detection) API docs for full usage details. h2. [Fingerprinting with BotD](#fingerprinting-with-botd) When using `fingerprint: true`, the composable will load the [**BotD**](https://github.com/fingerprintjs/BotD) library when the window is idle and perform client-side fingerprinting to detect advanced bots and automation tools. h3. [Performance Considerations](#performance-considerations) This fingerprinting is computationally expensive for end users' CPUs, so you should be mindful of when you enable it. For example, you may consider only enabling it for sensitive pages where bot detection is critical. That said, the composable aims to be performant and will cache the bot result in the user's local storage under the `'__nuxt_robots:botd'` key so it will only run once. ``` localStorage.getItem('__nuxt_robots:botd') // returns the cached bot detection result - used internally already ``` h3. [Watching For Fingerprinting](#watching-for-fingerprinting) The properties returned from the composable are all `ref`s. It's important to watch these for changes if you're using fingerprinting, as the results will not be immediately available when the composable is called. ``` import { useBotDetection } from '#robots/app/composables/useBotDetection' import { watch } from 'vue' const { isBot } = useBotDetection({ fingerprint: true, }) watch(isBot, (detected) => { if (detected) { console.log(\`Bot detected!\`) } }) ``` Alternatively you can use the `onFingerprintResult` callback to handle the result when fingerprinting completes. ``` import { useBotDetection } from '#robots/app/composables/useBotDetection' const botd = useBotDetection({ fingerprint: true, onFingerprintResult(result) { // Fingerprinting completed console.log('Detection result:', result) }, }) ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/4.bot-detection.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/bot-detection/docs/robots/guides/bot-detection.md) **Did this page help you? ** [**Config Using Route Rules** Learn how to configure robots through route rules.](https://nuxtseo.com/docs/robots/guides/bot-detection/docs/robots/guides/route-rules) [**AI Directives** Control how AI systems interact with your content using Content-Usage and Content-Signal directives.](https://nuxtseo.com/docs/robots/guides/bot-detection/docs/robots/guides/ai-directives) **On this page** - [Introduction](#introduction) - [Bot Categories](#bot-categories) - [Nitro Bot Detection](#nitro-bot-detection) - [Nuxt Bot Detection](#nuxt-bot-detection) - [Fingerprinting with BotD](#fingerprinting-with-botd) --- ### Nuxt Content · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/advanced/content Description: How to use the Nuxt Robots module with Nuxt Content. **Advanced** h1. **Nuxt Content** Last updated **Dec 2, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clean up](https://github.com/nuxt-modules/robots/commit/859edb2b931d6aa4ee50b856f4df07a6446b7045). [Copy for LLMs h2. [Introduction](#introduction) Nuxt Robots comes with an integration for Nuxt Content that allows you to configure robots straight from your markdown directly. h2. [Setup Nuxt Content v3](#setup-nuxt-content-v3) In Nuxt Content v3 we need to use the `asRobotsCollection()` function to augment any collections to be able to use the `robots` frontmatter key. content.config.ts ``` import { defineCollection, defineContentConfig } from '@nuxt/content' import { asRobotsCollection } from '@nuxtjs/robots/content' export default defineContentConfig({ collections: { content: defineCollection( // adds the robots frontmatter key to the collection asRobotsCollection({ type: 'page', source: '**/*.md', }), ), }, }) ``` To ensure the tags actually gets rendered you need to ensure you're using the `useSeoMeta()` composable with `seo`. [...slug].vue ``` <script setup lang="ts"> import { queryCollection, useRoute } from '#imports' const route = useRoute() const { data: page } = await useAsyncData(\`page-${route.path}\`, () => { return queryCollection('content').path(route.path).first() }) // Ensure the SEO meta tags are rendered useSeoMeta(page.value?.seo || {}) </script> ``` Due to current Nuxt Content v3 limitations, you must load the robots module before the content module. ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/robots', '@nuxt/content' // <-- Must be after @nuxtjs/robots ] }) ``` h2. [Setup Nuxt Content v2](#setup-nuxt-content-v2) In Nuxt Content v2 markdown files require either [**Document Driven Mode**](https://content.nuxt.com/document-driven/introduction) or a `path` key to be set in the frontmatter. content/foo.md ``` --- path: /foo --- ``` h2. [Usage](#usage) You can use any boolean or string value as `robots` that will be forwarded as a [**Meta Robots Tag**](https://nuxtseo.com/docs/robots/advanced/content/learn/controlling-crawlers/meta-tags). ``` robots: false ``` ``` <meta name="robots" content="noindex, nofollow"> ``` h3. [Disabling Nuxt Content Integration](#disabling-nuxt-content-integration) If you need to disable the Nuxt Content integration, you can do so by setting the `disableNuxtContentIntegration` option in the module configuration. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { disableNuxtContentIntegration: true, } }) ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/3.advanced/3.content.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/advanced/content/docs/robots/advanced/content.md) **Did this page help you? ** [**Yandex: Clean-param** Learn how to use the `clean-param` directive to remove query parameters from URLs with Yandex.](https://nuxtseo.com/docs/robots/advanced/content/docs/robots/advanced/yandex) [**Nuxt I18n** How to use the Nuxt Robots module with Nuxt I18n.](https://nuxtseo.com/docs/robots/advanced/content/docs/robots/advanced/i18n) **On this page** - [Introduction](#introduction) - [Setup Nuxt Content v3](#setup-nuxt-content-v3) - [Setup Nuxt Content v2](#setup-nuxt-content-v2) - [Usage](#usage) --- ### AI Directives · Nuxt Robots · Nuxt SEO Source: https://nuxtseo.com/docs/robots/guides/ai-directives Description: Control how AI systems interact with your content using Content-Usage and Content-Signal directives. **Core Concepts** h1. **AI Directives** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, broken links, and add robots.txt tool links (#256) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/robots/pull/256). [Copy for LLMs AI Directives allow you to express preferences about how AI systems, search engines, and automated tools should interact with your content. Two standards are supported: - **[**Content-Usage**](https://ietf-wg-aipref.github.io/drafts/draft-ietf-aipref-vocab.html)** - IETF standard with broader automation categories - **[**Content-Signal**](https://contentsignals.org/)** - Cloudflare's widely-deployed implementation focused on AI use cases Both can be used together in your robots.txt file and are enforced through the robots.txt protocol. [](https://nuxtseo.com/docs/robots/guides/ai-directives/tools/robots-txt-generator)**Test AI crawler blocking** - Our [**Robots.txt Generator**](https://nuxtseo.com/docs/robots/guides/ai-directives/tools/robots-txt-generator) includes presets for GPTBot, ClaudeBot, and other AI crawlers. **Important:** AI directives rely on voluntary compliance by crawlers and AI systems. They are not enforced by web servers and should be combined with other protection methods for sensitive content. h2. [Content-Usage (IETF aipref-vocab)](#content-usage-ietf-aipref-vocab) The Content-Usage directive follows the [**IETF AI Preferences specification**](https://ietf-wg-aipref.github.io/drafts/draft-ietf-aipref-vocab.html), providing a standardized way to express automation preferences. h3. [Categories](#categories) | **Category** | **Description** | **Example Use Case** | | --- | --- | --- | | `train-ai` | Foundation Model Production | Training large language models | h3. [Values](#values) - `y` - Allow this category of use - `n` - Disallow this category of use h3. [Syntax](#syntax) robots.txt ``` User-agent: * Content-Usage: <category>=<value>[, <category>=<value>] Content-Usage: /path/ <category>=<value>[, <category>=<value>] ``` h3. [Examples](#examples) h4. [Block AI Training Globally](#block-ai-training-globally) robots.txt ``` User-agent: * Allow: / Content-Usage: train-ai=n ``` h4. [Allow Bots, Block AI Training](#allow-bots-block-ai-training) robots.txt ``` User-agent: * Allow: / Content-Usage: bots=y, train-ai=n ``` h4. [Path-Specific Rules](#path-specific-rules) robots.txt ``` User-agent: * Allow: / Content-Usage: train-ai=n Content-Usage: /docs/ train-ai=y Content-Usage: /api/ train-ai=n ``` h3. [Programmatic Configuration](#programmatic-configuration) **Object Format (Recommended)** - Type-safe with autocomplete: nuxt.config.ts ``` export default defineNuxtConfig({ robots: { groups: [ { userAgent: '*', allow: '/', contentUsage: { 'train-ai': 'n' } } ] } }) ``` h2. [Content-Signal (Cloudflare/IETF aipref-contentsignals)](#content-signal-cloudflareietf-aipref-contentsignals) Content-Signal is [**Cloudflare's implementation**](https://blog.cloudflare.com/content-signals-policy/) based on [**IETF aipref-contentsignals**](https://www.ietf.org/archive/id/draft-romm-aipref-contentsignals-00.html). h3. [Categories](#categories-1) | **Category** | **Description** | **Example Use Case** | | --- | --- | --- | | `search` | Search Applications | Indexing for search results and snippets | | `ai-input` | AI Input | RAG, grounding, generative AI search answers | | `ai-train` | AI Training | Training or fine-tuning AI models | h3. [Values](#values-1) - `yes` - Allow this category of use - `no` - Disallow this category of use h3. [Syntax](#syntax-1) robots.txt ``` User-agent: * Content-Signal: <category>=<value>[, <category>=<value>] Content-Signal: /path/ <category>=<value>[, <category>=<value>] ``` h3. [Examples](#examples-1) h4. [Block AI Training, Allow Search](#block-ai-training-allow-search) robots.txt ``` User-agent: * Allow: / Content-Signal: ai-train=no, search=yes ``` h4. [Block All AI Usage](#block-all-ai-usage) robots.txt ``` User-agent: * Allow: / Content-Signal: ai-train=no, ai-input=no, search=yes ``` h4. [Path-Specific Rules](#path-specific-rules-1) robots.txt ``` User-agent: * Allow: / Content-Signal: ai-train=no, search=yes Content-Signal: /docs/ ai-input=yes Content-Signal: /api/ ai-train=no, ai-input=no, search=no ``` h3. [Programmatic Configuration](#programmatic-configuration-1) **Object Format (Recommended)** - Type-safe with autocomplete: nuxt.config.ts ``` export default defineNuxtConfig({ robots: { groups: [ { userAgent: '*', allow: '/', contentSignal: { 'ai-train': 'no', 'search': 'yes' } } ] } }) ``` h2. [Using Both Together](#using-both-together) You can use both Content-Usage and Content-Signal in the same robots.txt for comprehensive coverage: robots.txt ``` User-agent: * Allow: / Content-Usage: bots=y, train-ai=n Content-Signal: ai-train=no, search=yes ``` ``` export default defineNuxtConfig({ robots: { groups: [ { userAgent: '*', allow: '/', contentUsage: { 'train-ai': 'n' }, contentSignal: { 'ai-train': 'no', 'search': 'yes' } } ] } }) ``` ``` export default defineNuxtConfig({ robots: { groups: [ { userAgent: '*', allow: '/', contentUsage: ['bots=y, train-ai=n'], contentSignal: ['ai-train=no, search=yes'] } ] } }) ``` h2. [Examples](#examples-2) h3. [Block All AI Training](#block-all-ai-training) ``` User-agent: * Allow: / Content-Usage: train-ai=n ``` ``` User-agent: * Allow: / Content-Signal: ai-train=no ``` h3. [Documentation-Only Training](#documentation-only-training) ``` User-agent: * Allow: / Content-Usage: train-ai=n Content-Usage: /docs/ train-ai=y ``` ``` User-agent: * Allow: / Content-Signal: ai-train=no Content-Signal: /docs/ ai-train=yes ``` [Edit this page](https://github.com/nuxt-modules/robots/edit/main/docs/content/2.guides/5.ai-directives.md) [Markdown For LLMs](https://nuxtseo.com/docs/robots/guides/ai-directives/docs/robots/guides/ai-directives.md) **Did this page help you? ** [**Bot Detection** Detect and classify bots with server-side header analysis and client-side browser fingerprinting.](https://nuxtseo.com/docs/robots/guides/ai-directives/docs/robots/guides/bot-detection) [**Yandex: Clean-param** Learn how to use the `clean-param` directive to remove query parameters from URLs with Yandex.](https://nuxtseo.com/docs/robots/guides/ai-directives/docs/robots/advanced/yandex) **On this page** - [Content-Usage (IETF aipref-vocab)](#content-usage-ietf-aipref-vocab) - [Content-Signal (Cloudflare/IETF aipref-contentsignals)](#content-signal-cloudflareietf-aipref-contentsignals) - [Using Both Together](#using-both-together) - [Examples](#examples-2) --- ### Nuxt Sitemap v7.0.0 · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/releases/v7 Description: Release notes for v7.0.0 of Nuxt Sitemap. **Releases** h1. **Nuxt Sitemap v7.0.0** Last updated **Dec 4, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clean up](https://github.com/nuxt-modules/sitemap/commit/7c6389e031611b40655a3460d62856af915eec62). [Copy for LLMs h2. [Introduction](#introduction) The v7 major of Nuxt Sitemap is a simple release to remove deprecations and add support for the [**Nuxt SEO v2 stable**](https://nuxtseo.com/docs/sitemap/releases/v7/announcement). h2. [Breaking Features](#breaking-features) h3. [Site Config v3](#site-config-v3) Nuxt Site Config is a module used internally by Nuxt Sitemap. The major update to v3.0.0 shouldn't have any direct effect on your site, however, you may want to double-check the [**breaking changes**](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v3.0.0). h3. [Removed `inferStaticPagesAsRoutes` config](#removed-inferstaticpagesasroutes-config) If you set this value to `false` previously, you will need to change it to the below: ``` export default defineNuxtConfig({ sitemap: { - inferStaticPagesAsRoutes: false, + excludeAppSources: ['pages', 'route-rules', 'prerender'] } }) ``` h3. [Removed `dynamicUrlsApiEndpoint` config](#removed-dynamicurlsapiendpoint-config) The `sources` config supports multiple API endpoints and allows you to provide custom fetch options, use this instead. ``` export default defineNuxtConfig({ sitemap: { - dynamicUrlsApiEndpoint: '/__sitemap/urls', + sources: ['/__sitemap/urls'] } }) ``` h3. [Removed `cacheTtl` config](#removed-cachettl-config) Please use the `cacheMaxAgeSeconds` as its a clearer config. ``` export default defineNuxtConfig({ sitemap: { - cacheTtl: 10000, + cacheMaxAgeSeconds: 10000 } }) ``` h3. [Removed `index` route rule / Nuxt Content support](#removed-index-route-rule-nuxt-content-support) If you were using the `index: false` in either route rules or your Nuxt Content markdown files, you will need to update this to use the `robots` key. ``` export default defineNuxtConfig({ routeRules: { // use the \`index\` shortcut for simple rules - '/secret/**': { index: false }, + '/secret/**': { robots: false }, } }) ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/5.releases/4.v7.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/releases/v7/docs/sitemap/releases/v7.md) **Did this page help you? ** [**Nitro Hooks** Learn how to use Nitro Hooks to customize your sitemap entries.](https://nuxtseo.com/docs/sitemap/releases/v7/docs/sitemap/nitro-api/nitro-hooks) [**v6.0.0** Release notes for v6.0.0 of Nuxt Sitemap.](https://nuxtseo.com/docs/sitemap/releases/v7/docs/sitemap/releases/v6) **On this page** - [Introduction](#introduction) - [Breaking Features](#breaking-features) --- ### Dynamic URL Endpoints · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/dynamic-urls Description: Use runtime API endpoints to generate dynamic URLs for your sitemap. **Core Concepts** h1. **Dynamic URL Endpoints** Last updated **Dec 17, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [feat: mark entries as pre-encoded `_encoded: true` Fixes #473](https://github.com/nuxt-modules/sitemap/commit/b1b177783a6d96d6ebffff97e6ed422ac4d5bba3). [Copy for LLMs h2. [Introduction](#introduction) When working with a CMS or external data sources, you may need to generate sitemap URLs dynamically at runtime. The module supports two types of data sources: - JSON responses from API endpoints - XML sitemaps from external sources h2. [Using External XML Sitemaps](#using-external-xml-sitemaps) If you have an existing XML sitemap, you can reference it directly in your configuration: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { sources: [ 'https://example.com/sitemap.xml', ] } }) ``` h2. [Dynamic URLs from External APIs](#dynamic-urls-from-external-apis) When fetching dynamic URLs from external APIs, you have two main approaches: 1. **Direct source configuration** - Use when the API returns data in the correct format 2. **Custom API endpoint** - Use when you need to transform data or implement caching h3. [1. Using Source Configuration](#_1-using-source-configuration) For APIs that require authentication or custom headers, provide sources as an array with fetch options: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { sources: [ // Unauthenticated endpoint 'https://api.example.com/pages/urls', // Authenticated endpoint [ 'https://authenticated-api.example.com/pages/urls', { headers: { Authorization: 'Bearer <token>' } } ] ] } }) ``` h3. [2. Creating Custom Endpoints](#_2-creating-custom-endpoints) **Step 1: Create the API endpoint** Use the [`defineSitemapEventHandler()`](https://nuxtseo.com/docs/sitemap/guides/dynamic-urls/docs/sitemap/nitro-api/nitro-hooks) helper to create type-safe sitemap endpoints: ``` // server/api/__sitemap__/urls.ts import { defineSitemapEventHandler } from '#imports' import type { SitemapUrlInput } from '#sitemap/types' export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // Specify which sitemap this URL belongs to _sitemap: 'pages', }, ] satisfies SitemapUrlInput[] }) ``` ``` // server/api/__sitemap__/urls.ts import { defineSitemapEventHandler } from '#imports' import type { SitemapUrl } from '#sitemap/types' export default defineSitemapEventHandler(async () => { const [posts, pages] = await Promise.all([ $fetch<{ path: string, slug: string }[]>('https://api.example.com/posts') .then(posts => posts.map(p => ({ loc: \`/blog/${p.slug}\`, // Transform to your domain structure _sitemap: 'posts', } satisfies SitemapUrl))), $fetch<{ path: string }[]>('https://api.example.com/pages') .then(pages => pages.map(p => ({ loc: p.path, _sitemap: 'pages', } satisfies SitemapUrl))), ]) return [...posts, ...pages] }) ``` ``` // server/api/__sitemap__/wordpress.ts import { defineSitemapEventHandler } from '#imports' export default defineSitemapEventHandler(async () => { const posts = await $fetch('https://api.externalwebsite.com/wp-json/wp/v2/posts') return posts.map(post => ({ // Transform external URL to your domain loc: \`/blog/${post.slug}\`, // NOT post.link lastmod: post.modified, changefreq: 'weekly', priority: 0.7, })) }) ``` ``` // server/api/__sitemap__/urls.ts import { defineSitemapEventHandler } from '#imports' import type { SitemapUrl } from '#sitemap/types' export default defineSitemapEventHandler(async () => { const config = useRuntimeConfig() const baseUrl = config.public.siteUrl const locales = config.public.i18n.locales.map(locale => locale.code) const isoLocales = Object.fromEntries( config.public.i18n.locales.map(locale => ([locale.code, locale.iso])) ) // Example: Fetch data for each locale const apiQueries = locales.map(locale => $fetch(\`${config.public.apiEndpoint}/sitemap/${locale}/products\`) ) const sitemaps = await Promise.all(apiQueries) return sitemaps.flat().map(entry => ({ // explicit sitemap mapping _sitemap: isoLocales[entry.locale], loc: \`${baseUrl}/${entry.locale}/product/${entry.url}\`, alternatives: entry.alternates?.map(alt => ({ hreflang: isoLocales[alt.locale], href: \`${baseUrl}/${alt.locale}/product/${alt.url}\` })) } satisfies SitemapUrl)) }) ``` **Step 2: Configure the endpoint** Add your custom endpoint to the sitemap configuration: ``` export default defineNuxtConfig({ sitemap: { sources: [ '/api/__sitemap__/urls', ] } }) ``` ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { sources: [ '/api/__sitemap__/urls/posts', ] }, pages: { sources: [ '/api/__sitemap__/urls/pages', ] } } } }) ``` h2. [Handling Pre-Encoded URLs](#handling-pre-encoded-urls) By default, the module automatically encodes URL paths. This handles special characters like spaces and unicode (e.g., emojis, accented characters). If your API or CMS returns URLs that are already encoded, mark them with `_encoded: true` to prevent double-encoding. server/api/__sitemap__/urls.ts ``` import { defineSitemapEventHandler } from '#imports' export default defineSitemapEventHandler(async () => { // URLs from your API are already encoded const urls = await $fetch<{ path: string }[]>('https://api.example.com/pages') // e.g. [{ path: '/products/%24pecial-offer' }, { path: '/blog/%F0%9F%98%85' }] return urls.map(url => ({ loc: url.path, _encoded: true, })) }) ``` When `_encoded: true` is set, the module skips automatic encoding entirely. Make sure your URLs are properly encoded. [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/0.dynamic-urls.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/dynamic-urls/docs/sitemap/guides/dynamic-urls.md) **Did this page help you? ** h3. **Related ** [**Data Sources**](https://nuxtseo.com/docs/sitemap/guides/dynamic-urls/docs/sitemap/getting-started/data-sources) [**I18n Integration**](https://nuxtseo.com/docs/sitemap/guides/dynamic-urls/docs/sitemap/guides/i18n) [**Multi Sitemaps**](https://nuxtseo.com/docs/sitemap/guides/dynamic-urls/docs/sitemap/guides/multi-sitemaps) [**Troubleshooting** Common issues and debugging tips for Nuxt Sitemap.](https://nuxtseo.com/docs/sitemap/guides/dynamic-urls/docs/sitemap/getting-started/troubleshooting) [**Disabling Indexing** How to filter the URLs generated from application sources.](https://nuxtseo.com/docs/sitemap/guides/dynamic-urls/docs/sitemap/guides/filtering-urls) **On this page** - [Introduction](#introduction) - [Using External XML Sitemaps](#using-external-xml-sitemaps) - [Dynamic URLs from External APIs](#dynamic-urls-from-external-apis) - [Handling Pre-Encoded URLs](#handling-pre-encoded-urls) --- ### Multi Sitemaps · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps Description: Generate multiple sitemaps for different sections of your site. **Core Concepts** h1. **Multi Sitemaps** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: overhaul](https://github.com/nuxt-modules/sitemap/commit/25a370e521eb226799ba9eb3c4253731b91c1c1a). [Copy for LLMs h2. [Introduction](#introduction) By default, the module generates a single `/sitemap.xml` file, which works perfectly for most websites. For larger sites with thousands of URLs, multiple sitemaps offer several benefits: - Easier debugging and management - More efficient search engine crawling - Better organization of content types h2. [Enabling Multiple Sitemaps](#enabling-multiple-sitemaps) You can enable multiple sitemaps using the `sitemaps` option in two ways: 1. **Manual Chunking** (`object`): Best for sites with clear content types (pages, posts, etc) or fewer than 1000 URLs 2. **Automatic Chunking** (`true`): Best for sites with more than 1000 URLs without clear content types ``` export default defineNuxtConfig({ sitemap: { // manually chunk into multiple sitemaps sitemaps: { posts: { include: [ '/blog/**', ], // example: give blog posts slightly higher priority (this is optional) defaults: { priority: 0.7 }, }, pages: { exclude: [ '/blog/**', ] }, }, }, }) ``` ``` export default defineNuxtConfig({ sitemap: { sitemaps: true, // modify the chunk size if you need defaultSitemapsChunkSize: 2000 // default 1000 }, }) ``` h3. [Customizing Sitemap URLs](#customizing-sitemap-urls) By default, all multi-sitemaps are served under the `/__sitemap__/` prefix. You can customize this behavior to create cleaner URLs: ``` export default defineNuxtConfig({ sitemap: { sitemapsPathPrefix: '/', // or false sitemaps: { // will be available at /sitemap-foo.xml ['sitemap-foo']: { // ... } } } }) ``` h2. [Manual Chunking](#manual-chunking) Manual chunking gives you complete control over how your URLs are distributed across sitemaps. This approach is ideal when you have distinct content types or specific organizational needs. h3. [Setting Default Values](#setting-default-values) You can provide default values for URLs within each sitemap using the `defaults` option: ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { // posts low priority defaults: { priority: 0.7 }, }, }, }, }) ``` h3. [Extending App Sources](#extending-app-sources) When you already have all URLs in your single sitemap but want to split them into separate sitemaps, you can extend existing [**app sources**](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/getting-started/data-sources) and apply filters. Available options: - `includeAppSources`: Include URLs from automatic app sources - `includeGlobalSources`: Include URLs from global sources - `include`: Array of glob patterns to include - `exclude`: Array of glob patterns to exclude nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { sitemaps: { pages: { // extend the nuxt:pages app source includeAppSources: true, // filter the URLs to only include pages exclude: ['/blog/**'], }, posts: { // extend the nuxt:pages app source includeAppSources: true, // filter the URLs to only include pages include: ['/blog/**'], }, }, }, }) ``` h4. [Using the `_sitemap` Key](#using-the-_sitemap-key) When using global sources and need to direct specific URLs to particular sitemaps, use the `_sitemap` key: ``` export default defineNuxtConfig({ sitemap: { sources: [ '/api/sitemap-urls' ], sitemaps: { pages: { includeGlobalSources: true, includeAppSources: true, exclude: ['/**'] // ... }, }, }, }) ``` ``` export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // will end up in the pages sitemap _sitemap: 'pages', } ] }) ``` h3. [Managing Custom Sources](#managing-custom-sources) For sitemaps that need to fetch URLs from endpoints, you have two options: - `urls`: Static URLs to include in the sitemap (avoid for large URL sets) - `sources`: Endpoints to fetch [**dynamic URLs**](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/guides/dynamic-urls) from (JSON or XML) ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { urls() { // resolved when the sitemap is shown return ['/foo', '/bar'] }, sources: [ '/api/sitemap-urls' ] }, }, }, }) ``` h3. [Chunking Large Sources](#chunking-large-sources) When you have sources that return a large number of URLs, you can enable chunking to split them into multiple XML files: ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { sources: ['/api/posts'], // returns 10,000 posts chunks: true, // Enable chunking with default size (1000) }, products: { sources: ['/api/products'], // returns 50,000 products chunks: 5000, // Chunk into files with 5000 URLs each }, articles: { sources: ['/api/articles'], chunks: true, chunkSize: 2000, // Alternative way to specify chunk size } } }, }) ``` This will generate: - `/sitemap_index.xml` - Lists all sitemaps including chunks - `/posts-0.xml` - First 1000 posts - `/posts-1.xml` - Next 1000 posts - `/products-0.xml` - First 5000 products - `/products-1.xml` - Next 5000 products - etc. h3. [Linking External Sitemaps](#linking-external-sitemaps) Use the special `index` key to add external sitemaps to your sitemap index: ``` export default defineNuxtConfig({ sitemaps: { // generated sitemaps posts: { // ... }, pages: { // ... }, // extending the index sitemap with an external sitemap index: [ { sitemap: 'https://www.google.com/sitemap-pages.xml' } ] } }) ``` h2. [Automatic Chunking](#automatic-chunking) Automatic chunking divides your sitemap into multiple files based on URL count. This feature: - Uses numbered naming convention (`0-sitemap.xml`, `1-sitemap.xml`, etc.) - Chunks based on `defaultSitemapsChunkSize` (default: 1000 URLs per sitemap) - Should be avoided for sites with fewer than 1000 URLs ``` export default defineNuxtConfig({ sitemap: { // automatically chunk into multiple sitemaps sitemaps: true, // optionally customize chunk size defaultSitemapsChunkSize: 2000 // default: 1000 }, }) ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/2.multi-sitemaps.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/guides/multi-sitemaps.md) **Did this page help you? ** h3. **Related ** [**Data Sources**](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/getting-started/data-sources) [**Dynamic URL Endpoints**](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/guides/dynamic-urls) [**Sitemap Chunking**](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/advanced/chunking-sources) [**Sitemap Performance**](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/advanced/performance) [**Disabling Indexing** How to filter the URLs generated from application sources.](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/guides/filtering-urls) [**I18n** Setting up a sitemap with Nuxt I18n and Nuxt I18n Micro.](https://nuxtseo.com/docs/sitemap/guides/multi-sitemaps/docs/sitemap/guides/i18n) **On this page** - [Introduction](#introduction) - [Enabling Multiple Sitemaps](#enabling-multiple-sitemaps) - [Manual Chunking](#manual-chunking) - [Automatic Chunking](#automatic-chunking) --- ### Data Sources · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/getting-started/data-sources Description: Understand where your sitemap URLs come from. **Getting Started** h1. **Data Sources** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#540) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/540). [Copy for LLMs h2. [Where do sitemap URLs come from?](#where-do-sitemap-urls-come-from) After installing the module, you may wonder: where do the URLs in your sitemap come from? Every URL belongs to a **source**. There are two types: - **Application Sources** - Automatically discovered from your Nuxt app - **User Sources** - Manually provided by you For most sites, application sources handle everything automatically. You only need user sources when you have dynamic routes from a CMS or database. h2. [Application Sources](#application-sources) Application sources are automatically generated from your Nuxt application. They provide convenience by automatically discovering URLs from your app's structure, but can be disabled if they don't match your needs. - `nuxt:pages` - Statically analysed pages of your application - `nuxt:prerender` - URLs that were prerendered - `nuxt:route-rules` - URLs from your route rules - `@nuxtjs/i18n:pages` - When using the `pages` config with Nuxt I18n. See [**Nuxt I18n**](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/guides/i18n) for more details. - `@nuxt/content:document-driven` - When using Document Driven mode. See [**Nuxt Content**](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/guides/content) for more details. h3. [Disabling Application Sources](#disabling-application-sources) You can disable application sources individually or all at once using the `excludeAppSources` config option. ``` export default defineNuxtConfig({ sitemap: { // exclude all app sources excludeAppSources: true, } }) ``` ``` export default defineNuxtConfig({ sitemap: { // exclude static pages excludeAppSources: ['nuxt:pages'], } }) ``` h2. [User Sources](#user-sources) User sources allow you to manually configure where your sitemap URLs come from. These are especially useful for dynamic routes that aren't using [**prerendering discovery**](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/guides/prerendering). You have several options for providing user sources: h3. [1. Build-time Sources with `urls` Function](#_1-build-time-sources-with-urls-function) For sitemap data that only needs to be updated at build time, the `urls` function is the simplest solution. This function runs once during sitemap generation. nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { urls: async () => { // fetch your URLs from a database or other source const urls = await fetch('https://example.com/api/urls') return urls } } }) ``` h3. [2. Runtime Sources with `sources` Array](#_2-runtime-sources-with-sources-array) For sitemap data that must always be up-to-date at runtime, use the `sources` array. Each source is a URL that gets fetched and should return either: - JSON array of sitemap URL entries - XML sitemap document ``` export default defineNuxtConfig({ sitemap: { sources: [ // create our own API endpoints '/api/__sitemap__/urls', // use a static remote file 'https://cdn.example.com/my-urls.json', // hit a remote API with credentials ['https://api.example.com/pages/urls', { headers: { Authorization: 'Bearer <token>' } }] ] } }) ``` ``` export default defineNuxtConfig({ sitemap: { sitemaps: { foo: { sources: [ '/api/__sitemap__/urls/foo', ] }, bar: { sources: [ '/api/__sitemap__/urls/bar', ] } } } }) ``` You can provide multiple sources, but consider implementing your own caching strategy for performance. Learn more about working with dynamic data in the [**Dynamic URLs**](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/guides/dynamic-urls) guide. h3. [3. Dynamic Sources Using Nitro Hooks](#_3-dynamic-sources-using-nitro-hooks) For advanced use cases like forwarding authentication headers or adding sources based on request context, see the [**Nitro Hooks documentation**](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/nitro-api/nitro-hooks#sitemap-sources). [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/0.getting-started/2.data-sources.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/getting-started/data-sources.md) **Did this page help you? ** [**Installation** Get started with Nuxt Sitemap by installing the dependency to your project.](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/getting-started/installation) [**Troubleshooting** Common issues and debugging tips for Nuxt Sitemap.](https://nuxtseo.com/docs/sitemap/getting-started/data-sources/docs/sitemap/getting-started/troubleshooting) **On this page** - [Where do sitemap URLs come from?](#where-do-sitemap-urls-come-from) - [Application Sources](#application-sources) - [User Sources](#user-sources) --- ### I18n · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/i18n Description: Setting up a sitemap with Nuxt I18n and Nuxt I18n Micro. **Core Concepts** h1. **I18n** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: overhaul](https://github.com/nuxt-modules/sitemap/commit/25a370e521eb226799ba9eb3c4253731b91c1c1a). [Copy for LLMs h2. [Introduction](#introduction) The sitemap module automatically integrates with [**@nuxtjs/i18n**](https://i18n.nuxtjs.org/) and [**nuxt-i18n-micro**](https://github.com/s00d/nuxt-i18n-micro) without any extra configuration. While the integration works out of the box, you may need to fine-tune some options depending on your i18n setup. h2. [I18n Modes](#i18n-modes) The module supports two main modes for handling internationalized sitemaps: h3. [Automatic I18n Multi Sitemap](#automatic-i18n-multi-sitemap) The module automatically generates a sitemap for each locale when: - You're not using the `no_prefix` strategy - Or you're using [**Different Domains**](https://i18n.nuxtjs.org/docs/v7/different-domains) - And you haven't manually configured the `sitemaps` option This generates the following structure: ``` ./sitemap_index.xml ./en-sitemap.xml ./fr-sitemap.xml h1. ...additional locales ``` Key features: - Includes [**app sources**](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/getting-started/data-sources) automatically - The `nuxt:pages` source determines the correct `alternatives` for your pages - To disable app sources, set `excludeAppSources: true` h3. [I18n Pages Mode](#i18n-pages-mode) When you enable `i18n.pages` in your i18n configuration, the sitemap module generates a single sitemap using that configuration. Key differences: - Does not include [**app sources**](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/getting-started/data-sources) automatically - You can add additional URLs using the `sources` option h2. [Dynamic URLs with i18n](#dynamic-urls-with-i18n) By default, dynamic URLs you provide won't have i18n data and will only appear in the default locale sitemap. To handle i18n for dynamic URLs, use these special options: h3. [1. `_i18nTransform` - Automatic Locale Transformation](#_1-_i18ntransform-automatic-locale-transformation) Use `_i18nTransform: true` to automatically generate URLs for all locales: server/api/__sitemap__/urls.ts ``` export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // automatically creates: /en/about-us, /fr/about-us, etc. _i18nTransform: true, } ] }) ``` h4. [Custom Path Translations](#custom-path-translations) If you have custom path translations defined in your i18n configuration using `pages`, the `_i18nTransform` option will automatically use them: nuxt.config.ts ``` export default defineNuxtConfig({ i18n: { pages: { 'about': { en: '/about', fr: '/a-propos', es: '/acerca-de', }, 'services': { en: '/services', fr: '/offres', es: '/servicios', }, }, }, }) ``` With this configuration, when you set `_i18nTransform: true` on a URL: server/api/__sitemap__/urls.ts ``` export default defineSitemapEventHandler(() => { return [ { loc: '/about', // base path _i18nTransform: true, // automatically generates: // - /about (for en) // - /fr/a-propos (for fr) // - /es/acerca-de (for es) } ] }) ``` h3. [2. `_sitemap` - Specific Locale Assignment](#_2-_sitemap-specific-locale-assignment) Use `_sitemap` to assign a URL to a specific locale sitemap: server/api/__sitemap__/urls.ts ``` export default defineSitemapEventHandler(() => { return [ { loc: '/about-us', // only appears in the English sitemap _sitemap: 'en', } ] }) ``` h2. [Debugging Hreflang](#debugging-hreflang) By default, hreflang tags aren't visible in the XML stylesheet view. To see them, you'll need to view the page source. Note: Search engines can still see these tags even if they're not visible in the stylesheet. To display hreflang tag counts in the visual interface, customize the columns: ``` export default defineNuxtConfig({ sitemap: { xslColumns: [ { label: 'URL', width: '50%' }, { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' }, { label: 'Hreflangs', select: 'count(xhtml)', width: '25%' }, ], } }) ``` For more customization options, see the [**Customising UI guide**](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/advanced/customising-ui). [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/3.i18n.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/guides/i18n.md) **Did this page help you? ** h3. **Related ** [**Data Sources**](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/getting-started/data-sources) [**Dynamic URL Endpoints**](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/guides/dynamic-urls) [**Customising the UI**](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/advanced/customising-ui) [**Multi Sitemaps** Generate multiple sitemaps for different sections of your site.](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/guides/multi-sitemaps) [**Nuxt Content** How to use the Nuxt Sitemap module with Nuxt Content.](https://nuxtseo.com/docs/sitemap/guides/i18n/docs/sitemap/guides/content) **On this page** - [Introduction](#introduction) - [I18n Modes](#i18n-modes) - [Dynamic URLs with i18n](#dynamic-urls-with-i18n) - [Debugging Hreflang](#debugging-hreflang) --- ### Install Nuxt Sitemap · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/getting-started/installation Description: Get started with Nuxt Sitemap by installing the dependency to your project. **Getting Started** h1. **Install Nuxt Sitemap** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#540) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/540). [Copy for LLMs h2. [Setup Module](#setup-module) Want to know why you might need this module? Check out the [**introduction**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/getting-started/introduction). To get started with Nuxt Sitemap, you need to install the dependency and add it to your Nuxt config. `npx nuxt module add @nuxtjs/sitemap` `npm i @nuxtjs/sitemap` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/sitemap', ], }) ``` `yarn add @nuxtjs/sitemap` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/sitemap', ], }) ``` `pnpm i @nuxtjs/sitemap` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/sitemap', ], }) ``` `bun i @nuxtjs/sitemap` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/sitemap', ], }) ``` h2. [Verifying Installation](#verifying-installation) After you've set up the module with the minimal config, you should be able to visit [`/sitemap.xml`](http://localhost:3000/sitemap.xml) to see the generated sitemap. You may notice that the URLs point to your `localhost` domain, this is to make navigating your local site easier, and will be updated when you deploy your site. All pages preset are discovered from your [**Application Sources**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/getting-started/data-sources), for dynamic URLs see [**Dynamic URLs**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/dynamic-urls). You can debug this further in Nuxt DevTools under the Sitemap tab. h2. [Configuration](#configuration) At a minimum the module requires a Site URL to be set, this is to only your canonical domain is being used for the sitemap. A site name can also be provided to customize the sitemap [**stylesheet**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/advanced/customising-ui). Without a Site URL, your sitemap will use localhost in production. [Site Config Quick Setup](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/site-config/getting-started/introduction) nuxt.config.ts ``` export default defineNuxtConfig( site: { url: 'https://example.com', name: 'My Awesome Website' }, }) ``` .env ``` NUXT_SITE_URL=https://example.comNUXT_SITE_NAME=My Awesome Website ``` For more advanced configurations, check out the [site config guide](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/site-config/guides/setting-site-config). To ensure search engines find your sitemap, you will need to add it to your robots.txt. It's recommended to use the [**Nuxt Robots**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/robots/getting-started/installation) module for this. [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/robots/getting-started/introduction) Every site is different and will require their own further unique configuration, to give you a head start: - [**Dynamic URL Endpoint**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/dynamic-urls) - If you have dynamic URLs you need to add to the sitemap, you can use a runtime API endpoint. For example, if your generating your site from a CMS. - [**Multi Sitemaps**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/multi-sitemaps) - If you have 10k+ pages, you may want to split your sitemap into multiple files so that search engines can process them more efficiently. You do not need to worry about any further configuration in most cases, check the [**best practices**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/best-practices) guide for more information. h2. [Next Steps](#next-steps) You've successfully installed Nuxt Sitemap. Here's the recommended reading path: 1. **[**Data Sources**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/getting-started/data-sources)** - Understand where your sitemap URLs come from 2. **[**Dynamic URLs**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/dynamic-urls)** - Add URLs from a CMS or database 3. **[**Best Practices**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/best-practices)** - Ensure your sitemap follows SEO guidelines **Using other Nuxt modules?** - [**Nuxt I18n**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/i18n) - Automatic locale sitemaps - [**Nuxt Content**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/content) - Configure sitemap from markdown frontmatter **Ready to deploy?** Check out [**Submitting Your Sitemap**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/guides/submitting-sitemap). [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/0.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/getting-started/installation.md) **Did this page help you? ** h3. **Related ** [**Data Sources**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/getting-started/data-sources) [**Nuxt Robots**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/robots/getting-started/installation) [**Nuxt Site Config**](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/site-config/getting-started/installation) [**Introduction** Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt.](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/getting-started/introduction) [**Data Sources** Understand where your sitemap URLs come from.](https://nuxtseo.com/docs/sitemap/getting-started/installation/docs/sitemap/getting-started/data-sources) **On this page** - [Setup Module](#setup-module) - [Verifying Installation](#verifying-installation) - [Configuration](#configuration) - [Next Steps](#next-steps) --- ### Config · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/api/config Description: Configure the sitemap module. **Nuxt API** h1. **Config** Last updated **Dec 16, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [feat: `zeroRuntime` mode (#532)](https://github.com/nuxt-modules/sitemap/pull/532). [Copy for LLMs h2. [`enabled`](#enabled) - Type: `boolean` - Default: `true` Whether to generate the sitemap. h2. [`sortEntries`](#sortentries) - Type: `boolean` - Default: `true` Whether the sitemap entries should be sorted or be shown in the order they were added. When enabled the entries will be sorted by the `loc`, they will be sorted by the path segment count and then alphabetically using `String.localeCompare` to ensure numbers are sorted correctly. h2. [`sources`](#sources) - Type: `SitemapSource[]` - Default: `[]` The sources to use for the sitemap. See [**Data Sources**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/getting-started/data-sources) and [**Dynamic URL Endpoint**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/guides/dynamic-urls) for details. h2. [`excludeAppSources`](#excludeappsources) - Type: `boolean|AppSourceId[]` - Default: `false` Whether to exclude [**app sources**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/getting-started/data-sources) from the sitemap. h2. [`appendSitemaps`](#appendsitemaps) - Type: `(string| { sitemap: string, lastmod?: Date})[]` - Default: `false` Sitemaps to append to the sitemap index. This will only do anything when using multiple sitemaps. h2. [`autoLastmod`](#autolastmod) - Type: `boolean` - Default: `false` Whether to automatically detect the `lastmod` date for each URL. If the `lastmod` date can't be inferred from a route page file it will use the current Date. h2. [`sitemaps`](#sitemaps) - Type: `SitemapConfig[] | boolean` - Default: `false` Whether to generate multiple sitemaps. Each sitemap can have the following options: h3. [SitemapConfig](#sitemapconfig) h4. [`sources`](#sources-1) - Type: `SitemapSource[]` - Default: `[]` Data sources for this specific sitemap. h4. [`chunks`](#chunks) - Type: `boolean | number` - Default: `undefined` Enable chunking for sitemap sources. This splits large collections of URLs from sources into multiple smaller sitemap files to stay within search engine limits. - Set to `true` to enable chunking with the default chunk size (from `defaultSitemapsChunkSize` or 1000) - Set to a positive number to use that as the chunk size (e.g., `5000` for 5000 URLs per chunk) - Set to `false` or leave undefined to disable chunking Note: Chunking only applies to URLs from `sources`. Direct URLs in the `urls` property are not chunked. ``` export default defineNuxtConfig({ sitemap: { sitemaps: { products: { sources: ['/api/products'], chunks: 5000 // Split into files with 5000 URLs each } } } }) ``` h4. [`chunkSize`](#chunksize) - Type: `number` - Default: `undefined` Explicitly set the chunk size for this sitemap. Takes precedence over the `chunks` property when both are specified. ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { sources: ['/api/posts'], chunks: true, // Enable chunking chunkSize: 2500 // Use 2500 URLs per chunk } } } }) ``` See the [**Chunking Sources**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/advanced/chunking-sources) guide for more details. h4. [`urls`](#urls) - Type: `string[] | (() => string[] | Promise<string[]>)` - Default: `[]` Static URLs to include in this sitemap. h4. [`include`](#include) - Type: `(string | RegExp)[]` - Default: `undefined` Filter URLs to include in this sitemap. h4. [`exclude`](#exclude) - Type: `(string | RegExp)[]` - Default: `undefined` Filter URLs to exclude from this sitemap. h4. [`defaults`](#defaults) - Type: `SitemapItemDefaults` - Default: `{}` Default values for all URLs in this sitemap. h4. [`includeAppSources`](#includeappsources) - Type: `boolean` - Default: `false` Whether to include automatic app sources in this sitemap. See [**Multi Sitemaps**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/guides/multi-sitemaps) for details. h2. [`defaultSitemapsChunkSize`](#defaultsitemapschunksize) - Type: `number | false` - Default: `1000` The default chunk size when chunking is enabled for multi-sitemaps. This value is used when: - A sitemap has `chunks: true` (without specifying a number) - No `chunkSize` is explicitly set for the sitemap Set to `false` to disable chunking by default for all sitemaps. ``` export default defineNuxtConfig({ sitemap: { defaultSitemapsChunkSize: 5000, sitemaps: { // These will use 5000 as chunk size posts: { sources: ['/api/posts'], chunks: true }, // This overrides the default products: { sources: ['/api/products'], chunks: 10000 } } } }) ``` h2. [`defaults`](#defaults-1) - Type: `object` - Default: `{}` Default values for the sitemap.xml entries. See [**sitemaps.org**](https://www.sitemaps.org/protocol.html) for all available options. h2. [`urls`](#urls-1) - Type: `() => MaybePromise<SitemapEntry[]> | MaybePromise<SitemapEntry[]>` - Default: `[]` Provide custom URLs to be included in the sitemap.xml. h2. [`include`](#include-1) - Type: `(string | RegExp)[]` - Default: `['/**']` Filter routes that match the given rules. See the [**Filtering URLs**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/guides/filtering-urls) guide for details. ``` export default defineNuxtConfig({ sitemap: { include: [ '/my-hidden-url' ] } }) ``` h2. [`exclude`](#exclude-1) - Type: `(string | RegExp)[]` - Default: `undefined` Filter routes that match the given rules. See the [**Filtering URLs**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/guides/filtering-urls) guide for details. ``` export default defineNuxtConfig({ sitemap: { exclude: [ '/my-secret-section/**' ] } }) ``` h2. [`xsl`](#xsl) - Type: `string | false` - Default: `/__sitemap__/style.xsl` The path to the XSL stylesheet for the sitemap.xml. Set to `false` to disable. h2. [`discoverImages`](#discoverimages) - Type: `boolean` - Default: `true` Whether to discover images from routes when prerendering. h2. [`discoverVideos`](#discovervideos) - Type: `boolean` - Default: `true` Whether to discover videos from routes when prerendering. h2. [`autoI18n`](#autoi18n) - Type: `undefined | boolean | { locales: NormalisedLocales; defaultLocale: string; strategy: 'prefix' | 'prefix_except_default' | 'prefix_and_default' }` - Default: `undefined` Automatically add alternative language prefixes for each entry with the given prefixes. Set to `false` to disable. When using the @nuxtjs/i18n module, this will automatically be set to the configured `locales` when left `undefined`. h2. [`sitemapName`](#sitemapname) - Type: `string` - Default: `sitemap.xml` Modify the name of the root sitemap. Note: This only works when you're not using the multiple `sitemaps` option. h2. [`strictNuxtContentPaths`](#strictnuxtcontentpaths) - Type: `boolean` - Default: `false` Whether the paths within nuxt/content match their real paths. This is useful when you're using the `nuxt/content` module without documentDriven mode. h2. [`cacheMaxAgeSeconds`](#cachemaxageseconds) - Type: `number` - Default: `60 * 10` The time in seconds to cache the sitemaps. h2. [`sitemapsPathPrefix`](#sitemapspathprefix) - Type: `string | false` - Default: `/__sitemap__/` The path prefix for the sitemaps when using multiple sitemaps. h2. [`runtimeCacheStorage`](#runtimecachestorage) - Type: `boolean | (Record<string, any> & { driver: string })` - Default: `true` The storage engine to use for the cache. See [**Performance**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/advanced/performance) for details. h2. [`xslColumns`](#xslcolumns) - Type: `({ label: string; width: \`${string}%\`; select?: string })[]` - Default: ``` [ { "label": "URL", "width": "50%" }, { "label": "Images", "width": "25%", "select": "count(image:image)" }, { "label": "Last Updated", "width": "25%", "select": "concat(substring(sitemap:lastmod,0,11),concat(' ', substring(sitemap:lastmod,12,5)),concat(' ', substring(sitemap:lastmod,20,6)))" } ] ``` The columns to display in the XSL stylesheet. h2. [`xslTips`](#xsltips) - Type: `boolean` - Default: `true` Whether to include tips on how to use the sitemap in the XSL stylesheet. h2. [`experimentalWarmUp`](#experimentalwarmup) - Type: `boolean` - Default: `false` Should the sitemaps be warmed up when Nitro starts. This can be useful for large sitemaps. h2. [`experimentalCompression`](#experimentalcompression) - Type: `boolean` - Default: `false` Should the sitemaps be compressed and streamed when the request accepts it. h2. [`credits`](#credits) - Type: `boolean` - Default: `true` Whether to include a comment on the sitemaps on how it was generated. h2. [`debug`](#debug) - Type: `boolean` - Default: `false` Enable to see debug logs and API endpoint. The route at `/__sitemap__/debug.json` will be available in non-production environments. See the [**Troubleshooting**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/getting-started/troubleshooting) guide for details. h2. [`zeroRuntime`](#zeroruntime) - Type: `boolean` - Default: `false` When enabled, sitemap generation only runs during prerendering. The sitemap building code is tree-shaken from the runtime bundle, reducing server bundle size by ~50KB. Requires sitemaps to be prerendered. When enabled, `/sitemap.xml` is automatically added to `nitro.prerender.routes`. See the [**Zero Runtime**](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/guides/zero-runtime) guide for details. [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/4.api/0.config.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/api/config.md) **Did this page help you? ** [**Customising the UI** Change the look and feel of your sitemap.](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/advanced/customising-ui) [**Nuxt Hooks** Build-time Nuxt hooks provided by @nuxtjs/sitemap.](https://nuxtseo.com/docs/sitemap/api/config/docs/sitemap/api/nuxt-hooks) **On this page** - [enabled](#enabled) - [sortEntries](#sortentries) - [sources](#sources) - [excludeAppSources](#excludeappsources) - [appendSitemaps](#appendsitemaps) - [autoLastmod](#autolastmod) - [sitemaps](#sitemaps) - [defaultSitemapsChunkSize](#defaultsitemapschunksize) - [defaults](#defaults-1) - [urls](#urls-1) - [include](#include-1) - [exclude](#exclude-1) - [xsl](#xsl) - [discoverImages](#discoverimages) - [discoverVideos](#discovervideos) - [autoI18n](#autoi18n) - [sitemapName](#sitemapname) - [strictNuxtContentPaths](#strictnuxtcontentpaths) - [cacheMaxAgeSeconds](#cachemaxageseconds) - [sitemapsPathPrefix](#sitemapspathprefix) - [runtimeCacheStorage](#runtimecachestorage) - [xslColumns](#xslcolumns) - [xslTips](#xsltips) - [experimentalWarmUp](#experimentalwarmup) - [experimentalCompression](#experimentalcompression) - [credits](#credits) - [debug](#debug) - [zeroRuntime](#zeroruntime) --- ### Troubleshooting Nuxt Sitemap · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting Description: Common issues and debugging tips for Nuxt Sitemap. **Getting Started** h1. **Troubleshooting Nuxt Sitemap** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, improve descriptions, and add sitemap validator links (#539) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/539). [Copy for LLMs h2. [Debugging](#debugging) h3. [Nuxt DevTools](#nuxt-devtools) The best tool for debugging is the Nuxt DevTools integration with Nuxt Sitemap. This will show you all of your sitemaps and the sources used to generate it. h3. [Debug Endpoint](#debug-endpoint) If you prefer looking at the raw data, you can use the debug endpoint. This is only enabled in development unless you enable the `debug` option. Visit `/__sitemap__/debug.json` within your browser, this is the same data used by Nuxt DevTools. h3. [Debugging Prerendering](#debugging-prerendering) If you're trying to debug the prerendered sitemap, you should enable the `debug` option and check your output for the file `.output/public/__sitemap__/debug.json`. h2. [Submitting an Issue](#submitting-an-issue) When submitting an issue, it's important to provide as much information as possible. The easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds: - [**Dynamic URLs**](https://stackblitz.com/edit/nuxt-starter-dyraxc?file=server%2Fapi%2F_sitemap-urls.ts) - [**i18n**](https://stackblitz.com/edit/nuxt-starter-jwuie4?file=app.vue) - [**Manual Chunking**](https://stackblitz.com/edit/nuxt-starter-umyso3?file=nuxt.config.ts) - [**Nuxt Content Document Driven**](https://stackblitz.com/edit/nuxt-starter-a5qk3s?file=nuxt.config.ts) h2. [Troubleshooting FAQ](#troubleshooting-faq) h3. [Why is my browser not rendering the XML properly?](#why-is-my-browser-not-rendering-the-xml-properly) When disabling the [**XSL**](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/advanced/customising-ui#disabling-the-xls) (XML Stylesheet) in, the XML will be rendered by the browser. If you have a i18n integration, then it's likely you'll see your sitemap look raw text instead of XML. ![Broken XML because of xhtml namespace.](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/formatting-error.png) This is a [**browser bug**](https://bugs.chromium.org/p/chromium/issues/detail?id=580033) in parsing the `xhtml` namespace which is required to add localised URLs to your sitemap. There is no workaround besides re-enabled the XSL. h3. [Google Search Console shows Error when submitting my Sitemap?](#google-search-console-shows-error-when-submitting-my-sitemap) Seeing "Error" when submitting a new sitemap is common. This is because Google previously crawled your site for a sitemap and found nothing. If your sitemap is [**validating**](https://www.xml-sitemaps.com/validate-xml-sitemap.html) correctly, then you're all set. It's best to way a few days and check back. In nearly all cases, the error will resolve itself. h3. [Search Console shows "Invalid character" error?](#search-console-shows-invalid-character-error) This happens when URLs contain reserved characters like `$`, `:`, or `@` that aren't properly encoded for XML. The module automatically encodes unicode characters (emojis, accents) but does not encode RFC-3986 reserved characters. **Solution:** If your API returns pre-encoded URLs, mark them with `_encoded: true` to prevent double-encoding: server/api/__sitemap__/urls.ts ``` export default defineSitemapEventHandler(async () => { const urls = await $fetch('https://api.example.com/pages') // URLs are already encoded: [{ path: '/products/%24pecial' }] return urls.map(url => ({ loc: url.path, _encoded: true, })) }) ``` See [**Handling Pre-Encoded URLs**](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/guides/dynamic-urls#handling-pre-encoded-urls) for more details. h2. [Debugging Tools](#debugging-tools) - [**XML Sitemap Validator**](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/tools/xml-sitemap-validator) - Validate sitemap structure, check URL format, and test against Google requirements [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/0.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/getting-started/troubleshooting.md) **Did this page help you? ** h3. **Related ** [**Customising the UI**](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/advanced/customising-ui) [**Submitting Your Sitemap**](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/guides/submitting-sitemap) [**Nuxt SEO Troubleshooting**](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/seo/getting-started/troubleshooting) [**Data Sources** Understand where your sitemap URLs come from.](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/getting-started/data-sources) [**Dynamic URL Endpoints** Use runtime API endpoints to generate dynamic URLs for your sitemap.](https://nuxtseo.com/docs/sitemap/getting-started/troubleshooting/docs/sitemap/guides/dynamic-urls) **On this page** - [Debugging](#debugging) - [Submitting an Issue](#submitting-an-issue) - [Troubleshooting FAQ](#troubleshooting-faq) - [Debugging Tools](#debugging-tools) --- ### Nuxt Content · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/content Description: How to use the Nuxt Sitemap module with Nuxt Content. **Core Concepts** h1. **Nuxt Content** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: overhaul](https://github.com/nuxt-modules/sitemap/commit/25a370e521eb226799ba9eb3c4253731b91c1c1a). [Copy for LLMs h2. [Introduction](#introduction) Nuxt Sitemap comes with an integration for Nuxt Content that allows you to configure your sitemap entry straight from your content files directly. h3. [Supported Content Types](#supported-content-types) The sitemap integration works with all content file types supported by Nuxt Content: - Markdown (`.md`) - YAML (`.yml` / `.yaml`) - JSON (`.json`) - CSV (`.csv`) h2. [Setup Nuxt Content v3](#setup-nuxt-content-v3) In Nuxt Content v3 we need to use the `asSitemapCollection()` function to augment any collections to be able to use the `sitemap` frontmatter key. content.config.ts ``` import { defineCollection, defineContentConfig } from '@nuxt/content' import { asSitemapCollection } from '@nuxtjs/sitemap/content' export default defineContentConfig({ collections: { content: defineCollection( // adds the robots frontmatter key to the collection asSitemapCollection({ type: 'page', source: '**/*.md', }), ), }, }) ``` Due to current Nuxt Content v3 limitations, you must load the sitemap module before the content module. ``` export default defineNuxtConfig({ modules: [ '@nuxtjs/sitemap', '@nuxt/content' // <-- Must be after @nuxtjs/sitemap ] }) ``` h2. [Setup Nuxt Content v2](#setup-nuxt-content-v2) In Nuxt Content v2 markdown files require either [**Document Driven Mode**](https://content.nuxt.com/document-driven/introduction), a `path` key to be set in the frontmatter or the `strictNuxtContentPaths` option to be enabled. nuxt.config.ts ``` export default defineNuxtConfig({ // things just work! content: { documentDriven: true } }) ``` If you're not using `documentDriven` mode and your content paths are the same as their real paths, you can enable `strictNuxtContentPaths` to get the same behaviour. nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { strictNuxtContentPaths: true } }) ``` h3. [Advanced: Nuxt Content App Source](#advanced-nuxt-content-app-source) If you'd like to set up a more automated Nuxt Content integration and you're not using Document Driven mode, you can add content to the sitemap as you would with [**Dynamic URLs**](https://nuxtseo.com/docs/sitemap/guides/content/docs/sitemap/guides/dynamic-urls). An example of what this might look like is below, customize to your own needs. server/api/__sitemap__/urls.ts ``` import { defineEventHandler } from 'h3' import type { ParsedContent } from '@nuxt/content/dist/runtime/types' import { serverQueryContent } from '#content/server' import { asSitemapUrl, defineSitemapEventHandler } from '#imports' export default defineSitemapEventHandler(async (e) => { const contentList = (await serverQueryContent(e).find()) as ParsedContent[] return contentList .filter(c => c._path.startsWith('_articles')) .map((c) => { return asSitemapUrl({ loc: \`/blog/${c._path.replace('_articles', '')}\`, lastmod: updatedAt }) }) }) ``` ``` export default defineNuxtConfig({ sitemap: { sources: [ '/api/__sitemap__/urls' ] } }) ``` h2. [Usage](#usage) h3. [Frontmatter `sitemap`](#frontmatter-sitemap) Use the `sitemap` key in your frontmatter to add a page to your sitemap. You can provide any data that you would normally provide in the sitemap configuration. h4. [Markdown Example](#markdown-example) ``` --- sitemap: loc: /my-page lastmod: 2021-01-01 changefreq: monthly priority: 0.8 --- h1. My Page ``` h4. [YAML Example](#yaml-example) content/pages/about.yml ``` title: About Page description: Learn more about us sitemap: lastmod: 2025-05-13 changefreq: monthly priority: 0.8 content: | This is the about page content ``` h4. [JSON Example](#json-example) content/products/widget.json ``` { "title": "Widget Product", "price": 99.99, "sitemap": { "lastmod": "2025-05-14", "changefreq": "weekly", "priority": 0.9 } } ``` h3. [Exclude from Sitemap](#exclude-from-sitemap) If you'd like to exclude a page from the sitemap, you can set `sitemap: false` in the frontmatter or `robots: false` if you'd like to exclude it from search engines. ``` --- sitemap: false robots: false --- ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/4.content.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/content/docs/sitemap/guides/content.md) **Did this page help you? ** h3. **Related ** [**Dynamic URL Endpoints**](https://nuxtseo.com/docs/sitemap/guides/content/docs/sitemap/guides/dynamic-urls) [**Data Sources**](https://nuxtseo.com/docs/sitemap/guides/content/docs/sitemap/getting-started/data-sources) [**Lastmod, Priority, and Changefreq**](https://nuxtseo.com/docs/sitemap/guides/content/docs/sitemap/advanced/loc-data) [**I18n** Setting up a sitemap with Nuxt I18n and Nuxt I18n Micro.](https://nuxtseo.com/docs/sitemap/guides/content/docs/sitemap/guides/i18n) [**Nuxt Prerendering** Prerender your pages and have them all automatically added to your sitemap.](https://nuxtseo.com/docs/sitemap/guides/content/docs/sitemap/guides/prerendering) **On this page** - [Introduction](#introduction) - [Setup Nuxt Content v3](#setup-nuxt-content-v3) - [Setup Nuxt Content v2](#setup-nuxt-content-v2) - [Usage](#usage) --- ### Disabling Indexing · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/filtering-urls Description: How to filter the URLs generated from application sources. **Core Concepts** h1. **Disabling Indexing** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#540) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/540). [Copy for LLMs h2. [Introduction](#introduction) When viewing your sitemap.xml for the first time, you may notice some URLs you don't want to be included. These URLs are most likely coming from [**Application Sources**](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/sitemap/getting-started/data-sources). If you don't want to disable these sources but want to remove these URLs you have a couple of options. h2. [Nuxt Robots](#nuxt-robots) The easiest way to block search engines from indexing a URL is to use the [**Nuxt Robots**](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/robots/getting-started/installation) module and simply block the URL in your robots.txt. [Robots **v5.6.7** 7.8M 502 Tame the robots crawling and indexing your site with ease.](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/robots/getting-started/introduction) Nuxt Sitemap will honour any blocked pages from being ignored in the sitemap. h2. [Disabling indexing with Route Rules](#disabling-indexing-with-route-rules) If you don't want a page in your sitemap because you don't want search engines to crawl it, then you can make use of the `robots` route rule. For comprehensive route rules documentation, see [**Nuxt Robots route rules**](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/robots/guides/route-rules). h3. [Disabling indexing for a pattern of URLs](#disabling-indexing-for-a-pattern-of-urls) If you have a pattern of URLs that you want hidden from search you can use route rules. nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // Don't add any /secret/** URLs to the sitemap.xml '/secret/**': { robots: false }, } }) ``` h3. [Inline route rules](#inline-route-rules) If you just have some specific pages, you can use the experimental [`defineRouteRules()`](https://nuxt.com/docs/api/utils/define-route-rules), which must be enabled. ``` <script setup lang="ts"> defineRouteRules({ robots: false }) </script> ``` h2. [Filter URLs with include / exclude](#filter-urls-with-include-exclude) For all other cases, you can use the `include` and `exclude` module options to filter URLs. nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { // exclude all URLs that start with /secret exclude: ['/secret/**'], // include all URLs that start with /public include: ['/public/**'], } }) ``` Either option supports either an array of strings, RegExp objects or a `{ regex: string }` object. Providing strings will use the [**route rules path matching**](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering) which does not support variable path segments in front of static ones. For example, `/foo/**` will work but `/foo/**/bar` will not. To get around this you should use regex. h3. [Regex Filtering](#regex-filtering) Filtering using regex is more powerful and can be used to match more complex patterns. It's recommended to pass a `RegExp` object explicitly. nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { exclude: [ // exclude /foo/**/bar using regex new RegExp('/foo/.*/bar') ], } }) ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/1.filtering-urls.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/sitemap/guides/filtering-urls.md) **Did this page help you? ** h3. **Related ** [**Data Sources**](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/sitemap/getting-started/data-sources) [**Nuxt Robots**](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/robots/getting-started/installation) [**Controlling Web Crawlers**](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/learn/controlling-crawlers) [**Dynamic URL Endpoints** Use runtime API endpoints to generate dynamic URLs for your sitemap.](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/sitemap/guides/dynamic-urls) [**Multi Sitemaps** Generate multiple sitemaps for different sections of your site.](https://nuxtseo.com/docs/sitemap/guides/filtering-urls/docs/sitemap/guides/multi-sitemaps) **On this page** - [Introduction](#introduction) - [Nuxt Robots](#nuxt-robots) - [Disabling indexing with Route Rules](#disabling-indexing-with-route-rules) - [Filter URLs with include / exclude](#filter-urls-with-include-exclude) --- ### Nuxt Prerendering · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/prerendering Description: Prerender your pages and have them all automatically added to your sitemap. **Core Concepts** h1. **Nuxt Prerendering** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, improve descriptions, and add sitemap validator links (#539) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/539). [Copy for LLMs h2. [Introduction](#introduction) When prerendering routes using Nuxt through either `nuxi generate` or using the prerender options, the module will extract data from the generated HTML and add it to the sitemap. This can be useful if you have dynamic routes that you want to be included in the sitemap and want to minimise your configuration. h2. [Extracted HTML Data](#extracted-html-data) The following data can be extracted from the raw HTML. - `images` - Adds image entries `<image:image>`. Passes any `<img>` tags within the `<main>` tag. Opt-out by disabling `discoverImages`. - `videos` - Adds video entries `<video:video>`. Passes any `<video>` tags within the `<main>` tag. Opt-out by disabling `discoverVideos`. - `lastmod` - Adds lastmod date `<lastmod>`. Uses the [**opengraph**](https://ogp.me) `article:modified_time` and `article:published_time` meta tag. h2. [Enabling Nuxt Prerendering](#enabling-nuxt-prerendering) You will need to use configuration to enable this feature. ``` export default defineNuxtConfig({ nitro: { prerender: { // enabled by default with nuxt generate, not required crawlLinks: true, // add any routes to prerender routes: ['/'] } } }) ``` You can also use route rules to enable prerendering for specific routes. ``` export default defineNuxtConfig({ routeRules: { '/': { prerender: true } } }) ``` h3. [Prerendering the Sitemap on Build](#prerendering-the-sitemap-on-build) If you're using `nuxi build` and want to prerender the sitemap on build, you can add the sitemap path to the `nitro.prerender.routes` option. ``` export default defineNuxtConfig({ nitro: { prerender: { routes: ['/sitemap.xml'] } } }) ``` h3. [Customizing the prerender data](#customizing-the-prerender-data) If needed, you can customize the prerender data by using the Nitro hooks. Here is a simple recipe that will extract YouTube video iframes and add them to the sitemap. ``` import type { ResolvedSitemapUrl } from '#sitemap/types' export default defineNuxtConfig({ modules: [ // run this before the sitemap moduke (_, nuxt) => { nuxt.hooks.hook('nitro:init', async (nitro) => { nitro.hooks.hook('prerender:generate', async (route) => { const html = route.contents // check for youtube video iframes and append to the videos array const matches = html.match(/<iframe.*?youtube.com\/embed\/(.*?)".*?<\/iframe>/g) if (matches) { const sitemap = route._sitemap || {} as ResolvedSitemapUrl sitemap.videos = sitemap.videos || [] for (const match of matches) { const videoId = match.match(/youtube.com\/embed\/(.*?)" /)[1] sitemap.videos.push({ title: 'YouTube Video', description: 'A video from YouTube', content_loc: \`https://www.youtube.com/watch?v=${videoId}\`, thumbnail_loc: \`https://img.youtube.com/vi/${videoId}/0.jpg\`, }) } // the sitemap module should be able to pick this up route._sitemap = sitemap } }) }) }, ], }) ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/5.prerendering.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/prerendering/docs/sitemap/guides/prerendering.md) **Did this page help you? ** h3. **Related ** [**Images, Videos, News**](https://nuxtseo.com/docs/sitemap/guides/prerendering/docs/sitemap/advanced/images-videos) [**Data Sources**](https://nuxtseo.com/docs/sitemap/guides/prerendering/docs/sitemap/getting-started/data-sources) [**Nuxt Content** How to use the Nuxt Sitemap module with Nuxt Content.](https://nuxtseo.com/docs/sitemap/guides/prerendering/docs/sitemap/guides/content) [**Best Practices** The best practices for generating a sitemap.xml file.](https://nuxtseo.com/docs/sitemap/guides/prerendering/docs/sitemap/guides/best-practices) **On this page** - [Introduction](#introduction) - [Extracted HTML Data](#extracted-html-data) - [Enabling Nuxt Prerendering](#enabling-nuxt-prerendering) --- ### Sitemap.xml Best Practices · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/best-practices Description: The best practices for generating a sitemap.xml file. **Core Concepts** h1. **Sitemap.xml Best Practices** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#540) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/540). [Copy for LLMs h2. [Set appropriate lastmod](#set-appropriate-lastmod) The `lastmod` field is used to indicate when a page was last updated. This is used by search engines to determine how often to crawl your site. This should not change based on code changes, only for updating the content. For example, if you have a blog post, the `lastmod` should be updated when the content of the blog post changes. It's recommended not to use `autoLastmod: true` as this will use the last time the page was built, which does not always reflect content updates. Learn more in [**Google's sitemap lastmod documentation**](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping). h2. [You probably don't need `changefreq` or `priority`](#you-probably-dont-need-changefreq-or-priority) These two fields are not used by search engines, and are only used by crawlers to determine how often to crawl your site. If you're trying to get your site crawled more often, you should use the `lastmod` field instead. Learn more in [**Google's sitemap best practices**](https://developers.google.com/search/blog/2023/06/sitemaps-lastmod-ping). h2. [Use Zero Runtime when content only changes on deploy](#use-zero-runtime-when-content-only-changes-on-deploy) If your pages only change when you commit and deploy (not at runtime), you don't need runtime sitemap generation. Enable `zeroRuntime` to generate sitemaps at build time and remove ~50KB of sitemap code from your server bundle. ``` export default defineNuxtConfig({ sitemap: { zeroRuntime: true } }) ``` This is ideal for sites using `nuxt build` where content is static between deployments. If you're using a CMS that updates content without redeploying, you'll need runtime generation. Learn more in the [**Zero Runtime**](https://nuxtseo.com/docs/sitemap/guides/best-practices/docs/sitemap/guides/zero-runtime) guide. [](https://nuxtseo.com/docs/sitemap/guides/best-practices/tools/xml-sitemap-validator)**Check your sitemap** - Validate your sitemap meets Google requirements with our [**XML Sitemap Validator**](https://nuxtseo.com/docs/sitemap/guides/best-practices/tools/xml-sitemap-validator). **Quick Checklist** - Set meaningful lastmod dates based on content changes - Skip changefreq and priority (ignored by search engines) - Enable zeroRuntime for static sites - Submit sitemap to Google Search Console [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/6.best-practices.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/best-practices/docs/sitemap/guides/best-practices.md) **Did this page help you? ** h3. **Related ** [**Lastmod, Priority, and Changefreq**](https://nuxtseo.com/docs/sitemap/guides/best-practices/docs/sitemap/advanced/loc-data) [**Submitting Your Sitemap**](https://nuxtseo.com/docs/sitemap/guides/best-practices/docs/sitemap/guides/submitting-sitemap) [**Controlling Web Crawlers**](https://nuxtseo.com/docs/sitemap/guides/best-practices/learn/controlling-crawlers) [**Nuxt Prerendering** Prerender your pages and have them all automatically added to your sitemap.](https://nuxtseo.com/docs/sitemap/guides/best-practices/docs/sitemap/guides/prerendering) [**Submitting Your Sitemap** How to submit your sitemap to Google Search Console to start getting indexed.](https://nuxtseo.com/docs/sitemap/guides/best-practices/docs/sitemap/guides/submitting-sitemap) **On this page** - [Set appropriate lastmod](#set-appropriate-lastmod) - [You probably don't need changefreq or priority](#you-probably-dont-need-changefreq-or-priority) - [Use Zero Runtime when content only changes on deploy](#use-zero-runtime-when-content-only-changes-on-deploy) --- ### Images, Videos, News · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/advanced/images-videos Description: Learn how to add images, videos and news in your sitemap. **Advanced** h1. **Images, Videos, News** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: overhaul](https://github.com/nuxt-modules/sitemap/commit/25a370e521eb226799ba9eb3c4253731b91c1c1a). [Copy for LLMs h2. [Introduction](#introduction) The `image`, `video` and `news` namespaces is added to your sitemap by default, allowing you to configure images, videos and news for your sitemap entries. When prerendering your app, it's possible for the generated sitemap to automatically infer images and videos from your pages. h2. [Sitemap Images](#sitemap-images) To add images to your sitemap, you can use the `images` property on the sitemap entry. You can learn more about images in sitemaps on the [**Google documentation**](https://developers.google.com/search/docs/advanced/sitemaps/image-sitemaps). ``` export interface ImageEntry { loc: string } ``` You can implement this as follows: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { urls: [ { loc: '/blog/my-post', images: [ { loc: 'https://example.com/image.jpg', caption: 'My image caption', geo_location: 'My image geo location', title: 'My image title', license: 'My image license', } ] } ] } }) ``` h3. [Automatic Image Discovery](#automatic-image-discovery) The module can discover images in your page and add them to your sitemap automatically. For this to work: - The page _must_ be prerendered. These images will not be shown in development or if the page is not prerendered. - You must wrap your page content with a `<main>` tag, avoid wrapping shared layouts that include duplicate images. h2. [Videos](#videos) To add videos to your sitemap, you can use the `videos` property on the sitemap entry. The TypeScript interface for videos is as follows: ``` export interface VideoEntry { title: string thumbnail_loc: string | URL description: string content_loc?: string | URL player_loc?: string | URL duration?: number expiration_date?: Date | string rating?: number view_count?: number publication_date?: Date | string family_friendly?: 'yes' | 'no' | boolean restriction?: Restriction platform?: Platform price?: ({ price?: number | string currency?: string type?: 'rent' | 'purchase' | 'package' | 'subscription' })[] requires_subscription?: 'yes' | 'no' | boolean uploader?: { uploader: string info?: string | URL } live?: 'yes' | 'no' | boolean tag?: string | string[] } ``` You can learn more about videos in sitemaps on the [**Google documentation**](https://developers.google.com/search/docs/advanced/sitemaps/video-sitemaps). You can implement this as follows: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { urls: [ { loc: '/blog/my-post', videos: [ { title: 'My video title', thumbnail_loc: 'https://example.com/video.jpg', description: 'My video description', content_loc: 'https://example.com/video.mp4', player_loc: 'https://example.com/video.mp4', duration: 600, expiration_date: '2021-01-01', rating: 4.2, view_count: 1000, publication_date: '2021-01-01', family_friendly: true, restriction: { relationship: 'allow', country: 'US', }, platform: { relationship: 'allow', platform: 'web', date: '2021-01-01', }, price: [ { price: 1.99, currency: 'USD', type: 'rent', } ], requires_subscription: true, uploader: { uploader: 'My video uploader', info: 'https://example.com/uploader', }, live: true, tag: ['tag1', 'tag2'], } ] } ] } }) ``` h3. [Automatic Video Discovery](#automatic-video-discovery) Like automatic image discovery, you can opt-in to automatic video discovery including video markup in your `<main>` tag. You are also required to provide a title and description for your video, this can be done using the `data-title` and `data-description` attributes. Simple ``` <video controls poster="https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg" width="620" data-title="Duck and Cover" data-description="This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack." > <source src="https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4" type="video/mp4" /> <source src="https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda.avi" type="video/x-msvideo" /> Sorry, your browser doesn't support embedded videos. However, you can <a href="https://archive.org/details/DuckAndCover_185">download it</a> and watch it with your favorite video player! </video> ``` Full ``` <video controls poster="https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg" width="620" data-title="Duck and Cover" data-description="This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack." data-rating="4.2" data-view-count="1000" data-publication-date="2021-01-01" data-family-friendly="yes" > ``` Each format would be added to your sitemap in the following format: ``` <video:video> <video:thumbnail_loc>https://archive.org/download/DuckAndCover_185/__ia_thumb.jpg</video:thumbnail_loc> <video:title>Duck and Cover</video:title> <video:description> This film, a combination of animated cartoon and live action, shows young children what to do in case of an atomic attack. </video:description> <video:content_loc> https://archive.org/download/DuckAndCover_185/CivilDefenseFilm-DuckAndCoverColdWarNuclearPropaganda_512kb.mp4 </video:content_loc> </video:video> ``` h2. [News](#news) To add news to your sitemap, you can use the `news` property on the sitemap entry. Only [**Google's News sitemap**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap) extension is supported. The TypeScript interface for news is as follows: ``` export interface GoogleNewsEntry { title: string publication_date: Date | string publication: { name: string language: string } } ``` You can implement this as follows: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { urls: [ { loc: '/news/nuxt-sitemap-turns-6', news: [ { title: 'Nuxt Sitemap Turns 6', publication_date: '2021-01-01', publication: { name: 'Nuxt Sitemap', language: 'en', }, } ] } ] } }) ``` h2. [Image & Video Opt-out](#image-video-opt-out) To opt-out of this behaviour, you can set the `discoverImages` and `discoverVideos` config to `false` respectively. nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { discoverImages: false, discoverVideos: false, } }) ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/2.advanced/1.images-videos.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/advanced/images-videos/docs/sitemap/advanced/images-videos.md) **Did this page help you? ** h3. **Related ** [**Nuxt Prerendering**](https://nuxtseo.com/docs/sitemap/advanced/images-videos/docs/sitemap/guides/prerendering) [**Best Practices**](https://nuxtseo.com/docs/sitemap/advanced/images-videos/docs/sitemap/guides/best-practices) [**Lastmod, Priority, and Changefreq** Configure lastmod, priority, and changefreq values for your sitemap entries.](https://nuxtseo.com/docs/sitemap/advanced/images-videos/docs/sitemap/advanced/loc-data) [**Sitemap Performance** Use the default cache engine to keep your sitemaps fast.](https://nuxtseo.com/docs/sitemap/advanced/images-videos/docs/sitemap/advanced/performance) **On this page** - [Introduction](#introduction) - [Sitemap Images](#sitemap-images) - [Videos](#videos) - [News](#news) - [Image & Video Opt-out](#image-video-opt-out) --- ### Sitemap Chunking · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/advanced/chunking-sources Description: Split large sitemap sources into multiple files for performance and search engine limits. **Advanced** h1. **Sitemap Chunking** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: overhaul](https://github.com/nuxt-modules/sitemap/commit/25a370e521eb226799ba9eb3c4253731b91c1c1a). [Copy for LLMs h2. [Introduction](#introduction) When dealing with large datasets, sitemap sources can be chunked into multiple files to: - Stay within search engine limits (50MB file size, 50,000 URLs) - Improve generation performance - Better manage memory usage h2. [Simple Configuration](#simple-configuration) Enable chunking on any named sitemap with sources: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { sources: ['/api/posts'], chunks: true, // Uses default size of 1000 } } } }) ``` This generates: ``` /sitemap_index.xml # Master index /posts-0.xml # First chunk (1-1000) /posts-1.xml # Second chunk (1001-2000) ... ``` h2. [Chunk Size Options](#chunk-size-options) Configure chunk sizes using different approaches: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { // Global default defaultSitemapsChunkSize: 5000, sitemaps: { // Using boolean (applies default) posts: { sources: ['/api/posts'], chunks: true, }, // Using number as size products: { sources: ['/api/products'], chunks: 10000, }, // Using explicit chunkSize (highest priority) articles: { sources: ['/api/articles'], chunks: true, chunkSize: 2000, } } } }) ``` h2. [Practical Examples](#practical-examples) h3. [E-commerce Site](#e-commerce-site) nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { defaultSitemapsChunkSize: 10000, sitemaps: { products: { sources: ['/api/products/all'], chunks: 2000, }, categories: { sources: ['/api/categories'], chunks: true, // Uses default 10k } } } }) ``` h3. [Large Content Site](#large-content-site) nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { sitemaps: { 'blog-posts': { sources: ['/api/blog/posts'], chunks: 5000, }, authors: { sources: ['/api/authors'], chunks: false, // Explicitly disable } } } }) ``` h2. [Source Implementation](#source-implementation) Basic endpoint for sitemap sources: server/api/products/all.ts ``` export default defineEventHandler(async () => { const products = await db.products.findAll({ select: ['id', 'slug', 'updatedAt'] }) return products.map(product => ({ loc: \`/products/${product.slug}\`, lastmod: product.updatedAt })) }) ``` For large datasets, use caching and streaming: server/api/products/all.ts ``` export default defineCachedEventHandler(async () => { const products = [] const cursor = db.products.cursor({ select: ['slug', 'updatedAt'] }) for await (const product of cursor) { products.push({ loc: \`/products/${product.slug}\`, lastmod: product.updatedAt }) } return products }, { maxAge: 60 * 60, // 1 hour cache name: 'sitemap-products' }) ``` h2. [Debugging](#debugging) Check chunk configuration and performance: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { debug: true, sitemaps: { products: { sources: ['/api/products'], chunks: 5000 } } } }) ``` Visit `/__sitemap__/debug.json` to see chunk details and generation metrics. [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/2.advanced/3.chunking-sources.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/advanced/chunking-sources/docs/sitemap/advanced/chunking-sources.md) **Did this page help you? ** h3. **Related ** [**Multi Sitemaps**](https://nuxtseo.com/docs/sitemap/advanced/chunking-sources/docs/sitemap/guides/multi-sitemaps) [**Sitemap Performance**](https://nuxtseo.com/docs/sitemap/advanced/chunking-sources/docs/sitemap/advanced/performance) [**Data Sources**](https://nuxtseo.com/docs/sitemap/advanced/chunking-sources/docs/sitemap/getting-started/data-sources) [**Sitemap Performance** Use the default cache engine to keep your sitemaps fast.](https://nuxtseo.com/docs/sitemap/advanced/chunking-sources/docs/sitemap/advanced/performance) [**Customising the UI** Change the look and feel of your sitemap.](https://nuxtseo.com/docs/sitemap/advanced/chunking-sources/docs/sitemap/advanced/customising-ui) **On this page** - [Introduction](#introduction) - [Simple Configuration](#simple-configuration) - [Chunk Size Options](#chunk-size-options) - [Practical Examples](#practical-examples) - [Source Implementation](#source-implementation) - [Debugging](#debugging) --- ### Zero Runtime · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/zero-runtime Description: Generate sitemaps at build time without runtime overhead. **Core Concepts** h1. **Zero Runtime** Last updated **Dec 16, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [feat: `zeroRuntime` mode (#532)](https://github.com/nuxt-modules/sitemap/pull/532). [Copy for LLMs If your sitemap URLs only change when you deploy, you don't need to ship sitemap generation code to production. The `zeroRuntime` option generates sitemaps at build time and tree-shakes the generation code from your server bundle. h2. [Usage](#usage) To enable zero runtime, add the following to your config: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { zeroRuntime: true } }) ``` When enabled, the module will automatically add `/sitemap.xml` to your prerender routes. The sitemap will be generated during build and served as a static file at runtime. h2. [How it Works](#how-it-works) With `zeroRuntime: true`: 1. Sitemap routes are automatically added to `nitro.prerender.routes` 2. Server handlers use dynamic imports gated by `import.meta.prerender` 3. At build time, the sitemap generation code is tree-shaken from the runtime bundle 4. Static XML files are served directly without any sitemap code execution h2. [Development Mode](#development-mode) Zero runtime mode still works in development (`nuxt dev`). The sitemap generation code runs normally during development so you can test your configuration. h2. [Benchmarks](#benchmarks) Enabling `zeroRuntime` reduces the server bundle by approximately: - **~50KB** uncompressed - **~5KB** gzip This is the sitemap generation code (XML building, URL normalization, source fetching) being tree-shaken from the bundle. h2. [Limitations](#limitations) - Runtime sitemap generation is not available - sitemaps are only generated during build - Dynamic data sources that require runtime fetching won't work - Debug endpoints are disabled in zero runtime mode h2. [When to Use](#when-to-use) Zero runtime is ideal when: - Your pages only change when you commit and deploy - You're using `nuxt generate` for a fully static site - You want to minimize your server bundle size for edge/serverless h2. [When Not to Use](#when-not-to-use) Avoid zero runtime when: - Your CMS updates content without redeploying - You have user-generated content that changes frequently - Your sitemap URLs depend on runtime data [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/8.zero-runtime.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/zero-runtime/docs/sitemap/guides/zero-runtime.md) **Did this page help you? ** [**Submitting Your Sitemap** How to submit your sitemap to Google Search Console to start getting indexed.](https://nuxtseo.com/docs/sitemap/guides/zero-runtime/docs/sitemap/guides/submitting-sitemap) [**Lastmod, Priority, and Changefreq** Configure lastmod, priority, and changefreq values for your sitemap entries.](https://nuxtseo.com/docs/sitemap/guides/zero-runtime/docs/sitemap/advanced/loc-data) **On this page** - [Usage](#usage) - [How it Works](#how-it-works) - [Development Mode](#development-mode) - [Benchmarks](#benchmarks) - [Limitations](#limitations) - [When to Use](#when-to-use) - [When Not to Use](#when-not-to-use) --- ### Submitting Your Sitemap · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap Description: How to submit your sitemap to Google Search Console to start getting indexed. **Core Concepts** h1. **Submitting Your Sitemap** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, improve descriptions, and add sitemap validator links (#539) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/539). [Copy for LLMs h2. [Introduction](#introduction) When going live with a new site and you're looking to get indexed by Google, the best starting point is to submit your sitemap to Google Search Console. > Google Search Console is a free service offered by Google that helps you monitor, maintain, and troubleshoot your site's presence in Google Search results. [](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/tools/xml-sitemap-validator)**Validate before submitting** - Use our [**XML Sitemap Validator**](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/tools/xml-sitemap-validator) to check for errors before submitting to Google Search Console. h2. [Submitting Sitemap](#submitting-sitemap) Google provides a guide on [**Submitting your Sitemap to Google**](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) which is a great starting point. You should index either `/sitemap.xml` or if you're using multiple sitemaps, add `/sitemap_index.xml`. h2. [Requesting Indexing](#requesting-indexing) It's important to know that submitting your sitemap does not guarantee that all your pages will be indexed and that it may take some time for Google to crawl and index your pages. To speed up the process, you can use the [**URL Inspection Tool**](https://support.google.com/webmasters/answer/9012289) to request indexing of a specific URL. In some cases you may want to expedite the indexing process, for this, you can try out my free open-source tool [**Request Indexing**](https://requestindexing.com). h2. [Sitemap Error](#sitemap-error) When submitting a sitemap for the first time you may get see "Error". This is because Google previously crawled your site for a sitemap and found nothing. When encountering this it's best to wait a few days and see if the error resolves itself. If not, you can try resubmitting the sitemap or making a [**GitHub Issue**](https://github.com/nuxt-modules/sitemap). [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/1.guides/7.submitting-sitemap.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/docs/sitemap/guides/submitting-sitemap.md) **Did this page help you? ** h3. **Related ** [**Troubleshooting**](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/docs/sitemap/getting-started/troubleshooting) [**Best Practices**](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/docs/sitemap/guides/best-practices) [**Controlling Web Crawlers**](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/learn/controlling-crawlers) [**Best Practices** The best practices for generating a sitemap.xml file.](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/docs/sitemap/guides/best-practices) [**Zero Runtime** Generate sitemaps at build time without runtime overhead.](https://nuxtseo.com/docs/sitemap/guides/submitting-sitemap/docs/sitemap/guides/zero-runtime) **On this page** - [Introduction](#introduction) - [Submitting Sitemap](#submitting-sitemap) - [Requesting Indexing](#requesting-indexing) - [Sitemap Error](#sitemap-error) --- ### Install Nuxt Site Config · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/getting-started/installation Description: Get started with Nuxt Site Config by installing the dependency to your project. **Getting Started** h1. **Install Nuxt Site Config** Last updated **Oct 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: cleaning up install Co-Authored-By: Claude <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-site-config/commit/79ae2680f2ff51990763671de72c42b03784cefb). [Copy for LLMs h2. [Setup Module](#setup-module) `npx nuxt module add site-config` `npm i nuxt-site-config` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-site-config', ], }) ``` `yarn add nuxt-site-config` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-site-config', ], }) ``` `pnpm i nuxt-site-config` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-site-config', ], }) ``` `bun i nuxt-site-config` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-site-config', ], }) ``` h2. [Configuration](#configuration) For Nuxt apps, see the [**Site Config Guide**](https://nuxtseo.com/docs/site-config/getting-started/installation/docs/site-config/guides/setting-site-config) for usage. h2. [Module Kit Usage](#module-kit-usage) Use the install function in your module: modules.ts ``` import { installNuxtSiteConfig, updateSiteConfig } from 'nuxt-site-config/kit' export default defineNuxtModule({ // ... async setup(options) { await installNuxtSiteConfig() // Optional: set some site config from your modules options // This is not recommended, only to keep supporting your modules options updateSiteConfig({ _context: 'my-module', url: options.siteUrl, }) } }) ``` That's it! Explore the documentation to learn more. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/0.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/getting-started/installation/docs/site-config/getting-started/installation.md) **Did this page help you? ** [**Introduction** Learn about the motivation behind Nuxt Site Config and a bit about how it works.](https://nuxtseo.com/docs/site-config/getting-started/installation/docs/site-config/getting-started/introduction) [**Troubleshooting** Debug Nuxt Site Config issues using DevTools, config options, and minimal reproductions.](https://nuxtseo.com/docs/site-config/getting-started/installation/docs/site-config/getting-started/troubleshooting) **On this page** - [Setup Module](#setup-module) - [Configuration](#configuration) - [Module Kit Usage](#module-kit-usage) --- ### Customising the UI · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/advanced/customising-ui Description: Change the look and feel of your sitemap. **Advanced** h1. **Customising the UI** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, improve descriptions, and add sitemap validator links (#539) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/539). [Copy for LLMs h2. [Disabling the XSL](#disabling-the-xsl) What you're looking at when you view the sitemap.xml is a XSL file, think of it just like you would a CSS file for HTML. To view the real sitemap.xml, you can view the source of the page. If you prefer, you can disable the XSL by setting `xsl` to `false`. ``` export default defineNuxtConfig({ sitemap: { xsl: false } }) ``` h2. [Changing the columns](#changing-the-columns) You can change the columns that are displayed in the sitemap by modifying the `xslColumns` option. These have no effect on SEO and is purely for developer experience. Note: You must always have a `URL` column at the start. ``` export default defineNuxtConfig({ sitemap: { xslColumns: [ // URL column must always be set, no value needed { label: 'URL', width: '75%' }, { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' }, ], }, }) ``` The `select` you provide is an XSL expression that will be evaluated against the sitemap entry. It's recommended to prefix the value with `sitemap:` if in doubt. h3. [Example: Adding priority and changefreq](#example-adding-priority-and-changefreq) ``` export default defineNuxtConfig({ sitemap: { xslColumns: [ { label: 'URL', width: '50%' }, { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' }, { label: 'Priority', select: 'sitemap:priority', width: '12.5%' }, { label: 'Change Frequency', select: 'sitemap:changefreq', width: '12.5%' }, ], }, }) ``` h3. [Example: Adding `hreflang`](#example-adding-hreflang) _Requires >= 3.3.2_ ``` export default defineNuxtConfig({ sitemap: { xslColumns: [ { label: 'URL', width: '50%' }, { label: 'Last Modified', select: 'sitemap:lastmod', width: '25%' }, { label: 'Hreflangs', select: 'count(xhtml:link)', width: '25%' }, ], }, }) ``` h2. [Disabling tips](#disabling-tips) In development tips are displayed on the sitemap page to help you get started. You can disable these tips by setting the `xslTips` option to `false`. ``` export default defineNuxtConfig({ sitemap: { xslTips: false, }, }) ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/2.advanced/4.customising-ui.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/advanced/customising-ui/docs/sitemap/advanced/customising-ui.md) **Did this page help you? ** h3. **Related ** [**I18n Integration**](https://nuxtseo.com/docs/sitemap/advanced/customising-ui/docs/sitemap/guides/i18n) [**Config Reference**](https://nuxtseo.com/docs/sitemap/advanced/customising-ui/docs/sitemap/api/config) [**Sitemap Chunking** Split large sitemap sources into multiple files for performance and search engine limits.](https://nuxtseo.com/docs/sitemap/advanced/customising-ui/docs/sitemap/advanced/chunking-sources) [**Config** Configure the sitemap module.](https://nuxtseo.com/docs/sitemap/advanced/customising-ui/docs/sitemap/api/config) **On this page** - [Disabling the XSL](#disabling-the-xsl) - [Changing the columns](#changing-the-columns) - [Disabling tips](#disabling-tips) --- ### Lastmod, Priority, and Changefreq · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/advanced/loc-data Description: Configure lastmod, priority, and changefreq values for your sitemap entries. **Advanced** h1. **Lastmod, Priority, and Changefreq** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, improve descriptions, and add sitemap validator links (#539) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/sitemap/pull/539). [Copy for LLMs h2. [Introduction](#introduction) Changing the `<loc>` entry data can be useful for a variety of reasons, such as changing the `changefreq`, `priority`, or `lastmod` values. If you're using [**Dynamic URLs**](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/guides/dynamic-urls), you can modify the data in the `sitemap` object, otherwise, you will need to override the [**app sources**](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/getting-started/data-sources) directly. While modifying these in most cases may be unnecessary, see [**Best Practices**](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/guides/best-practices), it can be useful when used right. h2. [Setting Defaults](#setting-defaults) While this is not recommended, in special circumstances you may wish to set defaults for your sitemap entries: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { defaults: { lastmod: new Date().toISOString(), priority: 0.5, changefreq: 'weekly' } } }) ``` h2. [Data Source Merging](#data-source-merging) You can provide the page you want to set the `lastmod`, `priority`, or `changefreq` for in your app sources, which includes the `urls` config. nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { urls: [ { loc: '/about', lastmod: '2023-01-01', priority: 0.3, changefreq: 'daily' } ] } }) ``` h2. [Modify Loc Data With Route Rules](#modify-loc-data-with-route-rules) To change the behaviour of your sitemap URLs, you can use [**Route rules**](https://nuxt.com/docs/api/configuration/nuxt-config/#routerules). nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { // Don't add any /secret/** URLs to the sitemap.xml '/secret/**': { robots: false }, // modify the sitemap.xml entry for specific URLs '/about': { sitemap: { changefreq: 'daily', priority: 0.3 } } } }) ``` Alternatively, you can use the experimental macro [`defineRouteRules()`](https://nuxt.com/docs/api/utils/define-route-rules), which must be enabled. pages/index.vue ``` <script setup> defineRouteRules({ sitemap: { changefreq: 'daily', priority: 0.3 } }) </script> ``` h2. [Lastmod: Prerendering Hints](#lastmod-prerendering-hints) When prerendering your site, you can make use of setting the `article:modified_time` meta tag in your page's head. This meta tag will be used as the `lastmod` value in your sitemap. pages/index.vue ``` <script setup> useSeoMeta({ // will be inferred as the lastmod value in the sitemap articleModifiedTime: '2023-01-01' }) </script> ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/2.advanced/0.loc-data.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/advanced/loc-data.md) **Did this page help you? ** h3. **Related ** [**Best Practices**](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/guides/best-practices) [**Dynamic URL Endpoints**](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/guides/dynamic-urls) [**Nuxt Prerendering**](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/guides/prerendering) [**Zero Runtime** Generate sitemaps at build time without runtime overhead.](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/guides/zero-runtime) [**Images, Videos, News** Learn how to add images, videos and news in your sitemap.](https://nuxtseo.com/docs/sitemap/advanced/loc-data/docs/sitemap/advanced/images-videos) **On this page** - [Introduction](#introduction) - [Setting Defaults](#setting-defaults) - [Data Source Merging](#data-source-merging) - [Modify Loc Data With Route Rules](#modify-loc-data-with-route-rules) - [Lastmod: Prerendering Hints](#lastmod-prerendering-hints) --- ### Sitemap Performance · Nuxt Sitemap · Nuxt SEO Source: https://nuxtseo.com/docs/sitemap/advanced/performance Description: Use the default cache engine to keep your sitemaps fast. **Advanced** h1. **Sitemap Performance** Last updated **Dec 16, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [feat: `zeroRuntime` mode (#532)](https://github.com/nuxt-modules/sitemap/pull/532). [Copy for LLMs h2. [Introduction](#introduction) For apps with 100k+ pages, generating a sitemap can be a slow process. As robots will request your sitemap frequently, it's important to keep it fast. Nuxt SEO provides a default cache engine to keep your sitemaps fast and recommendations on how to improve performance. h2. [Performance Recommendations](#performance-recommendations) When dealing with many URLs that are being generated from an external API, the best option is use the `sitemaps` option to create [**Named Sitemap Chunks**](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/sitemap/guides/multi-sitemaps). Each sitemap should contain its own `sources`, this allows other sitemaps to be generated without waiting for this request. ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts: { sources: [ 'https://api.something.com/urls' ] }, }, }, }) ``` If you need to split this up further, you should consider chunking by the type and some pagination format. For example, you can paginate by when posts were created. ``` export default defineNuxtConfig({ sitemap: { sitemaps: { posts2020: { sources: [ 'https://api.something.com/urls?filter[yearCreated]=2020' ] }, posts2021: { sources: [ 'https://api.something.com/urls?filter[yearCreated]=2021' ] }, }, }, }) ``` Additionally, you may want to consider the following experimental options that may help with performance: - `experimentalCompression` - Gzip's and streams the sitemap - `experimentalWarmUp` - Creates the sitemaps when Nitro starts h2. [Zero Runtime Mode](#zero-runtime-mode) If your sitemap URLs only change when you deploy (not at runtime), you can enable `zeroRuntime` to generate sitemaps at build time and eliminate sitemap generation code from your server bundle. ``` export default defineNuxtConfig({ sitemap: { zeroRuntime: true } }) ``` This reduces server bundle size by ~50KB. The sitemap is generated once at build time and served as a static file. See the [**Zero Runtime**](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/sitemap/guides/zero-runtime) guide for details. h2. [Sitemap Caching](#sitemap-caching) Caching your sitemap can help reduce the load on your server and improve performance. By default, SWR caching is enabled on production environments and sitemaps will be cached for 10 minutes. This is configured by overriding your route rules and leveraging the native Nuxt caching. h3. [Cache Time](#cache-time) You can change the cache time by setting the `cacheMaxAgeSeconds` option. ``` export default defineNuxtConfig({ sitemap: { cacheMaxAgeSeconds: 3600 // 1 hour } }) ``` If you want to disable caching, set the `cacheMaxAgeSeconds` to `0`. h3. [Cache Driver](#cache-driver) The cache engine is set to the Nitro default of the `cache/` path. If you want to customise the cache engine, you can set the `runtimeCacheStorage` option. nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { // cloudflare kv binding example runtimeCacheStorage: { driver: 'cloudflare-kv-binding', binding: 'OG_IMAGE_CACHE' } } }) ``` [Edit this page](https://github.com/nuxt-modules/sitemap/edit/main/docs/content/2.advanced/2.performance.md) [Markdown For LLMs](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/sitemap/advanced/performance.md) **Did this page help you? ** h3. **Related ** [**Multi Sitemaps**](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/sitemap/guides/multi-sitemaps) [**Sitemap Chunking**](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/sitemap/advanced/chunking-sources) [**Nuxt Site Config**](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/site-config/getting-started/installation) [**Images, Videos, News** Learn how to add images, videos and news in your sitemap.](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/sitemap/advanced/images-videos) [**Sitemap Chunking** Split large sitemap sources into multiple files for performance and search engine limits.](https://nuxtseo.com/docs/sitemap/advanced/performance/docs/sitemap/advanced/chunking-sources) **On this page** - [Introduction](#introduction) - [Performance Recommendations](#performance-recommendations) - [Zero Runtime Mode](#zero-runtime-mode) - [Sitemap Caching](#sitemap-caching) --- ### defineOgImage() · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/api/define-og-image Description: Define an og:image for the current page. **Nuxt API** h1. **defineOgImage()** Last updated **Apr 28, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: broken grammar & usage Relates to #363](https://github.com/nuxt-modules/og-image/commit/ed33611af919e6e3dca743bd382bfda1c19b9310). [Copy for LLMs h2. [Introduction](#introduction) The `defineOgImage()` composable allows you to define an og:image for the current page. It supports rendering a custom image, using an existing image, or disabling the og:image for the current page. h2. [Props](#props) If you'd like to change the default options for all `defineOgImage` calls, you can do so in the [**Nuxt Config**](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/api/config). h3. [`width`](#width) - Type: `number` - Default: `1200` The width of the image. h3. [`height`](#height) - Type: `number` - Default: `600` The height of the image. h3. [`alt`](#alt) - Type: `string` - Default: `undefined` The alt text of the image. It's recommended to always provide this for accessibility. h3. [`url`](#url) - Type: `string` - Default: `undefined` If you already have a URL of the image to use, you can use this instead of rendering a OG image. ``` defineOgImage({ url: '/my-image.png' }) ``` See [**using an existing image**](#using-an-existing-image) for more details. h3. [`renderer`](#renderer) - Type: `'satori' | 'chromium'` - Default: `'satori'` The renderer to use when generating the image. This is useful if you want to use a different renderer for a specific page. ``` defineOgImage({ component: 'MyCustomComponent', renderer: 'chromium' // generate screenshot of the MyCustomComponent component }) ``` h3. [`extension`](#extension) - Type: `'png' | 'jpeg' | 'jpg'` - Default: `'png'` The extension to use when generating the image. See the [**JPEGs**](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/guides/jpegs) guide for using JPEGs. h3. [`emojis`](#emojis) - Type: `'twemoji' | 'noto' | 'fluent-emoji' | 'fluent-emoji-flat' | 'fluent-emoji-high-contrast' | 'noto-v1' | 'emojione' | 'emojione-monotone' | 'emojione-v1' | 'streamline-emojis' | 'openmoji'` - Default: `'noto'` The emoji set to use when generating the image. h3. [`html`](#html) - Type: `string` - Default: `undefined` Inline HTML to use when generating the image. See the [**inline HTML templates**](#inline-html-templates) section for more details. h3. [`cacheMaxAgeSeconds`](#cachemaxageseconds) - Type: `number` - Default: `60 * 60 * 24 * 3` (3 days) The number of seconds to cache the image for. This is useful for reducing the number of requests to the server. h3. [`resvg`](#resvg) - Type: `ResvgRenderOptions` - Default: `{}` Options to pass to Resvg when generating images. See the [**Resvg docs**](https://github.com/yisibl/resvg-js). h3. [`satori`](#satori) - Type: `SatoriOptions` - Default: `{}` Options to pass to Satori when generating images. See the [**Satori docs**](https://github.com/vercel/satori). h3. [`sharp`](#sharp) - Type: `SharpOptions` - Default: `{}` Options to pass to Sharp when generating images. See the [**Sharp docs**](https://sharp.pixelplumbing.com/). h3. [`screenshot`](#screenshot) - Type: `ScreenshotOptions` - Default: `{}` Options to pass to chromium when generating screenshots. See the [**defineOgImageScreenshot**](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/api/define-og-image-screenshot) documentation for more details. h3. [`fonts`](#fonts) - Type: `InputFontConfig[]` - Default: `[]` Extra fonts to use when rendering this OG image. See the [**Custom Fonts**](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/guides/custom-fonts) documentation for more details. h3. [`component`](#component) - Type: `string` - Default: `NuxtSeo` The component to use when rendering the image. This is useful if you want to use a custom component. ``` defineOgImage({ component: 'MyCustomComponent' }) ``` It's recommended to use the [**defineOgImageComponent**](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/api/define-og-image-component) composable instead of this for better type safety. h3. [`props`](#props-1) - Type: `Record<string, any>` - Default: `undefined` Additional props to pass to the component. This is useful if you want to pass props to a custom component. ``` defineOgImage({ component: 'MyCustomTemplate', props: { myProp: 'myValue' } }) ``` It's recommended to use the [**defineOgImageComponent**](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/api/define-og-image-component) composable instead of this for better type safety. h2. [Usage](#usage) h3. [Inline HTML templates](#inline-html-templates) If you have a simple template and prefer to inline it, you can do so using the `html` prop. ``` defineOgImage({ html: \`<div class="w-full h-full text-6xl flex justify-end items-end bg-blue-500 text-white"> <div class="mb-10 underline mr-10">hello world</div> </div>\`, }) ``` h3. [Using an Existing Image](#using-an-existing-image) When you use `defineOgImage` with a `url` it will determine that you are using an og:image that you have already built. For example, one in your `public` directory, or hosted elsewhere. Using this can be useful for overriding runtime images for specific pages. ``` /* app.vue */ // setting a runtime image for all pages defineOgImage({ component: 'root' }) /* pages/static.vue */ // overriding the image using a prebuilt one defineOgImage({ url: 'https://some-domain.png/static.png', width: 1200, height: 600, alt: 'My Image' }) ``` Only valid Open Graph image properties will work when using `url` such as `alt`, `width`, `height` and `type`. h2. [Disabling the og:image](#disabling-the-ogimage) When you use `defineOgImage` with `false` it will disable the og:image for the current page. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/4.api/0.define-og-image.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/api/define-og-image.md) **Did this page help you? ** [**Error pages** How to display og images for error pages](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/guides/error-pages) [**defineOgImageComponent()** Define an og:image for the current page with type safety.](https://nuxtseo.com/docs/og-image/api/define-og-image/docs/og-image/api/define-og-image-component) **On this page** - [Introduction](#introduction) - [Props](#props) - [Usage](#usage) - [Disabling the og:image](#disabling-the-ogimage) --- ### v5.0.0 · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/releases/v5 Description: Release notes for Nuxt OG Image v5. **Releases** h1. **v5.0.0** Last updated **Jun 26, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: resolve type / lint errors](https://github.com/nuxt-modules/og-image/commit/5fbd8a0c1ad177b3e38b5356ed5adf67aa45c00f). [Copy for LLMs h2. [Breaking Features](#breaking-features) h3. [Nuxt v3.16](#nuxt-v316) To avoid hoisting issues with the new Unhead v2, the Nuxt OG Image v5 requires Nuxt v3.16. Please upgrade your Nuxt to continue using OG Image. ``` nuxi upgrade --force ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/7.releases/3.v5.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/releases/v5/docs/og-image/releases/v5.md) **Did this page help you? ** [**v2 to v3** Migrate Nuxt OG Image v2 to Nuxt OG Image v3.](https://nuxtseo.com/docs/og-image/releases/v5/docs/og-image/migration-guide/v3) [**v4.0.0** Release notes for Nuxt OG Image v4.](https://nuxtseo.com/docs/og-image/releases/v5/docs/og-image/releases/v4) --- ### Chromium Renderer · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/chromium Description: Learn how, when and why to use the Chromium renderer. **Core Concepts** h1. **Chromium Renderer** Last updated **Oct 29, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: fix links](https://github.com/nuxt-modules/og-image/commit/162276b7a50d167bfca52a22540ac0708edc75c3). [Copy for LLMs Nuxt OG Image comes with two ways of rendering your images, the non-default way is using Chromium to take screenshots. Using Chromium is only recommended when you are prerendering all of your images. ``` export default defineNuxtConfig({ // sets the default renderer to chromium ogImage: { defaults: { renderer: 'chromium' } } }) ``` ``` defineOgImageComponent('MyOgImage', { renderer: 'chromium' }) ``` h2. [Pros](#pros) - Much easier to create complex designs - Render JPEGs without using `sharp` - Page screenshots are simple and saves time h2. [Cons](#cons) - Requires a Chromium binary to be installed - Much slower than Satori, using it at runtime is not recommended - Doesn't work on most hosting providers h2. [Development Chromium](#development-chromium) When running in a development environment, a local Chrome / Chromium binary will be used, if available. If it's not, then the Chromium renderer will be disabled. h2. [Prerenderer / CI Chromium](#prerenderer-ci-chromium) When prerendering your images in a CI environment, the module will automatically install a Chromium binary for you. If you'd like to opt-out of this, you should disable the binding. nuxt.config.ts ``` export default defineNuxtConfig({ ogImage: { compatibility: { prerender: { chromium: false } } } }) ``` h2. [Runtime Chromium](#runtime-chromium) Chromium will only be enabled by default in runtime environments if you have explicitly included the `playwright` dependency in your project and the target environment is compatible. ``` pnpm i -D playwright ``` ``` yarn add -D playwright ``` ``` npm install -D playwright ``` Check the [**compatibility**](https://nuxtseo.com/docs/og-image/guides/chromium/docs/og-image/guides/compatibility) guide for more information. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/1.chromium.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/chromium/docs/og-image/guides/chromium.md) **Did this page help you? ** [**Satori Renderer** Learn how to use the Satori renderer.](https://nuxtseo.com/docs/og-image/guides/chromium/docs/og-image/guides/satori) [**Zero Runtime** Create OG Images without the server runtime overhead.](https://nuxtseo.com/docs/og-image/guides/chromium/docs/og-image/guides/zero-runtime) **On this page** - [Pros](#pros) - [Cons](#cons) - [Development Chromium](#development-chromium) - [Prerenderer / CI Chromium](#prerenderer-ci-chromium) - [Runtime Chromium](#runtime-chromium) --- ### Tutorial: Your first OG Image · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image Description: Get started with the module by setting up your first og:image on your home page. **Getting Started** h1. **Tutorial: Your first OG Image** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#406) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/406). [Copy for LLMs This is a three-part tutorial to help you get familiar with the module. It's recommended to follow this guide when you use the module for the first time. - [**Part 1: Using An OG Image**](#part-1-using-an-og-image) - [**Part 2: Customising NuxtSeo Template**](#part-2-customising-nuxtseo-template) - [**Part 3: Creating Your Own Template**](#part-3-creating-your-own-template) h2. [Part 1: Using An OG Image](#part-1-using-an-og-image) To start with, we just want to be able to see the module generating an image for us, any image, and play around with different options we can provide to change it. **Prerequisites:**[**Install the module**](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/getting-started/installation) and enable [**Nuxt DevTools**](https://devtools.nuxt.com/). h3. [1. Define an OG Image](#_1-define-an-og-image) Firstly, we're going to use the server-only composable `defineOgImageComponent` to define the `og:image` for our home page. pages/index.vue ``` <script lang="ts" setup> defineOgImageComponent('NuxtSeo') </script> ``` This will use the default template [**NuxtSeo**](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/api/nuxt-seo-template). h3. [2. View your `og:image`](#_2-view-your-ogimage) Visit the home page in your browser and open up the Nuxt DevTools (`Shift` + `Alt` + `D`). Once you're in the Nuxt DevTools, you can navigate to the OG Image tab by opening the command palette (`Ctrl` + `K`) and typing `og`. You should now see a preview of your OG Image. ![NuxtSeo Template](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/og-image/tutorial/0-hello.png) h2. [Part 2: Customising NuxtSeo Template](#part-2-customising-nuxtseo-template) Now that we can see our OG Image, we're going to customize it by modifying the props we pass to the `defineOgImageComponent` composable. Feel free to pass in any props you like, but for this example we're going to use the following: pages/index.vue ``` <script lang="ts" setup> defineOgImageComponent('NuxtSeo', { title: 'Hello OG Image 👋', description: 'Look at me in dark mode', theme: '#ff0000', colorMode: 'dark', }) </script> ``` The playground has full HMR, so you should see the updated image immediately. ![NuxtSeo Template](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/og-image/tutorial/1-customize.png) Congrats, you've set up and customized your first `og:image`! Going further, we can even try using one of the other community templates available. For the full list, check out the `Community` tab within Nuxt DevTools. pages/index.vue ``` <script lang="ts" setup> defineOgImageComponent('Nuxt', { headline: 'Greetings', title: 'Hello OG Image 👋', description: 'Look at me using the Nuxt template', }) </script> ``` ![NuxtSeo Template](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/og-image/tutorial/2-alt-template.png) You can see all the supported props on the [**NuxtSeo Template**](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/api/nuxt-seo-template) documentation. It's also worth checking out the [**defineOgImage API**](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/api/define-og-image). h2. [Part 3: Creating Your Own Template](#part-3-creating-your-own-template) Using the community templates is a fun way to experiment with the OG Image template you want to use. However, they will always be limited in what you can do with them. For this reason, it's recommended to copy and paste the template you want to use into your project and customize it from there. You can find the template source code within the `Community` tab of Nuxt DevTools or on [**GitHub**](https://github.com/nuxt-modules/og-image/tree/main/src/runtime/app/components/Templates/Community). h3. [1. Create your template component](#_1-create-your-template-component) We're going to start with the [**SimpleBlog**](https://github.com/nuxt-modules/og-image/blob/main/src/runtime/app/components/Templates/Community/SimpleBlog.vue) template as a quick way to get started. Let's copy this template into our project at `./components/OgImage/BlogPost.vue` and remove some of the boilerplate. Any components you add to an `OgImage` folder will be automatically registered as templates for you. components/OgImage/BlogPost.vue ``` <script setup lang="ts"> withDefaults(defineProps<{ title?: string }>(), { title: 'title', }) </script> <template> <div class="h-full w-full flex items-start justify-start border-solid border-blue-500 border-[12px] bg-gray-50"> <div class="flex items-start justify-start h-full"> <div class="flex flex-col justify-between w-full h-full"> <h1 class="text-[80px] p-20 font-black text-left"> {{ title }} </h1> <p class="text-2xl pb-10 px-20 font-bold mb-0"> mycoolsite.com </p> </div> </div> </div> </template> ``` h3. [2. Use the new template](#_2-use-the-new-template) Now that you have your template, you can use it for your home page. pages/index.vue ``` <script lang="ts" setup> defineOgImageComponent('BlogPost', { title: 'Is this thing on?' }) </script> ``` Check your Nuxt DevTools to see the new template in action. You may notice that the Tailwind classes just work, even if you're not using the Tailwind module. In fact, UnoCSS and Tailwind classes are supported out of the box and will be merged with your default theme config. You can learn more about this in the [**Styling**](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/guides/styling) guide. h3. [3. Customize your template](#_3-customize-your-template) Now that you have your template, you can start customizing it. Any props you pass to the `defineOgImageComponent` composable will be available in the component. With this in mind, let's add a new prop to change the border color: `borderColor`. It's recommended to always use a `withDefaults` wrapper around your props to provide default values. This allows you to preview the template when you're not passing any props. components/OgImage/BlogPost.vue ``` <script setup lang="ts"> withDefaults(defineProps<{ title?: string borderColor?: string }>(), { title: 'title', borderColor: 'blue-500' }) </script> <template> <div :class="[\`border-${borderColor}\`]" class="h-full w-full flex items-start justify-start border-solid border-[12px] bg-gray-50"> <div class="flex items-start justify-start h-full"> <div class="flex flex-col justify-between w-full h-full"> <h1 class="text-[80px] p-20 font-black text-left"> {{ title }} </h1> <p class="text-2xl pb-10 px-20 font-bold mb-0"> mycoolsite.com </p> </div> </div> </div> </template> ``` Now let's customize the border to be a light green instead. pages/index.vue ``` <script lang="ts" setup> defineOgImageComponent('BlogPost', { title: 'Is this thing on?', borderColor: 'green-300', }) </script> ``` Within the playground, you should now see the border color change to green. [](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/tools/social-share-debugger)**Test your OG images** - See how your images appear on social platforms with our [**Social Share Debugger**](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/tools/social-share-debugger). h2. [Conclusion](#conclusion) Thanks for following along! You now have a basic understanding of how to use the module. It's recommended to look through the rest of the documentation to get a full understanding of what's possible. If you have any questions, feel free to reach out on [**Discord**](https://discord.gg/275MBUBvgP) or [**GitHub**](https://github.com/nuxt-modules/og-image). [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/0.getting-started/5.getting-familiar-with-nuxt-og-image.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image.md) **Did this page help you? ** [**Troubleshooting** Create minimal reproductions for Nuxt OG Image or just experiment with the module.](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/getting-started/troubleshooting) [**Nuxt Content** How to use the Nuxt OG Image module with Nuxt Content.](https://nuxtseo.com/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image/docs/og-image/integrations/content) **On this page** - [Part 1: Using An OG Image](#part-1-using-an-og-image) - [Part 2: Customising NuxtSeo Template](#part-2-customising-nuxtseo-template) - [Part 3: Creating Your Own Template](#part-3-creating-your-own-template) - [Conclusion](#conclusion) --- ### Install Nuxt OG Image · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/getting-started/installation Description: Get started with Nuxt OG Image by installing the dependency to your project. **Getting Started** h1. **Install Nuxt OG Image** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#406) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/406). [Copy for LLMs h2. [Setup Module](#setup-module) Want to know why you might need this module? Check out the [**introduction**](https://nuxtseo.com/docs/og-image/getting-started/installation/docs/og-image/getting-started/introduction). To get started with Nuxt OG Image, you need to install the dependency and add it to your Nuxt config. `npx nuxt module add og-image` `npm i nuxt-og-image` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-og-image', ], }) ``` `yarn add nuxt-og-image` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-og-image', ], }) ``` `pnpm i nuxt-og-image` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-og-image', ], }) ``` `bun i nuxt-og-image` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-og-image', ], }) ``` h2. [Verifying Installation](#verifying-installation) Out-of-the-box the module will not do anything until you configure it. You can verify the module is installed correctly by checking the [**Nuxt DevTools**](https://devtools.nuxt.com/) for the OG Image tab. The DevTools is the starting point, providing a playground to design and test your OG Image with full HMR support. h2. [Configuration](#configuration) OG Images must be served with absolute URLs, if you're prerendering, you will need to provide a Site URL. [Site Config Quick Setup](https://nuxtseo.com/docs/og-image/getting-started/installation/docs/site-config/getting-started/introduction) nuxt.config.ts ``` export default defineNuxtConfig( site: { url: 'https://example.com', name: 'My Awesome Website' }, }) ``` .env ``` NUXT_SITE_URL=https://example.comNUXT_SITE_NAME=My Awesome Website ``` For more advanced configurations, check out the [site config guide](https://nuxtseo.com/docs/og-image/getting-started/installation/docs/site-config/guides/setting-site-config). h2. [Next Steps](#next-steps) You've successfully installed Nuxt OG Image, but you need to make your first OG Image. Follow the [**Getting Familiar with Nuxt OG Image**](https://nuxtseo.com/docs/og-image/getting-started/installation/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image) tutorial to get started. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/0.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/getting-started/installation/docs/og-image/getting-started/installation.md) **Did this page help you? ** [**Introduction** Generate OG Images with Vue templates in Nuxt.](https://nuxtseo.com/docs/og-image/getting-started/installation/docs/og-image/getting-started/introduction) [**Troubleshooting** Create minimal reproductions for Nuxt OG Image or just experiment with the module.](https://nuxtseo.com/docs/og-image/getting-started/installation/docs/og-image/getting-started/troubleshooting) **On this page** - [Setup Module](#setup-module) - [Verifying Installation](#verifying-installation) - [Configuration](#configuration) - [Next Steps](#next-steps) --- ### Route Rules · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/route-rules Description: Learn how to use route rules to customise your OG Image. **Core Concepts** h1. **Route Rules** Last updated **Sep 5, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: wrong folder name](https://github.com/nuxt-modules/og-image/commit/f63db7399eaf2666ed8977742c41baa9cba1e809). [Copy for LLMs In some cases, you'll want to apply OG Image setting for a subset of pages. You can handle this even easier with the route rule merging. This lets you provide a `ogImage` key that will be either used or merged into the existing OG Image options. For example, this documentation website uses it to set the `icon` depending on your path. nuxt.config.ts ``` export default defineNuxtConfig({ routeRules: { '/og-image/**': { ogImage: { props: { icon: 'carbon:image-search' } } } } }) ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/2.route-rules.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/route-rules/docs/og-image/guides/route-rules.md) **Did this page help you? ** [**Compatibility** Learn what environments can use what features.](https://nuxtseo.com/docs/og-image/guides/route-rules/docs/og-image/guides/compatibility) [**Caching Images** Getting to know how the Caching works with Nuxt OG Image.](https://nuxtseo.com/docs/og-image/guides/route-rules/docs/og-image/guides/cache) --- ### Troubleshooting Nuxt OG Image · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/getting-started/troubleshooting Description: Create minimal reproductions for Nuxt OG Image or just experiment with the module. **Getting Started** h1. **Troubleshooting Nuxt OG Image** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, code errors, and add social share debugger links (#405) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/405). [Copy for LLMs h2. [Debugging](#debugging) h3. [Nuxt DevTools](#nuxt-devtools) The best tool for debugging is the Nuxt DevTools integration with Nuxt OG Image. This will show you your OG Image and give you all of the debug information. h3. [Debug Config](#debug-config) You can enable the [**debug**](https://nuxtseo.com/docs/og-image/getting-started/troubleshooting/docs/og-image/api/config#debug) option which will give you more granular output. h2. [Submitting an Issue](#submitting-an-issue) You can use the following Stackblitz playgrounds to experiment with Nuxt OG Image and submit issues. If you run into any issues with Nuxt OG Image, it's recommended to clone of these playgrounds Stackblitz to reproduce the issue. - [**Nuxt OG Image**](https://stackblitz.com/edit/nuxt-starter-pxs3wk?file=pages/index.vue) - [**Nuxt OG Image x Nuxt Content v2**](https://stackblitz.com/edit/github-hgunsf?file=package.json) - [**Nuxt OG Image x Nuxt Content v3**](https://stackblitz.com/edit/github-hgunsf-wd8esdec) - [**Nuxt OG Image x Nuxt I18n**](https://stackblitz.com/edit/nuxt-starter-uw7pqmxg?file=nuxt.config.ts) h3. [Stackblitz Compatibility](#stackblitz-compatibility) StackBlitz runs Nuxt within a webcontainer, so it has fairly limited compatibility. - You can't use anything that will require a fetch request to a different server (e.g. Google Fonts, custom Emojis, images, etc). - The `chromium` renderer is not supported - `sharp` is not supported, so you can't use JPEGs - `inline-css` is not supported, so you can't `<style>` blocks h2. [Debugging Tools](#debugging-tools) - [**Social Share Debugger**](https://nuxtseo.com/docs/og-image/getting-started/troubleshooting/tools/social-share-debugger) - Preview OG images across Twitter, Facebook, LinkedIn, Slack and Discord. Clear platform caches instantly. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/0.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/getting-started/troubleshooting/docs/og-image/getting-started/troubleshooting.md) **Did this page help you? ** [**Installation** Get started with Nuxt OG Image by installing the dependency to your project.](https://nuxtseo.com/docs/og-image/getting-started/troubleshooting/docs/og-image/getting-started/installation) [**Tutorial: Your first OG Image** Get started with the module by setting up your first og:image on your home page.](https://nuxtseo.com/docs/og-image/getting-started/troubleshooting/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image) **On this page** - [Debugging](#debugging) - [Submitting an Issue](#submitting-an-issue) - [Debugging Tools](#debugging-tools) --- ### Satori Renderer · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/satori Description: Learn how to use the Satori renderer. **Core Concepts** h1. **Satori Renderer** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#406) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/406). [Copy for LLMs Nuxt OG Image comes with two ways of rendering your images, the default way is using [**Satori**](https://github.com/vercel/satori). It's not necessary to manually define the renderer as `satori` unless you set the default to the [**Chromium Renderer**](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/guides/chromium). Satori will be used to render images in all environments, unless you have explicitly disabled it. ``` export default defineNuxtConfig({ // this is not necessary as satori is already the default ogImage: { defaults: { renderer: 'satori' } } }) ``` ``` defineOgImageComponent('MyOgImage', { renderer: 'satori' // only when the default has been changed }) ``` h2. [Pros](#pros) - Fast - Works on all environments h2. [Cons](#cons) - A number of limitations (see below) h2. [Limitations](#limitations) It's important to familiarize yourself with Satori before you make more complex templates. Satori has CSS limitations. For complex templates, consider the [**Chromium renderer**](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/guides/chromium). If you find Satori too difficult to work with, you can use the [**Chromium Renderer**](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/guides/chromium) renderer instead. To learn more about the limitations and working around them, see: - [**Styling**](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/guides/styling) - [**Icons and Images**](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/guides/icons-and-images) [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/0.satori.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/guides/satori.md) **Did this page help you? ** [**Nuxt Color Mode** How to use the Nuxt OG Image module with Nuxt Color Mode.](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/integrations/color-mode) [**Chromium Renderer** Learn how, when and why to use the Chromium renderer.](https://nuxtseo.com/docs/og-image/guides/satori/docs/og-image/guides/chromium) **On this page** - [Pros](#pros) - [Cons](#cons) - [Limitations](#limitations) --- ### Zero Runtime · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/zero-runtime Description: Create OG Images without the server runtime overhead. **Core Concepts** h1. **Zero Runtime** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#406) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/406). [Copy for LLMs When using Nuxt as a server-side app with prerendered OG Images, you are forced to have runtime OG images as well which added significant overhead to the server build. To address this, we introduce the `zeroRuntime` config, which ensures all tree-shakable OG image code is removed from the final output. h2. [Usage](#usage) Enable zero runtime when your OG images only change at build time (not dynamically at runtime): To enable zero runtime, add the following to your config: nuxt.config.ts ``` export default { ogImage: { zeroRuntime: true } } ``` You will need to make sure you're prerendering all of your pages that use OG images, as the server runtime will not be available. ``` export default defineNuxtConfig({ nitro: { prerender: { crawlLinks: true, routes: [ '/', // ... ], }, } }) ``` h2. [Limitations](#limitations) When using the Zero Runtime mode none of the community templates will be available in your final build, you must copy+paste any community template that you wish to use into your `components/OgImage` folder. h2. [Benchmarks](#benchmarks) **Nuxt OG Image - Defaults** Nitro: 1.6mb - node_modules: 25mb **Nuxt OG Image - Zero Runtime** Nitro: 306kb (81% reduction) - node_modules: 2.0mb (92% reduction) [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/1.zero-runtime.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/zero-runtime/docs/og-image/guides/zero-runtime.md) **Did this page help you? ** [**Chromium Renderer** Learn how, when and why to use the Chromium renderer.](https://nuxtseo.com/docs/og-image/guides/zero-runtime/docs/og-image/guides/chromium) [**Compatibility** Learn what environments can use what features.](https://nuxtseo.com/docs/og-image/guides/zero-runtime/docs/og-image/guides/compatibility) **On this page** - [Usage](#usage) - [Limitations](#limitations) - [Benchmarks](#benchmarks) --- ### Compatibility · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/compatibility Description: Learn what environments can use what features. **Core Concepts** h1. **Compatibility** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, code errors, and add social share debugger links (#405) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/405). [Copy for LLMs Nuxt OG Image relies heavily on third-party packages that have different compatibility requirements. h2. [Bindings](#bindings) There are a number of bindings that can be used for each dependency: `node`, `wasm`, `wasm-fs` and `false`. - `node` binding is for default Node based environments. - `wasm` and `wasm-fs` bindings are used to run WebAssembly. They are mostly used for WebWorker support. - `wasm-fs` binding is for using WebAssembly in a development WebWorker development environment such as StackBlitz. - `false` disables the dependency. h2. [Dependencies](#dependencies) h3. [`chromium`](#chromium) **Supports**: `node` Used to render browser screenshots. This only works on Node based environments. However, it does not work on AWS Lambda as the Chromium binary is too large. Due to the rendering times being slow, it's recommended to only use browser screenshots when prerendering them. h3. [`satori`](#satori) **Supports**: `node`, `wasm`, `wasm-fs` Used to render a HTML vNode tree into OG Images. This is the default renderer, and works on all environments. h3. [`resvg`](#resvg) **Supports**: `node`, `wasm`, `wasm-fs` Used to transform SVGs into PNGs. This should work on all environments. When installing using the `node` binding, it can have issues resolving the correct binary. You may run into an issue like `Cannot resolve "./resvgjs.android-arm64.node"`, in which case you should manually set the binding to `wasm` or `wasm-fs`. h3. [`sharp`](#sharp) **Supports**: `node` Used to transform PNG to JPEG. This only works on Node based environments. The dependency is quite heavy so it's disabled by default. See the [**JPEGs**](https://nuxtseo.com/docs/og-image/guides/compatibility/docs/og-image/guides/jpegs) guide for more information. h3. [`css-inline`](#css-inline) **Supports**: `node`, `wasm`, `wasm-fs` Used to support `<style>` tags within Vue SFCs. Powered by [**css-inline**](https://github.com/Stranger6667/css-inline). h2. [Overriding compatibility](#overriding-compatibility) In some instances, it may be useful to override the compatibility, so you can toggle features or use more optimised bindings for your environment. ``` export default defineNuxtConfig({ ogImage: { compatibility: { // disable chromium dependency for prerendering (skips the chromium install in CIs) prerender: { chromium: false } } } }) ``` h2. [Provider compatibility](#provider-compatibility) h3. [Development](#development) - `sharp` - will use your local Sharp install - `chromium` will use your local Chromium install, if available h3. [Prerendering](#prerendering) - `chromium` - will use your local Chromium install or install a chromium binary if not found h3. [AWS Lambda, Netlify, Vercel](#aws-lambda-netlify-vercel) - `chromium` - can't be used due to the binary size - `sharp` - can't be used due to some post-install scripts issue h3. [Vercel Edge, Netlify Edge, Cloudflare Pages](#vercel-edge-netlify-edge-cloudflare-pages) - `chromium` - can't be used, no WASM support - `sharp` - can't be used, no WASM support h3. [Cloudflare Workers](#cloudflare-workers) - `chromium` - can't be used, no WASM support - `sharp` - can't be used, no WASM support - `css-inline` - can't be used, no WASM support There is an [**open issue**](https://github.com/harlan-zw/nuxt-og-image/issues/63) for custom fonts and images being broken in Cloudflare Workers. Please reply to the issue if you need this fixed. h3. [StackBlitz](#stackblitz) - `chromium` - can't be used, no WASM support - `sharp` - can't be used, no WASM support h2. [Provider Examples](#provider-examples) All examples are generated from the [**Nuxt OG Image Playground**](https://github.com/harlan-zw/nuxt-og-image-playground) GitHub repo. - [**Netlify**](https://main--nuxt-og-image-playground-netlify.netlify.app) ![](https://nuxt-og-image-playground-netlify.netlify.app/__og-image__/image/og.png?title=Hello+Netlify+%F0%9F%91%8B&description=This+is+a+test+of+Netlify+provider&theme=%2332e6e2) - [**Netlify Edge**](https://nuxt-og-image-playground-netlify-edge.netlify.app/) ![](https://nuxt-og-image-playground-netlify-edge.netlify.app/__og-image__/image/og.png?title=Hello+Netlify+Edge+%F0%9F%91%8B&description=This+is+a+test+of+Netlify+Edge+provider&theme=%2332e6e2) - [**Vercel**](https://nuxt-og-image-playground-harlan-zw.vercel.app/) ![](https://nuxt-og-image-playground-harlan-zw.vercel.app/__og-image__/image/og.png?title=Hello+Vercel+%F0%9F%91%8B&description=This+is+a+test+of+Vercel+provider&theme=%23121212) - [**Vercel Edge**](https://nuxt-og-image-playground-gkdt.vercel.app/) ![](https://nuxt-og-image-playground-gkdt.vercel.app/__og-image__/image/og.png?title=Hello+Vercel+Edge+%F0%9F%91%8B&description=This+is+a+test+of+Vercel+Edge+provider&theme=%23121212) - [**Cloudflare Pages**](https://nuxt-og-image-playground.pages.dev/) ![](https://nuxt-og-image-playground.pages.dev/__og-image__/image/og.png?title=Hello+Cloudflare+Pages+%F0%9F%91%8B&description=This+is+a+test+of+Cloudflare+Pages+provider&theme=%23f6821f) - [**Cloudflare Workers**](https://playground.harlanzw.workers.dev/) ![](https://playground.harlanzw.workers.dev/__og-image__/image/og.png?title=Hello+Cloudflare+Workers+%F0%9F%91%8B&description=This+is+a+test+of+Cloudflare+Workers+provider&theme=%23f6821f) [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/2.compatibility.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/compatibility/docs/og-image/guides/compatibility.md) **Did this page help you? ** [**Zero Runtime** Create OG Images without the server runtime overhead.](https://nuxtseo.com/docs/og-image/guides/compatibility/docs/og-image/guides/zero-runtime) [**Route Rules** Learn how to use route rules to customise your OG Image.](https://nuxtseo.com/docs/og-image/guides/compatibility/docs/og-image/guides/route-rules) **On this page** - [Bindings](#bindings) - [Dependencies](#dependencies) - [Overriding compatibility](#overriding-compatibility) - [Provider compatibility](#provider-compatibility) - [Provider Examples](#provider-examples) --- ### JPEGs · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/jpegs Description: Learn how to generate JPEG images instead of PNG for Nuxt OG Image. **Core Concepts** h1. **JPEGs** Last updated **Oct 29, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: fix links](https://github.com/nuxt-modules/og-image/commit/162276b7a50d167bfca52a22540ac0708edc75c3). [Copy for LLMs The default image extension generated by Nuxt OG Image for Satori images is a `png`. PNGs are great for most use cases, but they come at the cost of a larger file size. You can opt-in we can render JPEGs instead of PNG but it requires Sharp. This is disabled by default as Sharp is a heavy dependency and [**compatibility**](https://nuxtseo.com/docs/og-image/guides/jpegs/docs/og-image/guides/compatibility) is limited. If you're prerendering your images or using a Node based environment, you can enable Sharp to render JPEGs. For Chromium rendering, `jpeg` images are rendered by default. h2. [Setup](#setup) To install Sharp, you need to install the `sharp` dependency: ``` pnpm i -D sharp ``` ``` yarn add -D sharp ``` ``` npm install -D sharp ``` Now you can change your default extension to either `jpeg` or `jpg`. nuxt.config.ts ``` export default defineNuxtConfig({ ogImage: { defaults: { extension: 'jpeg', } }, }) ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/3.jpegs.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/jpegs/docs/og-image/guides/jpegs.md) **Did this page help you? ** [**Caching Images** Getting to know how the Caching works with Nuxt OG Image.](https://nuxtseo.com/docs/og-image/guides/jpegs/docs/og-image/guides/cache) [**Custom Fonts** Using custom fonts in your OG Images.](https://nuxtseo.com/docs/og-image/guides/jpegs/docs/og-image/guides/custom-fonts) --- ### Caching Images · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/cache Description: Getting to know how the Caching works with Nuxt OG Image. **Core Concepts** h1. **Caching Images** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, code errors, and add social share debugger links (#405) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/405). [Copy for LLMs In cases where you need to generate images at runtime, Nuxt OG Image provides a caching layer to reduce the load on your server. This caching layer uses SWR caching is enabled by default with a cache time of 72 hours. h2. [Cache Storage](#cache-storage) Nitro caching by default will use the memory as a cache storage. This means that if you restart your server, the cache will be cleared. It's recommended to set a persistent cache storage. This can be done using the `runtimeCacheStorage` option. The option takes the same configuration as the Nuxt `nitro.storage` option. See the [**Nitro Storage Layer**](https://nitro.unjs.io/guide/storage) documentation for more details. For example: ``` export default defineNuxtConfig({ ogImage: { // cloudflare kv binding example, set your own config runtimeCacheStorage: { driver: 'cloudflare-kv-binding', binding: 'OG_IMAGE_CACHE' } } }) ``` h2. [Cache Time](#cache-time) You can change the cache time of an image by providing `cacheMaxAgeSeconds` in milliseconds when defining the image. ``` defineOgImage({ cacheMaxAgeSeconds: 30 // 30 seconds }) ``` Alternatively, you can change the default cache time in your nuxt.config. nuxt.config.ts ``` export default defineNuxtConfig({ ogImage: { defaults: { cacheMaxAgeSeconds: 60 * 60 * 24 * 7 // 7 days } } }) ``` h2. [Purging the cache](#purging-the-cache) If you need to purge the cache, you can do so by visiting the OG Image URL appended with a `?purge` query param. For example, to purge the OG Image cache for this page you could visit: ``` https://nuxtseo.com/__og-image__/image/og-image/guides/cache/og.png?purge ``` h2. [Bypassing the cache](#bypassing-the-cache) While not recommended, if you prefer to opt-out of caching, you can do so by providing a `0` second `cacheMaxAgeSeconds` or disabling `runtimeCacheStorage`. ``` <script lang="ts" setup> defineOgImage({ cacheMaxAgeSeconds: 0 // disable at an individual image level }) </script> ``` ``` export default defineNuxtConfig({ ogImage: { // disable at a global level runtimeCacheStorage: false, } }) ``` [](https://nuxtseo.com/docs/og-image/guides/cache/tools/social-share-debugger)**Clear platform caches** - Use our [**Social Share Debugger**](https://nuxtseo.com/docs/og-image/guides/cache/tools/social-share-debugger) to force Twitter, Facebook, and LinkedIn to re-fetch your OG images. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/3.cache.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/cache/docs/og-image/guides/cache.md) **Did this page help you? ** [**Route Rules** Learn how to use route rules to customise your OG Image.](https://nuxtseo.com/docs/og-image/guides/cache/docs/og-image/guides/route-rules) [**JPEGs** Learn how to generate JPEG images instead of PNG for Nuxt OG Image.](https://nuxtseo.com/docs/og-image/guides/cache/docs/og-image/guides/jpegs) **On this page** - [Cache Storage](#cache-storage) - [Cache Time](#cache-time) - [Purging the cache](#purging-the-cache) - [Bypassing the cache](#bypassing-the-cache) --- ### Non-English Locales · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/non-english-locales Description: How to use the Nuxt OG Image module with non-english locales. **Core Concepts** h1. **Non-English Locales** Last updated **Oct 29, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: fix links](https://github.com/nuxt-modules/og-image/commit/162276b7a50d167bfca52a22540ac0708edc75c3). [Copy for LLMs To render Satori images correctly, the module provides the [**default font Inter**](https://nuxtseo.com/docs/og-image/guides/non-english-locales/docs/og-image/guides/custom-fonts). Inter does not support non-english character, so you will need to switch the font when rendering in different languages. The [**Noto Typeface**](https://fonts.google.com/noto) from Google Fonts is a good option for this as they support a wide range of languages. h3. [Example: Chinese](#example-chinese) ``` export default defineNuxtConfig({ ogImage: { fonts: [ 'Noto+Sans+SC:400' ] } }) ``` ``` <script lang="ts" setup> defineOgImageComponent('NuxtSeo', { title: '中文測試中文測試中文測試中文測試中文測試中文測試中文測試中文測試', }) </script> ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/5.non-english-locales.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/non-english-locales/docs/og-image/guides/non-english-locales.md) **Did this page help you? ** [**Custom Fonts** Using custom fonts in your OG Images.](https://nuxtseo.com/docs/og-image/guides/non-english-locales/docs/og-image/guides/custom-fonts) [**Emojis** Use emojis in your OG Images.](https://nuxtseo.com/docs/og-image/guides/non-english-locales/docs/og-image/guides/emojis) --- ### Emojis · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/emojis Description: Use emojis in your OG Images. **Core Concepts** h1. **Emojis** Last updated **Sep 5, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: wrong folder name](https://github.com/nuxt-modules/og-image/commit/f63db7399eaf2666ed8977742c41baa9cba1e809). [Copy for LLMs Nuxt OG Image integrates with the [**Iconify API**](https://iconify.design/docs/api/) to provide support for a number of emojis families. Supported families: `twemoji`, `noto`, `fluent-emoji`, `fluent-emoji-flat`, `fluent-emoji-high-contrast`, `noto-v1`, `emojione`, `emojione-monotone`, `emojione-v1`, `streamline-emojis`, `openmoji` The default emoji family is `noto`. h2. [How it works](#how-it-works) There is a Regex that detects unicode emoji characters in your template. When it finds them, it will map the characters to the emoji name. For example, the unicode character `U+1F600` (`😀`) will be mapped to `grinning-face`. Once we have the emoji name, we can use the Iconify API to fetch the SVG for that emoji, for example `https://api.iconify.design/noto/grinning-face.svg`. You should be mindful of the number of emojis you use in your template, as each one will result in a separate API request. h2. [Configuring the emoji family](#configuring-the-emoji-family) You can set the default emoji family within your module config. nuxt.config.ts ``` export default defineNuxtConfig({ ogImage: { defaults: { emojis: 'twemoji' } } }) ``` h2. [Per-page emoji family](#per-page-emoji-family) You can also set the emoji family on a per-page basis. pages/index.vue ``` defineOgImage({ emojis: 'twemoji' }) ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/6.emojis.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/emojis/docs/og-image/guides/emojis.md) **Did this page help you? ** [**Non-English Locales** How to use the Nuxt OG Image module with non-english locales.](https://nuxtseo.com/docs/og-image/guides/emojis/docs/og-image/guides/non-english-locales) [**Icons and Images** How to use icons and images in your templates.](https://nuxtseo.com/docs/og-image/guides/emojis/docs/og-image/guides/icons-and-images) **On this page** - [How it works](#how-it-works) - [Configuring the emoji family](#configuring-the-emoji-family) - [Per-page emoji family](#per-page-emoji-family) --- ### Styling · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/styling Description: How to style your OG Images. **Core Concepts** h1. **Styling** Last updated **Oct 29, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: fix links](https://github.com/nuxt-modules/og-image/commit/162276b7a50d167bfca52a22540ac0708edc75c3). [Copy for LLMs Due to Nuxt OG Image using Satori to render OG Images, there a number of limitations in how you can style your templates. h2. [Layout Constraints](#layout-constraints) - Satori does not support `inline`, `block` or `grid` layouts. Everything is a flexbox with a `flex-direction` of `column`. You should design your templates with this in mind. h2. [UnoCSS / Tailwind Classes](#unocss-tailwind-classes) Out-of-the-box and without any extra configuration, you can use UnoCSS and Tailwind classes in your templates. ``` <!-- just works --> <div class="bg-red-500 text-white p-4"> <h1 class="text-[60px]">Hello World</h1> </div> ``` Your theme configuration will be automatically set when you're using the `@nuxtjs/tailwind` or `@unocss/nuxt` modules. ``` <template> <div class="full centered bg-primary-500/50 text-base"> <h1 class="text-mega-big"> Custom Theme Classes! </h1> </div> </template> ``` h2. [Inline Styles](#inline-styles) There are certain cases where the utility classes won't be enough to style your templates. In these cases, you can use inline styles to style your templates. ``` <template> <div :style="background-image: url(https://example.com/bg.png)"> <h1 class="text-mega-big" style="color: red;"> Custom Theme Classes! </h1> </div> </template> ``` h2. [`<style>` tag support](#style-tag-support) You can use `<style>` tags within your templates, however, it requires the `inline-css` dependency, which has limited compatibility with WebWorker environments. See the [**Compatibility**](https://nuxtseo.com/docs/og-image/guides/styling/docs/og-image/guides/compatibility) guide for more information. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/7.styling.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/styling/docs/og-image/guides/styling.md) **Did this page help you? ** [**Icons and Images** How to use icons and images in your templates.](https://nuxtseo.com/docs/og-image/guides/styling/docs/og-image/guides/icons-and-images) [**Community Templates** Community templates that are included with the module, including NuxtSeo.](https://nuxtseo.com/docs/og-image/guides/styling/docs/og-image/guides/community-templates) **On this page** - [Layout Constraints](#layout-constraints) - [UnoCSS / Tailwind Classes](#unocss-tailwind-classes) - [Inline Styles](#inline-styles) - [<style> tag support](#style-tag-support) --- ### Community Templates · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/community-templates Description: Community templates that are included with the module, including NuxtSeo. **Core Concepts** h1. **Community Templates** Last updated **May 19, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: broken images Fixes https://github.com/harlan-zw/nuxt-seo/issues/423](https://github.com/nuxt-modules/og-image/commit/eaa058bac423a1f0e5f86025836e7240b1658047). [Copy for LLMs h2. [NuxtSeo](#nuxtseo) ![NuxtSeo Template](https://nuxtseo.com/docs/og-image/guides/community-templates/og-image/community-template.png) The `NuxtSeo` template is the default one provided for you. It comes with a number of props to let you customise it however you like. Like all Community templates, it's recommended to copy+paste it into your project if you want to customise it. You can find the source on [**GitHub**](https://github.com/nuxt-modules/og-image/blob/main/src/runtime/app/components/Templates/Community/Nuxt.vue). h2. [Props](#props) h3. [`title`](#title) **Type:** `string`**Default:** `<title>` The title of the page. This will be used as the main heading. h3. [`description`](#description) **Type:** `string`**Default:** `<meta name="description" />` The description of the page. This will be used as the subheading. h3. [`icon`](#icon) **Type:** `string` | `boolean`**Default:** `false` The icon of the page. This will be used as the main image. Requires Nuxt Icon to be installed. h3. [`siteName`](#sitename) **Type:** `string`**Default:** Site Name from [**Nuxt Site Config**](https://nuxtseo.com/docs/og-image/guides/community-templates/site-config/guides/setting-site-config) Sets the bottom centered text of the template. h3. [`siteLogo`](#sitelogo) **Type:** `string` Replaces the site name and the Nuxt Seo logo with a custom image. See the [**Icons and Images**](https://nuxtseo.com/docs/og-image/guides/community-templates/docs/og-image/guides/icons-and-images) guide for more information. h3. [`theme`](#theme) **Type:** `string`**Default:** `#00dc82` Changes the theme of the template. You should either provide a hexadecimal color or a valid rgb. For example: `#d946ef` h3. [`colorMode`](#colormode) **Type:** `light` | `dark`**Default:** `light` Changes from a light or dark background / text color. Integrates with `@nuxtjs/color-mode`, selecting your default color mode. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/8.community-templates.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/community-templates/docs/og-image/guides/community-templates.md) **Did this page help you? ** [**Styling** How to style your OG Images.](https://nuxtseo.com/docs/og-image/guides/community-templates/docs/og-image/guides/styling) [**Error pages** How to display og images for error pages](https://nuxtseo.com/docs/og-image/guides/community-templates/docs/og-image/guides/error-pages) **On this page** - [NuxtSeo](#nuxtseo) - [Props](#props) --- ### Custom Fonts · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/custom-fonts Description: Using custom fonts in your OG Images. **Core Concepts** h1. **Custom Fonts** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: improve content quality from audit (#406) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/406). [Copy for LLMs To generate images through Satori a font is required, system fonts can't be used. To avoid issues, the module will use `Inter` font family (`400`, `700`) by default. You can customise the font by using the `fonts` in nuxt.config and when defining the image. You can load fonts directly from Google Fonts (recommended) or use a local font file. For using non-english fonts you should read [**Non-English Locales**](https://nuxtseo.com/docs/og-image/guides/custom-fonts/docs/og-image/guides/non-english-locales) guide for a workaround. h2. [Loading A Google Font](#loading-a-google-font) Google fonts are recommended as their format will always be supported. To use Google Fonts simply provide the array of fonts you want to use using `${name}:${weight}`. This will download and cache the font when you first run your app. nuxt.config.ts ``` export default defineNuxtConfig({ ogImage: { fonts: [ // will load the Noto Sans font from Google fonts 'Noto+Sans:400', 'Noto+Sans:700', 'Work+Sans:ital:400' ] } }) ``` Note: Providing your own fonts will disable the default `Inter` font. h3. [Google Font API Mirror](#google-font-api-mirror) If you're in China or the Google APIs are blocked, you can provide your own proxy server that mirrors Google Fonts **in TTF format**. ``` export default defineNuxtConfig({ ogImage: { // Must serve TTF fonts (not WOFF2) googleFontMirror: 'your-proxy-server.com' } }) ``` **Important:** The mirror must serve fonts in TTF or OTF format for Satori compatibility. Most public CDNs only serve WOFF2 which won't work. **Recommended for China:** Instead of using a mirror, use local font files which are more reliable: ``` export default defineNuxtConfig({ ogImage: { fonts: [ { name: 'Inter', weight: 400, path: '/fonts/Inter-400.ttf', } ] } }) ``` h2. [Loading A Local Font File](#loading-a-local-font-file) Local font files must be either `.otf`, `ttf` or `.woff` and be within the `public` directory. For example, if you have a font file at `public/fonts/OPTIEinstein-Black.otf`, you can load it with the config: nuxt.config.ts ``` export default defineNuxtConfig({ ogImage: { fonts: [ { name: 'optieinstein', weight: 800, // path must point to a public font file path: '/fonts/OPTIEinstein-Black.otf', } ], } }) ``` h2. [Template Custom Fonts](#template-custom-fonts) Sometimes you'll be rendering a custom template that you want to use a custom font with, without having to load that font for all templates. In this case, you can use the `fonts` prop on the `defineOgImage` component. ``` defineOgImage({ fonts: [ { name: 'optieinstein', weight: 800, path: '/fonts/OPTIEinstein-Black.otf', } ] }) ``` h2. [Using A Custom Font In Your Template](#using-a-custom-font-in-your-template) To use your custom fonts, within your template you'll need to set the font-family. ``` <div style="font-family: 'optieinstein'"> <!-- ... --> </div> ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/5.custom-fonts.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/custom-fonts/docs/og-image/guides/custom-fonts.md) **Did this page help you? ** [**JPEGs** Learn how to generate JPEG images instead of PNG for Nuxt OG Image.](https://nuxtseo.com/docs/og-image/guides/custom-fonts/docs/og-image/guides/jpegs) [**Non-English Locales** How to use the Nuxt OG Image module with non-english locales.](https://nuxtseo.com/docs/og-image/guides/custom-fonts/docs/og-image/guides/non-english-locales) **On this page** - [Loading A Google Font](#loading-a-google-font) - [Loading A Local Font File](#loading-a-local-font-file) - [Template Custom Fonts](#template-custom-fonts) - [Using A Custom Font In Your Template](#using-a-custom-font-in-your-template) --- ### Nuxt Content · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/integrations/content Description: How to use the Nuxt OG Image module with Nuxt Content. **Integrations** h1. **Nuxt Content** Last updated **Jan 20, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [feat: Nuxt Content v3](https://github.com/nuxt-modules/og-image/commit/4aa8a7a331a6d3787ef892f8cb20d01acbc187bd). [Copy for LLMs h2. [Introduction](#introduction) Nuxt OG Image integrates with Nuxt Content out of the box, supporting a `ogImage` frontmatter key that can be used to configure your OG Image. You can see a demo of this integration in the [**Nuxt OG Image & Nuxt Content Playground**](https://stackblitz.com/edit/github-hgunsf?file=package.json). h2. [Setup Nuxt Content v3](#setup-nuxt-content-v3) In Nuxt Content v3 we need to use the `asOgImageCollection()` function to augment any collections to be able to use the `ogImage` frontmatter key. content.config.ts ``` import { defineCollection, defineContentConfig } from '@nuxt/content' import { asOgImageCollection } from 'nuxt-og-image/content' export default defineContentConfig({ collections: { content: defineCollection( asOgImageCollection({ type: 'page', source: '**/*.md', }), ), }, }) ``` To ensure the tags actually gets rendered you need to ensure you're using the `defineOgImage()` composable with the `ogImage` key. [...slug].vue ``` <script setup lang="ts"> import { queryCollection, useRoute } from '#imports' const route = useRoute() const { data: page } = await useAsyncData(\`page-${route.path}\`, () => { return queryCollection('content').path(route.path).first() }) if (page.value?.ogImage) { defineOgImage(page.value.ogImage) } </script> ``` h2. [Setup Nuxt Content v2](#setup-nuxt-content-v2) When `strictNuxtContentPaths` has been configured, images will be automatically generated for each markdown file in your content folder. To use `strictNuxtContentPaths` the markdown paths must match the path of the page. For example, if you have a markdown file at `content/blog/3-months-of-europe.md`, the path of the page must be `/blog/3-months-of-europe`. Otherwise, you will need to provide the `ogImage` for each markdown file. ``` export default defineNuxtConfig({ ogImage: { strictNuxtContentPaths: true } }) ``` ``` --- path: /blog/3-months-of-europe ogImage: true --- ``` h3. [Content head](#content-head) The v2 Content integration works by injecting extra tags within the `content.head` property of a markdown file. To have the OG Image work it needs to render this property, if you're not using document driven then you will need to use the `useContentHead()` composable or the `head` property in your component. ``` <script setup> const page = await useAsyncData(\`docs-${route.path}\`, () => queryContent(route.path).findOne()) useContentHead(page) </script> ``` ``` <template> <ContentDoc head /> </template> ``` h2. [Usage](#usage) The frontmatter key has the same options as [`defineOgImage()`](https://nuxtseo.com/docs/og-image/integrations/content/docs/og-image/api/define-og-image-component). Providing `true` will use the default values, a `false` value will disable the OG Image for that page. content/blog/3-months-of-europe.md ``` --- ogImage: component: BlogOgImage props: image: /blog/3-months-of-europe.png readingMins: 5 --- ``` If you'd like to use OG Images from your public folder, you can use the `ogImage.url` key which will serve your image instead of generating one. content/blog/3-months-of-europe.md ``` --- ogImage: url: /blog/3-months-of-europe.png ``` h3. [Screenshots](#screenshots) If you'd prefer to render just screenshots, you can use the`<OgImageScreenshot />` component within your content instead of using frontmatter. To have this work in your markdown files, you will need to make the components global. ``` export default defineNuxtConfig({ ogImage: { componentOptions: { global: true, } } }) ``` content/blog/3-months-of-europe.md ``` :OgImageScreenshot ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/1.integrations/1.content.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/integrations/content/docs/og-image/integrations/content.md) **Did this page help you? ** [**Tutorial: Your first OG Image** Get started with the module by setting up your first og:image on your home page.](https://nuxtseo.com/docs/og-image/integrations/content/docs/og-image/getting-started/getting-familiar-with-nuxt-og-image) [**Nuxt Color Mode** How to use the Nuxt OG Image module with Nuxt Color Mode.](https://nuxtseo.com/docs/og-image/integrations/content/docs/og-image/integrations/color-mode) **On this page** - [Introduction](#introduction) - [Setup Nuxt Content v3](#setup-nuxt-content-v3) - [Setup Nuxt Content v2](#setup-nuxt-content-v2) - [Usage](#usage) --- ### Nuxt Color Mode · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/integrations/color-mode Description: How to use the Nuxt OG Image module with Nuxt Color Mode. **Integrations** h1. **Nuxt Color Mode** Last updated **Sep 5, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: wrong folder name](https://github.com/nuxt-modules/og-image/commit/f63db7399eaf2666ed8977742c41baa9cba1e809). [Copy for LLMs Nuxt OG Image integrates with Nuxt Color Mode out of the box. It will render the default `NuxtSeo` component in the configured color mode. It will also take browser screenshots in the configured color mode. By default, it will use `light` mode when the module isn't configured. h2. [Usage](#usage) To use Nuxt OG Image with Nuxt Color Mode, you need to provide a `preference` or a `fallback` color mode that isn't `system`. ``` export default defineNuxtConfig({ colorMode: { preference: 'system', fallback: 'light', // will render in light mode }, }) ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/1.integrations/2.color-mode.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/integrations/color-mode/docs/og-image/integrations/color-mode.md) **Did this page help you? ** [**Nuxt Content** How to use the Nuxt OG Image module with Nuxt Content.](https://nuxtseo.com/docs/og-image/integrations/color-mode/docs/og-image/integrations/content) [**Satori Renderer** Learn how to use the Satori renderer.](https://nuxtseo.com/docs/og-image/integrations/color-mode/docs/og-image/guides/satori) --- ### Icons and Images · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/icons-and-images Description: How to use icons and images in your templates. **Core Concepts** h1. **Icons and Images** Last updated **Apr 1, 2025** by [cogor](https://github.com/cogor) in [docs: typo in title (#349)](https://github.com/nuxt-modules/og-image/pull/349). [Copy for LLMs h2. [Nuxt Icon & Nuxt UI](#nuxt-icon-nuxt-ui) Nuxt OG Image supports both Nuxt Icon `Icon`'s component and Nuxt UI's `UIcon` component. ``` <template> <div> <Icon name="carbon:bot" mode="svg" /> </div> </template> ``` ``` <template> <div> <UIcon name="i-carbon-bot" mode="svg" /> </div> </template> ``` It's important that the `mode` is set to `svg` to ensure the icon is rendered correctly. h2. [Image Resolution](#image-resolution) Image paths must be either relative to the `public` directory or absolute. It's not possible to bundle images as part of your template. h2. [Tips](#tips) h3. [Provide Width / Height](#provide-width-height) When no dimensions are set, the package `image-size` is used to determine the best dimensions for your image. However, this can be slow and provide incorrect results. Therefore it's always recommended to provide a width and height when using images. Likewise when using a background image, make sure the container has set dimensions. h3. [Base64 images Are Quickest](#base64-images-are-quickest) If you're having issues with performance and images, it's recommended to use base64 images. This will save render time as it won't need to fetch the image. h3. [Avoid Inlining SVGs](#avoid-inlining-svgs) Prefer rendering SVGs instead of inlining them within `img` tags ``` <!-- ❌ --> <img src="data:image/svg+xml;base64,..." /> <!-- ✅ --> <svg> <rect width="24" height="24" /> </svg> ``` [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/6.icons-and-images.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/icons-and-images/docs/og-image/guides/icons-and-images.md) **Did this page help you? ** [**Emojis** Use emojis in your OG Images.](https://nuxtseo.com/docs/og-image/guides/icons-and-images/docs/og-image/guides/emojis) [**Styling** How to style your OG Images.](https://nuxtseo.com/docs/og-image/guides/icons-and-images/docs/og-image/guides/styling) **On this page** - [Nuxt Icon & Nuxt UI](#nuxt-icon-nuxt-ui) - [Image Resolution](#image-resolution) - [Tips](#tips) --- ### Error pages · Nuxt OG Image · Nuxt SEO Source: https://nuxtseo.com/docs/og-image/guides/error-pages Description: How to display og images for error pages **Core Concepts** h1. **Error pages** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix typos, code errors, and add social share debugger links (#405) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/nuxt-modules/og-image/pull/405). [Copy for LLMs Nuxt OG Image supports displaying images for pages with a non-200 status code (for example a 404 page). It supports both errors thrown by Nuxt, as well as custom errors created using `setResponseStatus`. To define an og image for an error page, call `defineOgImageComponent` in the setup script of your `error.vue` file: error.vue ``` <script lang="ts" setup> defineOgImageComponent('NuxtSeo') </script> ``` You can use this, for example, to display a generic OG Image containing the status code and status message by accessing the error provided by Nuxt: error.vue ``` <script lang="ts" setup> import type { NuxtError } from '#app' const props = defineProps<{ error: NuxtError }>() defineOgImageComponent('NuxtSeo', { title: props.error.statusCode.toString(), description: props.error.statusText }) </script> ``` h2. [Limitations](#limitations) Note that displaying OG Images for error pages is only supported for status codes ranging 400 to 499. Pages with status codes >= 500 won't generate an OG Image. [Edit this page](https://github.com/nuxt-modules/og-image/edit/main/docs/content/3.guides/9.error-pages.md) [Markdown For LLMs](https://nuxtseo.com/docs/og-image/guides/error-pages/docs/og-image/guides/error-pages.md) **Did this page help you? ** [**Community Templates** Community templates that are included with the module, including NuxtSeo.](https://nuxtseo.com/docs/og-image/guides/error-pages/docs/og-image/guides/community-templates) [**defineOgImage()** Define an og:image for the current page.](https://nuxtseo.com/docs/og-image/guides/error-pages/docs/og-image/api/define-og-image) --- ### v7.0.0 · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/releases/v7 Description: Release notes for v7.0.0 of Nuxt SEO Utils. **Releases** h1. **v7.0.0** Last updated **Mar 9, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: require Nuxt v3.16](https://github.com/harlan-zw/nuxt-seo-utils/commit/ddd77b1e09a66de0d4ddb03656ece42691465c51). [Copy for LLMs h2. [Introduction](#introduction) The v7 major includes improved defaults for safer canonical URLs, improved breadcrumb usage and support for automatic icon creation. h2. [Nuxt Version Requirement](#nuxt-version-requirement) Nuxt SEO Utils v7 requires Nuxt v3.16 or later. Please upgrade your Nuxt version using `nuxi upgrade --force`. h2. [Lowercase Canonical URLs [**#41**](https://github.com/harlan-zw/nuxt-seo-utils/pull/41)](#lowercase-canonical-urls-41) Lowercases the canonical URLs to avoid duplicate content issues when displaying URLs that may have different capitalization. h3. [Breaking Changes](#breaking-changes) If you have URLs that are intentionally cased then this may lead to some issues, it's recommended to disable canonicalLowercase. ``` export default defineNuxtConfig({ seo: { canonicalLowercase: false } }) ``` h2. [Shared Breadcrumb Context](#shared-breadcrumb-context) If you were previously generating multiple breadcrumb lists on the same page without specifying an `id`, you will now run into issues with context being shared between the components. This is intentional as previously you would be rendering invalid Schema.org markup. To fix this you should specify an `id` which will have shared context. ``` useBreadcrumbItems({ id: 'my-breadcrumb', }) ``` h3. [Breaking Changes](#breaking-changes-1) If you have multiple breadcrumbs on your page please verify this new logic doesn't cause them to conflict. If they do make sure you are setting a unique `id` for each one. [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/4.releases/2.v7.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/releases/v7/docs/seo-utils/releases/v7.md) **Did this page help you? ** [**useBreadcrumbItems()** A composable used to display a breadcrumb list that helps users to navigate between pages.](https://nuxtseo.com/docs/seo-utils/releases/v7/docs/seo-utils/api/breadcrumbs) [**v6.0.0** Release notes for v6.0.0 of Nuxt SEO Utils.](https://nuxtseo.com/docs/seo-utils/releases/v7/docs/seo-utils/releases/v6) **On this page** - [Introduction](#introduction) - [Nuxt Version Requirement](#nuxt-version-requirement) - [Lowercase Canonical URLs #41](#lowercase-canonical-urls-41) - [Shared Breadcrumb Context](#shared-breadcrumb-context) --- ### Install Nuxt SEO Utils · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/getting-started/installation Description: Get started with Nuxt SEO Utils by installing the dependency to your project. **Getting Started** h1. **Install Nuxt SEO Utils** Last updated **Oct 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: cleaning up install Co-Authored-By: Claude <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/commit/6ecddad4dbd3d5bb981a5963481bd0d0aef6c8fa). [Copy for LLMs h2. [Setup Module](#setup-module) `npx nuxt module add nuxt-seo-utils` `npm i nuxt-seo-utils` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-seo-utils', ], }) ``` `yarn add nuxt-seo-utils` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-seo-utils', ], }) ``` `pnpm i nuxt-seo-utils` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-seo-utils', ], }) ``` `bun i nuxt-seo-utils` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-seo-utils', ], }) ``` h2. [Verifying Installation](#verifying-installation) Please check the features set out in the [**introduction**](https://nuxtseo.com/docs/seo-utils/getting-started/installation/docs/seo-utils/getting-started/introduction) to see what the module is doing. Most changes can be found within the Seo Meta DevTools tab or the page source. h2. [Next Steps](#next-steps) Time to put the module to work: - [**Automatic File Metadata Icons and Open Graph Images**](https://nuxtseo.com/docs/seo-utils/getting-started/installation/docs/seo-utils/guides/app-icons) - [**Automatic default meta for your site**](https://nuxtseo.com/docs/seo-utils/getting-started/installation/docs/seo-utils/guides/default-meta) [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/0.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/getting-started/installation/docs/seo-utils/getting-started/installation.md) **Did this page help you? ** [**Introduction** SEO utilities to improve your Nuxt sites discoverability and shareability.](https://nuxtseo.com/docs/seo-utils/getting-started/installation/docs/seo-utils/getting-started/introduction) [**Troubleshooting** Create minimal reproductions for Nuxt SEO Utils or just experiment with the module.](https://nuxtseo.com/docs/seo-utils/getting-started/installation/docs/seo-utils/getting-started/troubleshooting) **On this page** - [Setup Module](#setup-module) - [Verifying Installation](#verifying-installation) - [Next Steps](#next-steps) --- ### useBreadcrumbItems() · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/api/breadcrumbs Description: A composable used to display a breadcrumb list that helps users to navigate between pages. **Nuxt API** h1. **useBreadcrumbItems()** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Usage](#usage) Use the auto-imported `useBreadcrumbItems` composable to generate an automatic breadcrumb list that helps users to navigate between pages. - Integrates with [**Nuxt Schema.org**](https://nuxtseo.com/docs/seo-utils/api/breadcrumbs/docs/schema-org/getting-started/installation) to generate [**BreadcrumbList**](https://schema.org/BreadcrumbList) structured data. - Integrates with [**Nuxt I18n**](https://i18n.nuxtjs.org/) to generate localized breadcrumbs. h2. [Demo](#demo) This demo integrates directly with the [Nuxt UI Breadcrumb](https://ui.nuxt.com/navigation/breadcrumb) component. Enter any path and watch the breadcrumbs be generated! **Hide Root** **Hide Current** components/Breadcrumbs.vue ``` <script lang="ts" setup> const items = useBreadcrumbItems() // uses the current route </script> <template> <UBreadcrumb :items="items"/> </template> ``` Enter any path and watch the breadcrumbs be generated! **Hide Root** **Hide Current** components/Breadcrumbs.vue ``` <script lang="ts" setup> const items = useBreadcrumbItems() // uses the current route </script> <template> <nav aria-label="Breadcrumbs"> <ul> <li v-for="(item, key) in items" :key="key"> <NuxtLink v-bind="item"> {{ item.label }} </NuxtLink> </li> </ul> </nav> </template> ``` h2. [Modifying Breadcrumbs](#modifying-breadcrumbs) Because the breadcrumb is generated automatically, you may need to modify the final output. It's important to do this within the `defineBreadcrumbItems` function, as it will ensure that the Schema.org is generated correctly. h3. [Route Meta](#route-meta) If you need to modify the breadcrumb for a specific static route, you can use the `breadcrumb` property of the route meta. Page Meta ``` <script lang="ts" setup> definePageMeta({ breadcrumb: { icon: 'i-heroicons-home', ariaLabel: 'Home' }, }) </script> ``` h3. [Overrides](#overrides) When you need more control over the final output, you can use the `overrides` prop. This allows you to override any part of the breadcrumb. The property takes an array of either: `BreadcrumbItem`, `false` or `undefined`, the array of overrides is applied in the order they are provided. When providing `undefined`, nothing will be overridden. When providing `false`, the breadcrumb will be removed. For example, if you have the path `/blog/my-post` and you want to override the `my-post` segment, we need to target the third item in the array. ``` // path: /blog/my-post will generate ['Home', 'Blog', 'My Post'] useBreadcrumbItems({ overrides: [ undefined, // Home undefined, // Blog { label: 'My Awesome Post', to: '/blog/my-post', icon: 'i-heroicons-home' } ] }) ``` h3. [`append` and `prepend`](#append-and-prepend) If you need to add items to the end or beginning of the breadcrumb, you can use the `append` and `prepend` props. ``` import { useBreadcrumbItems } from '#imports' useBreadcrumbItems({ append: [ { label: 'Final Link' } ] }) ``` h3. [I18n Integration](#i18n-integration) If you're using the [**@nuxtjs/i18n**](https://i18n.nuxtjs.org/) module, you can use the key `breadcrumbs.items.${routeName}`. Where `routeName` is the generated name of the Vue Router route. ``` export default { breadcrumb: { items: { index: { icon: 'i-heroicons-home', ariaLabel: 'Home' } } } } ``` ``` { "breadcrumb": { "items": { "index": { "icon": "i-heroicons-home", "ariaLabel": "Home" } } } } ``` h3. [Hierarchical Breadcrumbs](#hierarchical-breadcrumbs) It's common to want to have a breadcrumb in your top-level layout and then have this be completely dynamic for each page. While this will work, it gets more complicated once you need to start overriding the breadcrumb list for data that's only available from a fetch. Consider a blog example where we want to show something like: - Blog (`/blog`) -> Dynamic Category (`/blog/dynamic-category`) -> Post title (`/blog/dynamic-category/post-title`) You would be tempted to insert the breadcrumb in the blog layout. However, this has issues. blog.vue ``` <script lang="ts" setup> import { useBreadcrumbItems } from '#imports' const items = useBreadcrumbItems({ rootNode: '/blog', overrides: [ { label: 'My Cool Blog', }, // Home // ❌ We're missing data to render the post title and category ] }) </script> <template> <div> <Breadcrumb :items="useBreadcrumbItems()" /> </div> </template> ``` Instead, we need to render the breadcrumb list as late as possible in the render tree. This means we need to insert the breadcrumb in the page component. However, this means we may need to do prop drilling to get the data to the breadcrumb. Instead, we can use the shared breadcrumb context that's based on the `id`. ``` <script lang="ts" setup> // setup root breadcrumb config useBreadcrumbItems({ overrides: [ { label: 'My Cool Site', }, ] }) </script> ``` ``` <script lang="ts" setup> // example path /blog/<category>/<post> const category = await useFetch<{ slug: string }>('/api/category', { params: { category: useRoute().params.category } }) // setup breadcrumb root config useBreadcrumbItems({ overrides: [ undefined, { label: 'Category', to: \`/blog/${category.value.slug}\` }, ] }) </script> <template> <div> <!-- ❌ don't render breadcrumbs here --> </div> </template> ``` ``` <script lang="ts" setup> definePageMeta({ layout: 'blog', }) // example const post = await useFetch('/api/post', { params: { post: useRoute().params.post } }) const items = useBreadcrumbItems({ overrides: [ undefined, // avoid overriding the root undefined, { label: 'Post Title', to: \`/blog/${useRoute().params.category}/${post.value.slug}\` } ] }) </script> <template> <div> <!-- ✅ render breadcrumbs here --> </div> </template> ``` This may change your design a bit, but it's the only way to reliable handle this without a hydration issue. h2. [Props](#props) h3. [`path`](#path) - Type: `string` - Default: `getRoute().path` The path to generate the breadcrumb for. h3. [`schemaOrg`](#schemaorg) - Type: `boolean` - Default: `false` Whether to generate [**BreadcrumbList**](https://schema.org/BreadcrumbList) structured data. h3. [`ariaLabel`](#arialabel) - Type: `string` - Default: `'Breadcrumbs'` The Aria Label for the breadcrumbs. h3. [`hideRoot`](#hideroot) - Type: `MaybeRefOrGetter<boolean>` - Default: `false` Whether the root breadcrumb should be shown. h3. [`hideCurrent`](#hidecurrent) - Type: `MaybeRefOrGetter<boolean>` - Default: `false` Whether the current breadcrumb should be shown. This is usually the last item in the breadcrumb, but not always. h3. [`overrides`](#overrides-1) - Type: `(BreadcrumbItem| false | undefined)[]` - Default: `[]` An array of items to override the generated breadcrumbs with. h3. [`append`](#append) - Type: `BreadcrumbItem[]` - Default: `[]` An array of items to append to the generated breadcrumbs. h3. [`prepend`](#prepend) - Type: `BreadcrumbItem[]` - Default: `[]` An array of items to prepend to the generated breadcrumbs. h3. [`rootNode`](#rootnode) - Type: `string` - Default: `undefined` The root node to use for the breadcrumb. This is useful for when you want to generate a breadcrumb for a specific route. The default will either use `/` or the i18n prefixed alternative. h3. [`hideNonExisting`](#hidenonexisting) - Type: `boolean` - Default: `false` Should breadcrumb items with non-existing path be shown. By default, every path segments will be present in breadcrumb list even if there is no corresponding page for such segment. **Key Integration:** The `useBreadcrumbItems()` composable automatically generates Schema.org BreadcrumbList markup when paired with the [**Nuxt Schema.org**](https://nuxtseo.com/docs/seo-utils/api/breadcrumbs/docs/schema-org/getting-started/introduction) module. [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/3.api/4.breadcrumbs.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/api/breadcrumbs/docs/seo-utils/api/breadcrumbs.md) **Did this page help you? ** [**Config** Configuration options for Nuxt SEO Utils module.](https://nuxtseo.com/docs/seo-utils/api/breadcrumbs/docs/seo-utils/api/config) [**v7.0.0** Release notes for v7.0.0 of Nuxt SEO Utils.](https://nuxtseo.com/docs/seo-utils/api/breadcrumbs/docs/seo-utils/releases/v7) **On this page** - [Usage](#usage) - [Demo](#demo) - [Modifying Breadcrumbs](#modifying-breadcrumbs) - [Props](#props) --- ### Troubleshooting Nuxt SEO Utils · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/getting-started/troubleshooting Description: Create minimal reproductions for Nuxt SEO Utils or just experiment with the module. **Getting Started** h1. **Troubleshooting Nuxt SEO Utils** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix broken links, add descriptions, and add meta tag checker links (#77) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/77). [Copy for LLMs h2. [Common Issues](#common-issues) h3. [Meta Tags Missing in Production](#meta-tags-missing-in-production) **Problem:** Meta tags work in development but are missing in production. **Solution:** Check the following: 1. Ensure you're using SSR (not SPA mode) 2. Run `pnpm build` and check the generated HTML 3. Enable debug mode to see what's being set: nuxt.config.ts ``` export default defineNuxtConfig({ seo: { debug: true } }) ``` h3. [`lang` Attribute Not Applied](#lang-attribute-not-applied) **Problem:** You set `htmlAttrs: { lang: 'ja' }` in `useHead()` but the HTML element still shows `lang="en"`. **Why:** When `automaticDefaults` is enabled (default), the module automatically sets the `lang` attribute based on site config, not from `useHead()` calls. **Solution:** Set the locale via site config instead: nuxt.config.ts ``` export default defineNuxtConfig({ site: { defaultLocale: 'ja' } }) ``` See the [**I18n Guide**](https://nuxtseo.com/docs/seo-utils/getting-started/troubleshooting/docs/seo-utils/guides/i18n) for detailed information. h3. [Canonical URL Not Using My Custom Logic](#canonical-url-not-using-my-custom-logic) **Problem:** You're setting a custom canonical URL with `useHead()` but the module overrides it. **Why:** The module sets canonical URLs with low priority by default when `automaticDefaults` is enabled. **Solution:** Either disable automatic defaults or use higher priority: ``` // Option 1: Higher priority useHead({ link: [{ rel: 'canonical', href: 'https://example.com/custom' }] }, { tagPriority: 'high' }) // Option 2: Disable automatic defaults export default defineNuxtConfig({ seo: { automaticDefaults: false } }) ``` h2. [Debugging Tools](#debugging-tools) - [**Meta Tag Checker**](https://nuxtseo.com/docs/seo-utils/getting-started/troubleshooting/tools/meta-tag-checker) - Validate title and description, preview Google SERP appearance - [**Social Share Debugger**](https://nuxtseo.com/docs/seo-utils/getting-started/troubleshooting/tools/social-share-debugger) - Debug OG images and social cards h2. [Submitting an Issue](#submitting-an-issue) When submitting an issue, it's important to provide as much information as possible. The easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds: - [**Basic**](https://stackblitz.com/edit/nuxt-starter-vbay3q?file=app.vue) [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/0.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/getting-started/troubleshooting/docs/seo-utils/getting-started/troubleshooting.md) **Did this page help you? ** [**Installation** Get started with Nuxt SEO Utils by installing the dependency to your project.](https://nuxtseo.com/docs/seo-utils/getting-started/troubleshooting/docs/seo-utils/getting-started/installation) [**Canonical URL** Ensure a canonical URL is set to avoid duplicate content issues.](https://nuxtseo.com/docs/seo-utils/getting-started/troubleshooting/docs/seo-utils/guides/canonical-url) **On this page** - [Common Issues](#common-issues) - [Debugging Tools](#debugging-tools) - [Submitting an Issue](#submitting-an-issue) --- ### Canonical URL · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/canonical-url Description: Ensure a canonical URL is set to avoid duplicate content issues. **Core Concepts** h1. **Canonical URL** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Introduction](#introduction) A canonical URL tells search engines which version of a page is the "main" one. Nuxt SEO automatically sets this for you using your site URL and current route path. Multiple URLs often point to the same content (www vs non-www, trailing slashes). The canonical URL prevents [**duplicate content issues**](https://support.google.com/webmasters/answer/66359?hl=en) and confusion for end-users. Learn more about canonical URLs with the [**Canonical Link Tag**](https://nuxtseo.com/docs/seo-utils/guides/canonical-url/learn/controlling-crawlers/canonical-urls) guide. h2. [Canonical Head Tag](#canonical-head-tag) Nuxt SEO automatically sets a canonical URL meta tag for you. This tag is generated from the site URL and the current route path. ``` <head> <!-- ... --> <link rel="canonical" href="https://example.com/my-page" /> </head> ``` h3. [Disabling Canonical URLs](#disabling-canonical-urls) If you need to disable automatic canonical URL generation completely, you can do so by setting the canonical URL to `false` in your SEO meta configuration: nuxt.config.ts ``` export default defineNuxtConfig({ seo: { meta: { canonical: false } } }) ``` You can also disable it on a per-page basis using `useSeoMeta`: ``` useSeoMeta({ canonical: false }) ``` h3. [canonicalLowercase](#canonicallowercase) By default, the canonical URL is generated in lowercase. This is to ensure that the canonical URL is consistent and avoids any issues with case sensitivity. While it's recommended to keep this on, if you need to disable this feature, you can do so by setting `canonicalLowercase` to `false`. nuxt.config.ts ``` export default defineNuxtConfig({ seo: { canonicalLowercase: false } }) ``` h3. [canonicalQueryWhitelist](#canonicalquerywhitelist) The canonical URL is generated from: - `site.url`. Your [**Site Config**](https://nuxtseo.com/docs/seo-utils/guides/canonical-url/docs/site-config/getting-started/introduction) url. - `$route.path`: The current route path. - `canonicalQueryWhitelist`: A list of query parameters that should be included in the canonical URL. By default, the `canonicalQueryWhitelist` includes a number of common query parameters that will modify the page content: - `page` - `sort` - `filter` - `search` - `q` - `query` You can override this by providing your own list of query parameters that should be included in the canonical URL. nuxt.config.ts ``` export default defineNuxtConfig({ seo: { canonicalQueryWhitelist: ['myCustomQuery'] } }) ``` h2. [Redirect To Canonical](#redirect-to-canonical) In some cases it may be preferred to redirect all non-canonical URLs to the canonical URL. This redirect is optional and disabled by default. nuxt.config.ts ``` export default defineNuxtConfig({ seo: { redirectToCanonicalSiteUrl: true } }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/0.canonical-url.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/canonical-url/docs/seo-utils/guides/canonical-url.md) **Did this page help you? ** [**Troubleshooting** Create minimal reproductions for Nuxt SEO Utils or just experiment with the module.](https://nuxtseo.com/docs/seo-utils/guides/canonical-url/docs/seo-utils/getting-started/troubleshooting) [**App Icons** Learn how metadata files work with logos.](https://nuxtseo.com/docs/seo-utils/guides/canonical-url/docs/seo-utils/guides/app-icons) **On this page** - [Introduction](#introduction) - [Canonical Head Tag](#canonical-head-tag) - [Redirect To Canonical](#redirect-to-canonical) --- ### App Icons · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/app-icons Description: Learn how metadata files work with logos. **Core Concepts** h1. **App Icons** Last updated **Dec 4, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply user feedback improvements (#72) Co-authored-by: Claude <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/72). [Copy for LLMs h2. [Overview](#overview) Nuxt SEO Utils automatically detects icon files in your project and generates the appropriate `link` tags in your HTML head. You **do not need to manually include these icons** - they are auto-generated based on files you place in your project. This is based on [**Next.js Metadata File**](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/app-icons) with an almost identical API, however runtime images are not supported. h2. [Setup](#setup) Simply place your icon files in either: - The root of your `public/` directory (applies to all pages) - Alongside your `pages/` directory (for route-specific icons) The module will automatically detect these files and generate the appropriate HTML tags. No configuration required. h2. [Naming Convention](#naming-convention) h3. [`favicon`](#favicon) - Name: `favicon.ico` Using an `ico` file for your favicon is a good practice as all browsers support it, you can pack multiple icon sizes into it without having to define a number of extra icon links. The `favicon.ico` file should be placed in the `public` directory. It is supported by most browsers and displayed in the browser tab. By default, it won't output any HTML as browsers know where to look for it. If you're using a `baseURL` then it will output the correct path. **Example:** Place `favicon.ico` in `public/favicon.ico` and the module automatically generates: head output ``` <link rel="icon" href="/base/favicon.ico" sizes="any" /> ``` h3. [`icon`](#icon) - Name: `*icon.{ico,jpg,jpeg,png,svg}`, `*icon-*.{ico,jpg,jpeg,png,svg}` Icons give you greater control over the displayed icon. You can provide multiple icons and the browser will automatically select the best icon based on the device's pixel density and uses it to display the app icon. If you have multiple icons and need to sort them, it's recommended you prefix them with their order. For example: `1.icon.png`, `2.icon.png`, `3.icon.png`. **Example:** Place icon files in `public/` and the module automatically generates: head output ``` <link rel="icon" href="/1.icon.png" sizes="32x32" type="image/png" /> <link rel="icon" href="/2.icon.png" sizes="192x192" type="image/png" /> <link rel="icon" href="/3.icon.png" sizes="360x360" type="image/png" /> ``` h4. [Dark / Light Mode](#dark-light-mode) You can also provide dark and light mode icons by appending `-dark`,`-light`, `.dark` or `.light` to the icon name. **Example:** Place `icon-dark.png` and `icon-light.png` in `public/` and the module automatically generates: head output ``` <link rel="icon" href="/icon-dark.png" type="image/png" media="(prefers-color-scheme: dark)" /> <link rel="icon" href="/icon-light.png" type="image/png" media="(prefers-color-scheme: light)" /> ``` h3. [`apple-touch-icon`](#apple-touch-icon) - Name: `*apple-icon*.{png,jpg,jpeg}`, `*apple-touch-icon*.{png,jpg,jpeg}` Apple touch icons are used by iOS devices to display a website's icon on the home screen and in the browser tab. You can again provide multiple icons. **Example:** Place `apple-icon.png` in `public/` and the module automatically generates: head output ``` <link rel="apple-touch-icon" href="/apple-icon.png" sizes="180x180" /> ``` h2. [Pages Directory](#pages-directory) You can also place logos in your `pages` directory. This is useful if you want to have different logos for different pages. The naming convention is the same as the `public` directory. ``` pages/ ├── index.vue ├── admin/ │ ├── index.vue │ └── icon.png ``` This will overwrite any logos in the `public` directory for all `admin` pages. You can optionally also place your files within the `_dir` directory, which will work the same way. ``` pages/ ├── index.vue ├── admin/ │ ├── _dir/ │ │ └── icon.png # Does the same thing! │ └── index.vue ``` [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/1.app-icons.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/app-icons/docs/seo-utils/guides/app-icons.md) **Did this page help you? ** [**Canonical URL** Ensure a canonical URL is set to avoid duplicate content issues.](https://nuxtseo.com/docs/seo-utils/guides/app-icons/docs/seo-utils/guides/canonical-url) [**Open Graph Images** Automatically set meta tags for Open Graph images.](https://nuxtseo.com/docs/seo-utils/guides/app-icons/docs/seo-utils/guides/open-graph-images) **On this page** - [Overview](#overview) - [Setup](#setup) - [Naming Convention](#naming-convention) - [Pages Directory](#pages-directory) --- ### Best Practice Default Meta · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/default-meta Description: The default meta tags Nuxt SEO sets for you. **Core Concepts** h1. **Best Practice Default Meta** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix broken links, add descriptions, and add meta tag checker links (#77) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/77). [Copy for LLMs h2. [Introduction](#introduction) To ensure your site is SEO friendly, Nuxt SEO sets some default meta tags for you based on your [**site config**](https://nuxtseo.com/docs/seo-utils/guides/default-meta/docs/nuxt-seo/guides/configuring-modules). h2. [Canonical URL](#canonical-url) Ensuring a canonical URL is set helps avoid [**duplicate content issues**](https://support.google.com/webmasters/answer/66359?hl=en). This can be an issue when you have multiple domains or subdomains pointing to the same content, [**trailing slashes**](https://nuxtseo.com/docs/seo-utils/guides/default-meta/docs/nuxt-seo/guides/trailing-slashes) and non-trailing slashes showing the same content and when you have query parameters that don't change the content. Learn more about canonical URLs with the [**Canonical Link Tag**](https://nuxtseo.com/docs/seo-utils/guides/default-meta/learn/controlling-crawlers/canonical-urls) guide. The canonical URL is generated from your site config `url`, the current route and the `canonicalQueryWhitelist`. h3. [canonicalQueryWhitelist](#canonicalquerywhitelist) By default, the `canonicalQueryWhitelist` includes a number of common query parameters that will modify the page content: - `page` - `sort` - `filter` - `search` - `q` - `query` You can override this by providing your own list of query parameters that should be included in the canonical URL. nuxt.config.ts ``` export default defineNuxtConfig({ seo: { canonicalQueryWhitelist: ['myCustomQuery'] } }) ``` h2. [I18n](#i18n) Google wants you to list the language that any given page is written in as `<html lang="<locale>">`. Nuxt SEO will set this for you based on your site config `currentLocale` and `defaultLocale` (default `en`). If you're using the `@nuxtjs/i18n` module, then this is automatically set for you. See the [**I18n Guide**](https://nuxtseo.com/docs/seo-utils/guides/default-meta/docs/nuxt-seo/guides/i18n) for detailed information on configuring locales, especially if you're experiencing issues with the `lang` attribute. h2. [Open Graph](#open-graph) Providing extra [**open-graph**](https://ogp.me/) meta tags helps social media platforms understand your content better. The following tags are set: - `og:url` - The canonical URL of the page. - `og:locale` - The current locale of the page based on site config `currentLocale` and `defaultLocale`. - `og:site_name` - The name of your site based on site config `name`. h2. [Disable Default Meta](#disable-default-meta) While all default meta is registered with low priority, allowing you to easily override them, you may want to disable them entirely. nuxt.config.ts ``` export default defineNuxtConfig({ seo: { automaticDefaults: false } }) ``` [](https://nuxtseo.com/docs/seo-utils/guides/default-meta/tools/meta-tag-checker)**Validate your meta tags** - Check title and description length with our [**Meta Tag Checker**](https://nuxtseo.com/docs/seo-utils/guides/default-meta/tools/meta-tag-checker). [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/2.default-meta.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/default-meta/docs/seo-utils/guides/default-meta.md) **Did this page help you? ** [**Open Graph Images** Automatically set meta tags for Open Graph images.](https://nuxtseo.com/docs/seo-utils/guides/default-meta/docs/seo-utils/guides/open-graph-images) [**Enhanced Titles** How Nuxt SEO enhances your page titles.](https://nuxtseo.com/docs/seo-utils/guides/default-meta/docs/seo-utils/guides/fallback-title) **On this page** - [Introduction](#introduction) - [Canonical URL](#canonical-url) - [I18n](#i18n) - [Open Graph](#open-graph) - [Disable Default Meta](#disable-default-meta) --- ### Open Graph Images · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/open-graph-images Description: Automatically set meta tags for Open Graph images. **Core Concepts** h1. **Open Graph Images** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Introduction](#introduction) New to Open Graph tags? Learn more about them with the [**Mastering Open Graph Tags**](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/learn/mastering-meta/open-graph) guide. Setting up Open Graph images is a great way to improve your click-through-rate when your link is shared. [](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/docs/og-image/getting-started/introduction)**Dynamic OG Images:** For programmatic image generation, see the [**Nuxt OG Image**](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/docs/og-image/getting-started/introduction) module. However, setting them up can be tricky. Knowing which `meta` tag to use and the rules for the image can be difficult to remember. In using Nuxt SEO Utils and you can set up your social share images seamless using [**Next.js Metadata Files**](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image), generating automatic `meta` tags. h2. [File Types](#file-types) h3. [`opengraph-image`](#opengraph-image) Open Graph images are used by social media sites to display a website's icon when shared. The image dimensions and type will be automatically generated from the image file. - `*og-image.{png,jpg,jpeg,gif}` - `*opengraph-image-*.{png,jpg,jpeg,gif}` Example File Structure ``` pages/ ├── index.vue ├── about/ │ ├── index.vue │ └── og-image.png ``` Head output ``` <meta property="og:image" content="<site-url>/about/og-image.png" /> <meta property="og:image:width" content="1200" /> <meta property="og:image:height" content="630" /> <meta property="og:image:type" content="image/png" /> ``` h3. [`twitter-image`](#twitter-image) Twitter images are used by to display an image when your site is shared on Twitter. The image dimensions and type will be automatically generated from the image file. - `*twitter-image.{png,jpg,jpeg,gif}` Example File Structure ``` pages/ ├── index.vue ├── about/ │ ├── index.vue │ └── twitter-image.png ``` head output ``` <meta name="twitter:image" content="<site-url>/about/twitter-image.png" /> <meta name="twitter:image:width" content="1200" /> <meta name="twitter:image:height" content="630" /> <meta name="twitter:image:type" content="image/png" /> ``` h2. [Providing Alternate Text](#providing-alternate-text) You can provide alternate text for your images by using creating a matching `.txt` file for your image. For example, if you have an image `og-image.png`, you can create a `og-image.txt` file with the alternate text. og-image.txt ``` This is the alternate text for my image. ``` head output ``` <meta property="og:image:alt" content="This is the alternate text for my image." /> ``` h2. [Using _dir folder](#using-_dir-folder) It's recommended to use the `_dir` folder to store your images when you have multiple images for a single route. Example File Structure ``` pages/ ├── index.vue ├── about/ │ ├── index.vue │ └── _dir/ │ ├── og-image.png │ ├── og-image.txt │ ├── twitter-image.png │ └── twitter-image.txt ``` head output ``` <meta property="og:image" content="<site-url>/about/_dir/og-image.png" /> <meta property="og:image:width" content="1200" /> <meta property="og:image:height" content="630" /> <meta property="og:image:type" content="image/png" /> <meta property="og:image:alt" content="This is the alternate text for my image." /> <meta name="twitter:image" content="<site-url>/about/_dir/twitter-image.png" /> <meta name="twitter:image:width" content="1200" /> <meta name="twitter:image:height" content="630" /> <meta name="twitter:image:type" content="image/png" /> <meta name="twitter:image:alt" content="This is the alternate text for my image." /> ``` [](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/tools/social-share-debugger)**Test your OG images** - Preview how your links appear on social platforms with our [**Social Share Debugger**](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/tools/social-share-debugger). [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/1.open-graph-images.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/docs/seo-utils/guides/open-graph-images.md) **Did this page help you? ** [**App Icons** Learn how metadata files work with logos.](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/docs/seo-utils/guides/app-icons) [**Best Practice Default Meta** The default meta tags Nuxt SEO sets for you.](https://nuxtseo.com/docs/seo-utils/guides/open-graph-images/docs/seo-utils/guides/default-meta) **On this page** - [Introduction](#introduction) - [File Types](#file-types) - [Providing Alternate Text](#providing-alternate-text) - [Using _dir folder](#using-_dir-folder) --- ### Enhanced Titles · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/fallback-title Description: How Nuxt SEO enhances your page titles. **Core Concepts** h1. **Enhanced Titles** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Introduction](#introduction) Getting your page titles right is difficult. Nuxt SEO provides several utils to make it easier: fallback titles, page meta titles a default title template. You can learn more about titles and titles templates with the [**Page Titles**](https://nuxtseo.com/docs/seo-utils/guides/fallback-title/learn-seo/nuxt/mastering-meta/titles) guide. h2. [Fallback Title](#fallback-title) Ensures that every page has a title by generating one from the last slug segment. For example, if your page is `/blog/my-awesome-post`, the title will be `My Awesome Post`. This is useful for when you have a lot of pages and don't want to manually set a title for each one or if you simply forget to set a title. To disable this feature: nuxt.config.ts ``` export default defineNuxtConfig({ seo: { fallbackTitle: false } }) ``` h2. [Default Title Template](#default-title-template) By default, a title template is inserted for you in the `nuxt.config.ts` file. ``` // equivalent of what the module does useHead({ titleTemplate: '%s %separator %siteName', }) ``` This will set your titles to a template like `'Page Title | Site Name'`. You can either modify the template or the params: - `%s` is the page title `useHead({ title: 'My Page Title' })` - `%separator` see [**Title template params**](https://nuxtseo.com/learn/mastering-meta/titles#template-params) - `%siteName` see [**Site Config**](https://nuxtseo.com/docs/seo-utils/guides/fallback-title/docs/site-config/guides/setting-site-config). You can disable this by [**Disabling Default Meta**](https://nuxtseo.com/docs/seo-utils/guides/fallback-title/docs/seo-utils/guides/default-meta#disable-default-meta) or simply overriding it. h2. [Page Meta Title](#page-meta-title) Normally you would need to use `useHead()` or `useSeoMeta()` to set your page title. Nuxt SEO also gives you the option to add a title using [**page meta**](https://nuxt.com/docs/api/utils/define-page-meta) instead. pages/index.vue ``` <script lang="ts" setup> import { definePageMeta } from '#imports' // Note: does not work for dynamic pages, only accepts strings definePageMeta({ title: 'My Page Title' }) </script> ``` [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/3.fallback-title.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/fallback-title/docs/seo-utils/guides/fallback-title.md) **Did this page help you? ** [**Best Practice Default Meta** The default meta tags Nuxt SEO sets for you.](https://nuxtseo.com/docs/seo-utils/guides/fallback-title/docs/seo-utils/guides/default-meta) [**SEO Route Rules** Utilise route rules for dynamic SEO meta tags.](https://nuxtseo.com/docs/seo-utils/guides/fallback-title/docs/seo-utils/guides/route-rules) **On this page** - [Introduction](#introduction) - [Fallback Title](#fallback-title) - [Default Title Template](#default-title-template) - [Page Meta Title](#page-meta-title) --- ### SEO Route Rules · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/route-rules Description: Utilise route rules for dynamic SEO meta tags. **Core Concepts** h1. **SEO Route Rules** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Introduction](#introduction) Sometimes we're dealing with complex page structures which make it difficult to set SEO meta tags appropriately without duplication. To get around this, you can use build-time [**Route Rules**](https://nitro.unjs.io/config#routerules) to set dynamic SEO meta tags. This is useful for setting up default meta tags for a specific set of pages, such as a blog or product pages. h2. [Usage](#usage) Route rules are injected server-side and won't update on client-side navigation. Only use for crawler-facing data. Trying to set default OG Images? Try instead use the [**App Icons - Open Graph Images**](https://nuxtseo.com/docs/seo-utils/guides/route-rules/docs/seo-utils/guides/open-graph-images#opengraph-image). h3. [`seoMeta`](#seometa) Takes the same input as [**useSeoMeta()**](https://nuxt.com/docs/api/composables/use-seo-meta#useseometa). **Infer SEO Meta Plugin** Nuxt SEO Utils loads the [**Infer SEO Meta Plugin**](https://unhead.unjs.io/plugins/plugins/infer-seo-meta-tags) from [**Unhead**](https://unhead.unjs.io/) that will automatically infer SEO meta tags for you based on your content, including `og:title`, `og:description`, `twitter:card`. ``` export default defineNuxtConfig({ routeRules: { '/blog/**': { seoMeta: { author: 'Harlan Wilton', }, }, } }) ``` h3. [`head`](#head) Takes the same input as [**useHead()**](https://nuxt.com/docs/api/composables/use-seo-meta#useseometa). ``` export default defineNuxtConfig({ routeRules: { '/blog/**': { head: { // set default icon for blog posts link: [ { rel: 'icon', type: 'image/png', href: '/blog-icon.png' } ] }, }, } }) ``` h2. [Limitations](#limitations) Route rules will by default take a `high` priority, meaning that they will override any other meta tags set by the page. You can override this by setting a higher priority in your page components: pages/blog/_slug.vue ``` <script lang="ts" setup> useSeoMeta({ description: 'My awesome blog post', }, { tagPriority: 'high' // overrides route rules }) </script> ``` [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/3.route-rules.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/route-rules/docs/seo-utils/guides/route-rules.md) **Did this page help you? ** [**Enhanced Titles** How Nuxt SEO enhances your page titles.](https://nuxtseo.com/docs/seo-utils/guides/route-rules/docs/seo-utils/guides/fallback-title) [**I18n & Localization** How Nuxt SEO Utils handles language and locale configuration.](https://nuxtseo.com/docs/seo-utils/guides/route-rules/docs/seo-utils/guides/i18n) **On this page** - [Introduction](#introduction) - [Usage](#usage) - [Limitations](#limitations) --- ### Recommended Config · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/guides/setting-site-config Description: Learn how to set site config in your Nuxt app. **Core Concepts** h1. **Recommended Config** Last updated **Oct 30, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: broken links](https://github.com/harlan-zw/nuxt-site-config/commit/76fac9355f512e5d1e3a8893f951b1bdd3b5fa3d). [Copy for LLMs Site config can be set from many different sources from each environment. For a full list see [**how it works**](https://nuxtseo.com/docs/site-config/guides/setting-site-config/docs/site-config/getting-started/how-it-works). At a minimum, it's recommended you provide a `url`, `env` and `name` for your site. h2. [Dev and Live Only](#dev-and-live-only) If you only run your site in development and live environments, you can safely set your site config in your `nuxt.config.ts` file. nuxt.config.ts ``` export default defineNuxtConfig({ site: { url: 'https://example.com', name: 'My Site', // ...etc }, }) ``` h2. [Environment-Specific Site Config](#environment-specific-site-config) If you have other environments, such as a staging or testing site, then it's recommended to set your site config using environment variables. ``` NUXT_SITE_URL=https://test.example.com NUXT_SITE_NAME="STAGING SITE NAME" NUXT_SITE_ENV="staging" ``` h2. [Build Time Site Config](#build-time-site-config) If you need to set your site config programmatically, you can use the `site-config:resolve` hook. ``` export default defineNuxtConfig({ hooks: { 'site-config:resolve': (siteConfig) => { if (process.env.FOO) siteConfig.name = 'Bar' }, }, }) ``` h2. [Runtime Site Config](#runtime-site-config) Sometimes you need to set your site config programmatically. This is fully supported, see the [**Runtime Site Config**](https://nuxtseo.com/docs/site-config/guides/setting-site-config/docs/site-config/guides/runtime-site-config) guide to learn how. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/2.guides/0.setting-site-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/guides/setting-site-config/docs/site-config/guides/setting-site-config.md) **Did this page help you? ** [**Troubleshooting** Debug Nuxt Site Config issues using DevTools, config options, and minimal reproductions.](https://nuxtseo.com/docs/site-config/guides/setting-site-config/docs/site-config/getting-started/troubleshooting) [**How it works** Learn how the Nuxt Site Config module works, so you can get the most out of it.](https://nuxtseo.com/docs/site-config/guides/setting-site-config/docs/site-config/guides/how-it-works) **On this page** - [Dev and Live Only](#dev-and-live-only) - [Environment-Specific Site Config](#environment-specific-site-config) - [Build Time Site Config](#build-time-site-config) - [Runtime Site Config](#runtime-site-config) --- ### I18n & Localization · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/i18n Description: How Nuxt SEO Utils handles language and locale configuration. **Core Concepts** h1. **I18n & Localization** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Introduction](#introduction) Nuxt SEO Utils automatically sets the `<html lang="...">` attribute and locale-based meta tags for your site. h2. [How is Locale Used?](#how-is-locale-used) Nuxt SEO Utils will automatically set: - `<html lang="...">` - Current locale based on your configuration - `og:locale` - OpenGraph locale (e.g., `en_US`, `ja_JP`) - Schema.org `inLanguage` - For WebSite, WebPage, and other structured data (when using [**Nuxt Schema.org**](https://nuxtseo.com/docs/seo-utils/guides/i18n/docs/schema-org/getting-started/introduction)) - `useBreadcrumbItems()` - Automatically handles locale-prefixed routes and translates labels via i18n keys (`breadcrumb.items.{routeName}.label`) `og:locale` requires a locale format with underscore (e.g., `en_US`). If your locale is just `"en"`, the module won't set `og:locale` automatically. h2. [How Locale is Determined](#how-locale-is-determined) When `automaticDefaults` is enabled (default), the module sets the `lang` attribute using this priority: 1. **`@nuxtjs/i18n` or `nuxt-i18n-micro` module** - Automatically detected and integrated 2. **Site Config `currentLocale`** - Dynamic locale from `useSiteConfig()` 3. **Site Config `defaultLocale`** - Static default from `nuxt.config.ts` 4. **Fallback** - Defaults to `"en"` The locale is set with **low priority**, allowing you to override it manually if needed. h2. [Setting up Nuxt SEO Utils for I18n](#setting-up-nuxt-seo-utils-for-i18n) If you're building a multilingual site, install `@nuxtjs/i18n`. For single language sites without `@nuxtjs/i18n`, set the locale via Site Config: nuxt.config.ts ``` export default defineNuxtConfig({ site: { defaultLocale: 'ja-JP' } }) ``` Set your locale as specific as possible using BCP 47 format (hyphen-separated). Instead of `en`, use `en-US`, `en-AU`, or whatever is appropriate for your site. The module automatically converts this to underscore format (`en_US`) for `og:locale`. h2. [Dynamic Locale Changes](#dynamic-locale-changes) If you need to change the locale dynamically without using the I18n module, use `updateSiteConfig()`: ``` <script setup> updateSiteConfig({ currentLocale: 'ja-JP' }) </script> ``` h2. [Manual Control](#manual-control) To manually control all locale settings, disable automatic defaults: nuxt.config.ts ``` export default defineNuxtConfig({ seo: { automaticDefaults: false } }) ``` Then set everything manually: app.vue ``` useHead({ htmlAttrs: { lang: 'ja-JP' } }) useSeoMeta({ ogLocale: 'ja_JP' }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/4.i18n.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/i18n/docs/seo-utils/guides/i18n.md) **Did this page help you? ** [**SEO Route Rules** Utilise route rules for dynamic SEO meta tags.](https://nuxtseo.com/docs/seo-utils/guides/i18n/docs/seo-utils/guides/route-rules) [**Nuxt Config SEO Meta** Make use of the power of useSeoMeta inside your nuxt.config.](https://nuxtseo.com/docs/seo-utils/guides/i18n/docs/seo-utils/guides/nuxt-config-seo-meta) **On this page** - [Introduction](#introduction) - [How is Locale Used?](#how-is-locale-used) - [How Locale is Determined](#how-locale-is-determined) - [Setting up Nuxt SEO Utils for I18n](#setting-up-nuxt-seo-utils-for-i18n) - [Dynamic Locale Changes](#dynamic-locale-changes) - [Manual Control](#manual-control) --- ### Nuxt Config SEO Meta · Nuxt SEO Utils · Nuxt SEO Source: https://nuxtseo.com/docs/seo-utils/guides/nuxt-config-seo-meta Description: Make use of the power of useSeoMeta inside your nuxt.config. **Core Concepts** h1. **Nuxt Config SEO Meta** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply content audit fixes (#78) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>](https://github.com/harlan-zw/nuxt-seo-utils/pull/78). [Copy for LLMs h2. [Introduction](#introduction) The [`useSeoMeta()`](https://nuxt.com/docs/api/composables/use-seo-meta#useseometa) composable is a powerful tool for managing SEO meta tags. Nuxt SEO Utils allows you to provide the `useSeoMeta()` input within your `nuxt.config`. h3. [Available Properties](#available-properties) For a complete list of all available SEO meta properties, see the [**Nuxt **`useSeoMeta()`** documentation**](https://nuxt.com/docs/api/composables/use-seo-meta#parameters). Common properties include: **Basic SEO:** - `title` - Page title - `description` - Page description - `keywords` - Page keywords - `author` - Content author - `robots` - Robot crawling instructions - `canonical` - Canonical URL (set to `false` to disable) **Open Graph (Facebook/Social):** - `ogTitle` - Open Graph title - `ogDescription` - Open Graph description - `ogImage` - Open Graph image URL - `ogUrl` - Open Graph URL - `ogType` - Open Graph type (e.g., 'website', 'article') - `ogSiteName` - Site name - `ogLocale` - Locale (e.g., 'en_US') **Twitter Card:** - `twitterCard` - Twitter card type (e.g., 'summary_large_image') - `twitterSite` - Twitter site handle - `twitterCreator` - Twitter creator handle - `twitterTitle` - Twitter title - `twitterDescription` - Twitter description - `twitterImage` - Twitter image URL **App & Mobile:** - `applicationName` - Application name - `themeColor` - Theme color (can be array with media queries) - `colorScheme` - Color scheme support (e.g., 'dark light') - `viewport` - Viewport settings **Advanced:** - `fallbackTitle` - Fallback title when page title is not set h2. [Usage](#usage) To use it, simply add within the `seo.meta` config of your `nuxt.config`: nuxt.config.ts ``` export default defineNuxtConfig({ seo: { meta: { // Basic SEO description: 'My awesome website', author: 'Harlan Wilton', fallbackTitle: 'My Awesome Site', // Used when page doesn't set a title // Theme & Color themeColor: [ { content: '#18181b', media: '(prefers-color-scheme: dark)' }, { content: 'white', media: '(prefers-color-scheme: light)' }, ], colorScheme: 'dark light', // Social Media twitterCreator: '@mytwitter', twitterSite: '@mysite', // App Info applicationName: 'My App', // Nuxt SEO Utils already sets the below tags for you ogSiteName: 'My Site Name', ogLocale: 'en_US', ogType: 'website', ogUrl: 'https://example.com', ogTitle: 'My Site', // Other Nuxt SEO modules handle these ogImage: 'https://example.com/my-og-image.png', robots: 'index, follow', } }, }) ``` The functionality is the same as the composable without reactivity. It has a higher priority than `app.head`. These settings provide **site-wide defaults**. You can override them on individual pages using the `useSeoMeta()` composable. [Edit this page](https://github.com/harlan-zw/nuxt-seo-utils/edit/main/docs/content/2.guides/4.nuxt-config-seo-meta.md) [Markdown For LLMs](https://nuxtseo.com/docs/seo-utils/guides/nuxt-config-seo-meta/docs/seo-utils/guides/nuxt-config-seo-meta.md) **Did this page help you? ** [**I18n & Localization** How Nuxt SEO Utils handles language and locale configuration.](https://nuxtseo.com/docs/seo-utils/guides/nuxt-config-seo-meta/docs/seo-utils/guides/i18n) [**Config** Configuration options for Nuxt SEO Utils module.](https://nuxtseo.com/docs/seo-utils/guides/nuxt-config-seo-meta/docs/seo-utils/api/config) **On this page** - [Introduction](#introduction) - [Usage](#usage) --- ### useSkewProtection() · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/api/use-skew-protection Description: Composable for accessing skew protection state and methods. **Nuxt API** h1. **useSkewProtection()** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [feat: `useSkewProtection()` lazy mode](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/f3179f38be9d3f4cf3dc51525b091c95ae7f8a85). [Copy for LLMs The main composable for interacting with Nuxt Skew Protection. h2. [Usage](#usage) ``` const skew = useSkewProtection() ``` h2. [Options](#options) h3. [`lazy`](#lazy) - Type: `boolean` - Default: `false` When `false` (default), the connection is established automatically when the composable is called in a mounted component. When `true`, you must call `connect()` manually. ``` // Auto-connect on mount (default) const skew = useSkewProtection() // Manual connection control const skew = useSkewProtection({ lazy: true }) skew.connect() // Connect when ready ``` h2. [Returns](#returns) h3. [`manifest`](#manifest) - Type: `Ref<NuxtAppManifestMeta | null>` The current build manifest from `builds/latest.json`. ``` const { manifest } = useSkewProtection() console.log(manifest.value?.id) // Current buildId console.log(manifest.value?.timestamp) // Build timestamp ``` h3. [`clientVersion`](#clientversion) - Type: `string` The user's current version (from cookie or buildId). ``` const { clientVersion } = useSkewProtection() console.log(clientVersion) // "abc123" ``` h3. [`isOutdated`](#isoutdated) - Type: `ComputedRef<boolean>` Whether the user's version is outdated. ``` const { isOutdated } = useSkewProtection() if (isOutdated.value) { console.log('User is on an old version') } ``` h3. [`isConnected`](#isconnected) - Type: `ComputedRef<boolean>` Whether the SSE/WebSocket connection is currently active. ``` const { isConnected } = useSkewProtection() if (isConnected.value) { console.log('Connected to update stream') } ``` h3. [`connect()`](#connect) - Type: `() => void` Manually establish the connection. Only needed when using `lazy: true`. ``` const skew = useSkewProtection({ lazy: true }) // Connect when user clicks a button function handleConnect() { skew.connect() } ``` h3. [`disconnect()`](#disconnect) - Type: `() => void` Manually close the connection. Useful for cleanup or pausing updates. ``` const { disconnect } = useSkewProtection() // Disconnect when leaving a section onBeforeRouteLeave(() => { disconnect() }) ``` h3. [`checkForUpdates()`](#checkforupdates) - Type: `() => Promise<void>` Manually trigger an update check. ``` const { checkForUpdates } = useSkewProtection() // Check for updates await checkForUpdates() ``` h3. [`onAppOutdated()`](#onappoutdated) - Type: `(callback: (manifest: NuxtAppManifestMeta | null) => void) => void` Triggers when a new version is detected, regardless of whether it affects the current session. Uses Nuxt's built-in `app:manifest:update` hook. **Use case:** Make next page load a hard navigation (forces HTML reload). ``` const { onAppOutdated } = useSkewProtection() onAppOutdated((manifest) => { console.log('New version available:', manifest?.id) // Show passive "Update available" badge }) ``` h3. [`onCurrentChunksOutdated()`](#oncurrentchunksoutdated) - Type: `(callback: (payload: ChunksOutdatedPayload) => void) => void` Triggers **only** when the user's loaded JavaScript modules have been changed in a new deployment. Consider the case of a user on a blog post when you deploy a new version that only changes the homepage. The user's session is unaffected and they can continue browsing without interruption. In this case, `onCurrentChunksOutdated` will not trigger. Under the hood this tracks modules using a service worker. ``` const { onCurrentChunksOutdated } = useSkewProtection() onCurrentChunksOutdated((payload) => { console.log('Deleted chunks:', payload.deletedChunks) console.log('Invalidated modules:', payload.invalidatedModules) console.log('Passed releases:', payload.passedReleases) // Show notification alert('Please reload to get the latest version') }) ``` h2. [Type Definitions](#type-definitions) ``` interface NuxtAppManifestMeta { id: string timestamp: number skewProtection?: { current: string versions: Record<string, VersionInfo> } } interface VersionInfo { timestamp: string expires: string assets: string[] deletedChunks?: string[] } interface ChunksOutdatedPayload { deletedChunks: string[] invalidatedModules: string[] passedReleases: string[] } ``` h2. [Examples](#examples) h3. [Custom Invalidation Handler](#custom-invalidation-handler) ``` const { onCurrentChunksOutdated } = useSkewProtection() onCurrentChunksOutdated(({ invalidatedModules, passedReleases }) => { // Track in analytics analytics.track('chunks_outdated', { count: invalidatedModules.length, modules: invalidatedModules, releases: passedReleases }) // Show custom UI showUpdateDialog({ title: 'Update Required', message: \`${invalidatedModules.length} modules need updating (${passedReleases.length} releases passed)\` }) }) ``` h3. [Version Information Display](#version-information-display) ``` <script setup> const { clientVersion, manifest } = useSkewProtection() const buildDate = computed(() => { if (!manifest.value?.timestamp) return null return new Date(manifest.value.timestamp).toLocaleString() }) </script> <template> <div class="version-info"> <p>Your version: {{ clientVersion }}</p> <p>Latest version: {{ manifest?.id }}</p> <p>Build date: {{ buildDate }}</p> </div> </template> ``` h3. [Programmatic Update Check](#programmatic-update-check) Note: Only relevant if you're using `polling`. ``` const { checkForUpdates, isOutdated } = useSkewProtection() async function handleUserAction() { // Check for updates before critical action await checkForUpdates() if (isOutdated.value) { const shouldReload = confirm('A new version is available. Reload now?') if (shouldReload) { window.location.reload() } } else { // Proceed with action await performCriticalAction() } } ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/3.api/1.use-skew-protection.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/api/use-skew-protection/docs/skew-protection/api/use-skew-protection.md) **Did this page help you? ** [**Nuxt Hooks** Learn how to use Nuxt hooks to respond to skew protection events.](https://nuxtseo.com/docs/skew-protection/api/use-skew-protection/docs/skew-protection/api/nuxt-hooks) [**<SkewNotification>** Headless component for displaying update notifications.](https://nuxtseo.com/docs/skew-protection/api/use-skew-protection/docs/skew-protection/api/skew-notification) **On this page** - [Usage](#usage) - [Options](#options) - [Returns](#returns) - [Type Definitions](#type-definitions) - [Examples](#examples) --- ### Check for Update Strategy · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/guides/update-strategies Description: Choose the best update detection strategy for your platform. **Core Concepts** h1. **Check for Update Strategy** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs When a new version of your Nuxt app is deployed we need to notify active users of the new version. Out of the box, Nuxt applies a polling strategy, checking every hour if the build is out of date. Alternative strategies like Server-Sent Events (SSE), WebSockets, and external providers provide real-time notifications, but have different platform compatibilities and performance trade-offs. | **Strategy** | **Real-time** | **Platforms** | **Server Load** | **Client Connections** | | --- | --- | --- | --- | --- | | **[**Polling**](#polling)** | ❌ Delayed | ✅ All | ✅ Minimal | ✅ None | | **[**SSE**](#sse)** | ✅ Instant | ⚠️ Node.js/Bun/Deno | ⚠️ Low | ⚠️ Persistent | | **[**WebSocket**](#websocket)** | ✅ Instant | ⚠️ Cloudflare Durable | ⚠️ Moderate | ⚠️ Persistent | | **[**Adapter**](#adapter)** | ✅ Instant | ✅ All (incl. Static) | ✅ None (external) | ✅ External provider | h2. [Performance Considerations](#performance-considerations) SSE and WebSocket strategies maintain persistent connections on your server. For **high-traffic applications** (10,000+ concurrent users), using an [**Adapter**](#adapter) with external providers like Ably or Pusher may be worthwhile. Bot traffic is automatically excluded from connections when `@nuxtjs/robots` is installed. See [**Bot Traffic Filtering**](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/guides/performance#bot-traffic-filtering). See the [**Performance Guide**](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/guides/performance) for more details. h2. [Platform Defaults](#platform-defaults) The best strategy is automatically selected based on your deployment platform, but you can override it in your configuration using [`updateStrategy`](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/api/config#updatestrategy-polling--sse--ws). | **Provider** | **updateStrategy** | | --- | --- | | **Node.js** | `sse` | | **Bun** | `sse` | | **Deno** | `sse` | | **Vercel** | `sse` | | **Cloudflare Workers** | `polling` | | **Cloudflare Durable** | `ws` | | **Netlify** | `sse` | | **Static/Prerendered** | `polling` or [**adapter**](#adapter) | Note: For `ws` strategy, you must enable `nuxt.options.nitro.experimental.websocket`. h2. [Polling](#polling) The default strategy that works on **all platforms**. Uses Nuxt's built-in `experimental.checkOutdatedBuildInterval` to periodically fetch `builds/latest.json` to determine if a new deployment has occurred. Update `checkOutdatedBuildInterval` to a quicker polling interval—1 hour is too slow for most applications. nuxt.config.ts ``` export default defineNuxtConfig({ skewProtection: { updateStrategy: 'polling' }, experimental: { checkOutdatedBuildInterval: 5 * 60 * 1000 // 5 minutes } }) ``` h2. [SSE](#sse) Real-time updates using **Server-Sent Events**. SSE does keep persistent connections open, be mindful of this if you have a high-traffic site. SSE has less overhead than WebSockets. nuxt.config.ts ``` export default defineNuxtConfig({ skewProtection: { updateStrategy: 'sse' } }) ``` h2. [WebSocket](#websocket) Real-time updates using **WebSockets**. These are mostly useless as SSE is more performant however for certain conditions like using Cloudflare Workers and some proxying services, WebSockets may be the best option. Cloudflare Durable Objects ``` export default defineNuxtConfig({ nitro: { preset: 'cloudflare-durable', experimental: { websocket: true } }, skewProtection: { updateStrategy: 'ws' } }) ``` h2. [Adapter](#adapter) Adapters allow you to use **external WebSocket providers** for real-time update notifications. This is ideal for **static/prerendered sites** or when you want to offload connection management to a third-party service. Available adapters: - [**Pusher**](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/providers/external#pusher) - [**Ably**](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/providers/external#ably) - [**Custom**](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/providers/external#custom-adapters) nuxt.config.ts ``` import { pusherAdapter } from 'nuxt-skew-protection/adapters/pusher' export default defineNuxtConfig({ skewProtection: { updateStrategy: pusherAdapter({ key: process.env.PUSHER_KEY, cluster: process.env.PUSHER_CLUSTER, appId: process.env.PUSHER_APP_ID, secret: process.env.PUSHER_SECRET, }) } }) ``` See [**External Providers**](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/providers/external) for detailed setup guides. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/2.guides/0.update-strategies.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/guides/update-strategies.md) **Did this page help you? ** [**Installation** Get started with Nuxt Skew Protection by installing the dependency to your project.](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/getting-started/installation) [**Update Notifications** React to version updates using useSkewProtection() composable.](https://nuxtseo.com/docs/skew-protection/guides/update-strategies/docs/skew-protection/guides/immediate-updates) **On this page** - [Performance Considerations](#performance-considerations) - [Platform Defaults](#platform-defaults) - [Polling](#polling) - [SSE](#sse) - [WebSocket](#websocket) - [Adapter](#adapter) --- ### Install Nuxt Skew Protection · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/getting-started/installation Description: Get started with Nuxt Skew Protection by installing the dependency to your project. **Getting Started** h1. **Install Nuxt Skew Protection** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: require @nuxtjs/robots v5.6.7+](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/60eae7e7fee92b875ae8d8307cce2353892540e6). [Copy for LLMs h2. [Setup Module](#setup-module) Not sure what Skew Protection is? Check out the [**introduction**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/getting-started/introduction). Install the dependency and add it to your Nuxt config. `npx nuxt module add skew-protection` `npm i nuxt-skew-protection` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-skew-protection', ], }) ``` `yarn add nuxt-skew-protection` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-skew-protection', ], }) ``` `pnpm i nuxt-skew-protection` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-skew-protection', ], }) ``` `bun i nuxt-skew-protection` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-skew-protection', ], }) ``` h3. [Required: Nuxt Robots](#required-nuxt-robots) This module requires [**@nuxtjs/robots**](https://nuxtseo.com/robots) (v5.6.7+) to detect bots and prevent unnecessary WebSocket/SSE connections from crawlers. ``` npx nuxi module add @nuxtjs/robots ``` h3. [License](#license) Nuxt AI Search requires a [**Nuxt SEO Pro**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/pro) license. ``` NUXT_SEO_PRO_KEY=your-license-key-here ``` [Sign in](https://nuxtseo.com/docs/skew-protection/getting-started/installation/pro) to see your license key. h2. [Configure Module](#configure-module) Once installed the module will cache previous build assets and select an [**update detection strategy**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/update-strategies) for your environment. Follow these configuration steps: h3. [1. Persistent Storage](#_1-persistent-storage) Configure a [**persistant storage**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/storage-configuration) so that Nuxt build assets can have longer lifetime across deployments. The module will automatically prune old versions based on the [**retention settings**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/storage-configuration#retention-periods). If you're using GitHub Actions you can use this config: your-github-workflow.yml ``` jobs: build: runs-on: ubuntu-latest steps: # Install... - name: Cache for Nuxt SEO uses: actions/cache@v4 with: path: node_modules/.cache/nuxt-seo key: deployment-${{ github.ref_name }} # Build, etc.. ``` Alternative storage options include: - [**Redis Storage**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/storage-configuration#redis) - [**Cloudflare KV**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/providers/cloudflare) - [**CDN Usage**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/storage-configuration#cdn-usage) h3. [2. Understand How Updates Are Detected](#_2-understand-how-updates-are-detected) The strategy used to detect new deployments can vary based on your hosting environment and performance needs. The module automatically selects the best strategy either: Polling, WebSockets or SSE. If you're using NuxtHub or Cloudflare directly, check the [**Cloudflare guide**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/providers/cloudflare) for recommended settings. Otherwise, please check the [**update detection strategy**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/update-strategies) documentation. This site uses [**Cloudflare Durable Objects**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/storage-configuration#github-actions) to check for updates via WebSockets. h3. [3. Add UI Component](#_3-add-ui-component) Users need to know when updates are available. [**Add notifications**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/immediate-updates) to prompt them. You can use this minimal Nuxt UI example, add it to your `app.vue` or layout component. app.vue ``` <template> <SkewNotification v-slot="{ isCurrentChunksOutdated, dismiss, reload }" force-open> <Transition enter-active-class="transition duration-300 ease-out" enter-from-class="opacity-0 translate-y-2" enter-to-class="opacity-100 translate-y-0" leave-active-class="transition duration-200 ease-in" leave-from-class="opacity-100 translate-y-0" leave-to-class="opacity-0 translate-y-2" > <div v-if="isCurrentChunksOutdated" class="fixed bottom-4 right-4 z-50"> <div class="flex items-center gap-3 bg-white dark:bg-gray-900 rounded-full shadow-lg ring-1 ring-gray-200 dark:ring-gray-800 px-4 py-3"> <span class="text-lg">✨</span> <div class="text-sm font-medium"> Update available </div> <UButton color="primary" size="xs" label="Refresh" @click="reload" /> <UButton color="gray" variant="ghost" size="xs" icon="i-heroicons-x-mark-20-solid" @click="dismiss" /> </div> </div> </Transition> </SkewNotification> </template> ``` This will only be shown when the user's loaded chunks are invalidated by a new deployment. For other examples see the [**UI Examples**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/ui-examples) documentation. h2. [Verifying Installation](#verifying-installation) You can verify the installation by checking your deployed Nuxt App Manifest file at [**/_nuxt/builds/latest.json**](https://nuxtseo.com/_nuxt/builds/latest.json), which should now include previous build manifests. Perform a test deployment modifying your `app.vue` to trigger an update notification in your browser. h2. [Next Steps](#next-steps) You've successfully installed Nuxt Skew Protection and configured it for your project. - Check your [**Cookie Consent integration**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/cookie-consent) if applicable. - Add version checks to your [**server routes**](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/immediate-updates#server-side-skew-detection). [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/1.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/getting-started/installation.md) **Did this page help you? ** [**Introduction** Eliminate version skew issues in your Nuxt application with intelligent update notifications and long-lived build assets.](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/getting-started/introduction) [**Check for Update Strategy** Choose the best update detection strategy for your platform.](https://nuxtseo.com/docs/skew-protection/getting-started/installation/docs/skew-protection/guides/update-strategies) **On this page** - [Setup Module](#setup-module) - [Configure Module](#configure-module) - [Verifying Installation](#verifying-installation) - [Next Steps](#next-steps) --- ### Update Notifications · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/guides/immediate-updates Description: React to version updates using useSkewProtection() composable. **Core Concepts** h1. **Update Notifications** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs Using the [**update strategies**](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/guides/update-strategies) the module knows when a new version is deployed. You can use the built-in [`<SkewNotification>`](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/api/skew-notification) component or build your own using the [`useSkewProtection()`](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/api/use-skew-protection) composable. Update notifications can be an annoying user experience if not handled properly. The module provides two hooks to differentiate between deployments: - [`onAppOutdated()`](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/api/use-skew-protection#onappoutdated) - triggers whenever a new version is detected. - [`onCurrentChunksOutdated()`](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/api/use-skew-protection#oncurrentchunksoutdated) - triggers only when the user's loaded chunks are invalidated by the new deployment. h2. [Setting Up Notifications](#setting-up-notifications) The `<SkewNotification>` component is a client-only wrapper around the `useSkewProtection()` composable. When the user's current chunks are outdated: app.vue ``` <template> <SkewNotification v-slot="{ isCurrentChunksOutdated }"> <Transition name="slide-up"> <div v-if="isCurrentChunksOutdated" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-blue-600 text-white px-4 py-2 rounded shadow"> An update is available. Please refresh the page. </div> </Transition> </SkewNotification> </template> ``` Please see the [**UI Examples**](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/guides/ui-examples) guide for more user friendly notification patterns. h2. [Server-Side Skew Detection](#server-side-skew-detection) Detect outdated clients in server handlers using [`isClientOutdated()`](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/nitro-api/is-client-outdated): server/api/data.ts ``` import { isClientOutdated } from 'nuxt-skew-protection/server' export default defineEventHandler((event) => { if (isClientOutdated(event)) { setResponseStatus(event, 409) return { error: 'Client outdated', requiresReload: true } } return { data: '...' } }) ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/2.guides/1.immediate-updates.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/guides/immediate-updates.md) **Did this page help you? ** [**Check for Update Strategy** Choose the best update detection strategy for your platform.](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/guides/update-strategies) [**Performance** Optimize skew protection for high-traffic applications.](https://nuxtseo.com/docs/skew-protection/guides/immediate-updates/docs/skew-protection/guides/performance) **On this page** - [Setting Up Notifications](#setting-up-notifications) - [Server-Side Skew Detection](#server-side-skew-detection) --- ### Performance · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/guides/performance Description: Optimize skew protection for high-traffic applications. **Core Concepts** h1. **Performance** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs h2. [Connection Overhead](#connection-overhead) SSE and WebSocket strategies maintain persistent connections to your server. For **small to medium applications** (under 10,000 concurrent users), this is typically fine—modern servers handle thousands of idle connections efficiently. For **high-traffic applications** (10,000+ concurrent users), use an [**Adapter**](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/guides/update-strategies#adapter) with [**Ably**](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/providers/external#ably) or [**Pusher**](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/providers/external#pusher). These services are built for millions of concurrent connections and include automatic reconnection, global edge distribution, and connection multiplexing. h2. [Selective Route Protection](#selective-route-protection) Connections are **not established until** [`<SkewNotification>`](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/api/skew-notification) or [`useSkewProtection()`](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/api/use-skew-protection) is used. The plugin only sets up infrastructure—actual SSE/WebSocket connections happen when a component using skew protection mounts. This means you can control exactly when connections are created by placing `<SkewNotification>` only on specific pages. Once connected, the connection persists for the session (even when navigating away from the page). This is intentional—once you're listening for updates, you keep listening. Use [`disconnect()`](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/api/use-skew-protection#disconnect) if you need to close it manually. h3. [Page-Specific Protection](#page-specific-protection) Add `<SkewNotification>` or `useSkewProtection()` only where needed: pages/dashboard.vue ``` <script setup lang="ts"> const skew = useSkewProtection() skew.onAppOutdated(() => { // handle update }) </script> ``` pages/admin.vue ``` <template> <div> <SkewNotification v-slot="{ isAppOutdated, reload }"> <button v-if="isAppOutdated" @click="reload"> Update available </button> </SkewNotification> <!-- page content --> </div> </template> ``` h3. [Route-Based Logic](#route-based-logic) Use route middleware or conditional rendering for more control: layouts/default.vue ``` <script setup lang="ts"> const route = useRoute() const protectedRoutes = ['/dashboard', '/admin', '/settings'] const shouldProtect = computed(() => protectedRoutes.some(r => route.path.startsWith(r)) ) </script> <template> <div> <SkewNotification v-if="shouldProtect" v-slot="{ isAppOutdated, reload }"> <button v-if="isAppOutdated" @click="reload"> Update available </button> </SkewNotification> <slot /> </div> </template> ``` This reduces active connections to only users on protected routes. h3. [Lazy Connection Mode](#lazy-connection-mode) For fine-grained control, use [`lazy: true`](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/api/use-skew-protection#lazy) to prevent auto-connect and manually control when connections are established: pages/checkout.vue ``` <script setup lang="ts"> const skew = useSkewProtection({ lazy: true }) // Only connect after user starts checkout function startCheckout() { skew.connect() // proceed with checkout... } // Disconnect when done onBeforeRouteLeave(() => { skew.disconnect() }) </script> ``` This is useful when you want connections only during specific user flows rather than entire pages. h2. [Bot Traffic Filtering](#bot-traffic-filtering) Bot and crawler traffic is automatically excluded from SSE/WebSocket connections when `@nuxtjs/robots` is installed. This prevents bots from inflating connection counts and wasting server resources. h3. [Setup](#setup) Install `@nuxtjs/robots` as a peer dependency: ``` npx nuxi module add @nuxtjs/robots ``` The module uses `useBotDetection()` from `@nuxtjs/robots` to detect bot user agents (Googlebot, Bingbot, etc.) and skip establishing real-time connections for them. h3. [How It Works](#how-it-works) When a bot visits your site: 1. `useBotDetection()` identifies the bot via user agent 2. The SSE/WebSocket connection is skipped entirely 3. No server resources are consumed for bot traffic 4. Bot visits are excluded from [`useActiveConnections()`](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/api/use-active-connections) stats This is automatic—no configuration needed beyond installing `@nuxtjs/robots`. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/2.guides/2.performance.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/guides/performance.md) **Did this page help you? ** [**Update Notifications** React to version updates using useSkewProtection() composable.](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/guides/immediate-updates) [**Persistent Storage** Configure persistent storage for build assets across deployments.](https://nuxtseo.com/docs/skew-protection/guides/performance/docs/skew-protection/guides/storage-configuration) **On this page** - [Connection Overhead](#connection-overhead) - [Selective Route Protection](#selective-route-protection) - [Bot Traffic Filtering](#bot-traffic-filtering) --- ### Persistent Storage · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/guides/storage-configuration Description: Configure persistent storage for build assets across deployments. **Core Concepts** h1. **Persistent Storage** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs Nuxt Skew Protection needs persistent storage to preserve previous build assets across deployments. By default, it uses the filesystem storage located at `node_modules/.cache/nuxt-seo/skew-protection`, but this may not be sufficient for all deployment environments, especially in CI/CD pipelines where caches may be cleared frequently. h2. [Retention Periods](#retention-periods) You can control how long versions are kept using [`retentionDays`](https://nuxtseo.com/docs/skew-protection/guides/storage-configuration/docs/skew-protection/api/config#retentiondays-number) and [`maxNumberOfVersions`](https://nuxtseo.com/docs/skew-protection/guides/storage-configuration/docs/skew-protection/api/config#maxnumberofversions-number). By default, the configuration is setup for optimal crawler compatibility. nuxt.config.ts ``` export default defineNuxtConfig({ skewProtection: { // Keep versions for 45 days retentionDays: 45, // Keep maximum 15 versions maxNumberOfVersions: 15 } }) ``` Cleanup happens **during build**, removing: - Versions older than `retentionDays` - Oldest versions beyond `maxNumberOfVersions` h2. [GitHub Actions](#github-actions) The default filesystem storage works with GitHub Actions caching to persist build assets between deployments using the `actions/cache` GitHub Action. The cache key must be unique per deployment, otherwise `actions/cache` won't save new assets (it only saves on cache miss). your-github-workflow.yml ``` jobs: build: runs-on: ubuntu-latest steps: # Install... - name: Cache for Nuxt SEO uses: actions/cache@v4 with: path: node_modules/.cache/nuxt-seo key: nuxt-skew-${{ github.sha }} restore-keys: | nuxt-skew- # Build, etc.. ``` The `restore-keys` fallback ensures previous assets are restored, while the unique `github.sha` key ensures new assets are saved after each build. h2. [Cloudflare Deployment](#cloudflare-deployment) If you have your deployments running through Cloudflare then the `node_modules` folder should automatically persist between deployments, so no additional configuration is required. h2. [Cloudflare KV](#cloudflare-kv) If you're deploying to Cloudflare using your own CI pipeline, you can use Cloudflare KV storage to persist build assets. See the dedicated [**Cloudflare Deployment**](https://nuxtseo.com/docs/skew-protection/guides/storage-configuration/docs/skew-protection/providers/cloudflare) guide which covers storage configuration in detail. h2. [Redis](#redis) nuxt.config.ts ``` export default defineNuxtConfig({ skewProtection: { storage: { driver: 'redis', host: process.env.REDIS_HOST || 'localhost', port: Number(process.env.REDIS_PORT) || 6379, password: process.env.REDIS_PASSWORD, db: 0, base: 'skew-protection' } } }) ``` h2. [Upstash Redis](#upstash-redis) For serverless deployments like Netlify or Vercel, [**Upstash**](https://upstash.com/) provides a Redis-compatible database with an HTTP API. Install the Upstash Redis client: Terminal ``` npx nypm add @upstash/redis ``` nuxt.config.ts ``` export default defineNuxtConfig({ skewProtection: { storage: { driver: 'upstash', url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, base: 'skew-protection' } } }) ``` You can find your REST URL and token in the [**Upstash Console**](https://console.upstash.com/) after creating a database. h2. [CDN Usage](#cdn-usage) In cases where you're already using a CDN with long cache times on `/_nuxt/**` assets, you may want to disable persistent storage using [`bundlePreviousDeploymentChunks`](https://nuxtseo.com/docs/skew-protection/guides/storage-configuration/docs/skew-protection/api/config#bundlepreviousdeploymentchunks-boolean). ``` export default defineNuxtConfig({ skewProtection: { // Disable persistent storage bundlePreviousDeploymentChunks: false } }) ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/2.guides/3.storage-configuration.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/guides/storage-configuration/docs/skew-protection/guides/storage-configuration.md) **Did this page help you? ** [**Performance** Optimize skew protection for high-traffic applications.](https://nuxtseo.com/docs/skew-protection/guides/storage-configuration/docs/skew-protection/guides/performance) [**Notification UI** Example implementations of update notifications using the SkewNotification component.](https://nuxtseo.com/docs/skew-protection/guides/storage-configuration/docs/skew-protection/guides/ui-examples) **On this page** - [Retention Periods](#retention-periods) - [GitHub Actions](#github-actions) - [Cloudflare Deployment](#cloudflare-deployment) - [Cloudflare KV](#cloudflare-kv) - [Redis](#redis) - [Upstash Redis](#upstash-redis) - [CDN Usage](#cdn-usage) --- ### Notification UI · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/guides/ui-examples Description: Example implementations of update notifications using the SkewNotification component. **Core Concepts** h1. **Notification UI** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs These examples use the [`<SkewNotification>`](https://nuxtseo.com/docs/skew-protection/guides/ui-examples/docs/skew-protection/api/skew-notification) component. See the [**API reference**](https://nuxtseo.com/docs/skew-protection/guides/ui-examples/docs/skew-protection/api/skew-notification) for all available slot props. h2. [Minimal Pill](#minimal-pill) Compact notification using Nuxt UI. ✨ **Update available ** app.vue ``` <template> <SkewNotification v-slot="{ isCurrentChunksOutdated, dismiss, reload }" force-open> <Transition enter-active-class="transition duration-300 ease-out" enter-from-class="opacity-0 translate-y-2" enter-to-class="opacity-100 translate-y-0" leave-active-class="transition duration-200 ease-in" leave-from-class="opacity-100 translate-y-0" leave-to-class="opacity-0 translate-y-2" > <div v-if="isCurrentChunksOutdated" class="fixed bottom-4 right-4 z-50"> <div class="flex items-center gap-3 bg-white dark:bg-gray-900 rounded-full shadow-lg ring-1 ring-gray-200 dark:ring-gray-800 px-4 py-3"> <span class="text-lg">✨</span> <div class="text-sm font-medium"> Update available </div> <UButton color="primary" size="xs" label="Refresh" @click="reload" /> <UButton color="gray" variant="ghost" size="xs" icon="i-heroicons-x-mark-20-solid" @click="dismiss" /> </div> </div> </Transition> </SkewNotification> </template> ``` h2. [Native CSS](#native-css) ✨ New update available! app.vue ``` <template> <SkewNotification v-slot="{ isCurrentChunksOutdated, dismiss, reload, timeAgo }"> <Transition name="slide-up"> <div v-if="isCurrentChunksOutdated" class="skew-notification"> <div class="skew-notification-content"> <div class="skew-notification-message"> <span class="skew-notification-icon">✨</span> <div> <div class="skew-notification-title"> New update available! </div> <div v-if="timeAgo" class="skew-notification-subtitle"> Released {{ timeAgo }} </div> </div> </div> <div class="skew-notification-actions"> <button class="skew-button skew-button-secondary" @click="dismiss"> Dismiss </button> <button class="skew-button skew-button-primary" @click="reload"> Refresh </button> </div> </div> </div> </Transition> </SkewNotification> </template> <style> .skew-notification { position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 9999; max-width: 400px; } .skew-notification-content { background: white; border-radius: 12px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); padding: 1rem 1.25rem; border: 1px solid rgba(0, 0, 0, 0.05); } .skew-notification-message { display: flex; gap: 0.75rem; margin-bottom: 1rem; } .skew-notification-title { font-weight: 600; font-size: 0.9375rem; } .skew-notification-subtitle { font-size: 0.8125rem; color: #666; } .skew-notification-actions { display: flex; gap: 0.5rem; justify-content: flex-end; } .skew-button { padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.875rem; font-weight: 500; border: none; cursor: pointer; } .skew-button-primary { background: #18181b; color: white; } .skew-button-secondary { background: #f4f4f5; color: #52525b; } .slide-up-enter-active, .slide-up-leave-active { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .slide-up-enter-from { opacity: 0; transform: translateY(1rem); } </style> ``` Custom styled notification without UI library: [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/2.guides/4.ui-examples.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/guides/ui-examples/docs/skew-protection/guides/ui-examples.md) **Did this page help you? ** [**Persistent Storage** Configure persistent storage for build assets across deployments.](https://nuxtseo.com/docs/skew-protection/guides/ui-examples/docs/skew-protection/guides/storage-configuration) [**Cookie Consent** Information about cookie consent requirements for using Nuxt Skew Protection.](https://nuxtseo.com/docs/skew-protection/guides/ui-examples/docs/skew-protection/guides/cookie-consent) **On this page** - [Minimal Pill](#minimal-pill) - [Native CSS](#native-css) --- ### Cookie Consent · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/guides/cookie-consent Description: Information about cookie consent requirements for using Nuxt Skew Protection. **Core Concepts** h1. **Cookie Consent** Last updated **Dec 8, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clean up](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/adaeea8f4e688067347a59358b0585229d36d9c1). [Copy for LLMs h2. [Skew Cookie](#skew-cookie) The module stores the user's current build version in a cookie named `__nkpv`. See [**cookie options**](https://nuxtseo.com/docs/skew-protection/guides/cookie-consent/docs/skew-protection/api/config#cookie) for full details. Update your cookie consent settings to allow this cookie. h2. [Cookie Consent Requirements](#cookie-consent-requirements) > Cookie Name: `__nkpv` Purpose: Store the user's current build version for Nuxt Skew Protection. Duration: Session (deleted when the browser is closed). Type: Functional Cookie [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/2.guides/5.cookie-consent.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/guides/cookie-consent/docs/skew-protection/guides/cookie-consent.md) **Did this page help you? ** [**Notification UI** Example implementations of update notifications using the SkewNotification component.](https://nuxtseo.com/docs/skew-protection/guides/cookie-consent/docs/skew-protection/guides/ui-examples) [**View Active Connections** Monitor active connections and version distribution in real-time.](https://nuxtseo.com/docs/skew-protection/guides/cookie-consent/docs/skew-protection/guides/live-connections) **On this page** - [Skew Cookie](#skew-cookie) - [Cookie Consent Requirements](#cookie-consent-requirements) --- ### View Active Connections · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/guides/live-connections Description: Monitor active connections and version distribution in real-time. **Core Concepts** h1. **View Active Connections** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs Track how many users are connected to your application and which versions they're running. This is useful for admin dashboards, deployment monitoring, and understanding rollout progress. Connection tracking only works with `sse` or `ws` update strategies. It does not support polling or external adapters (Pusher/Ably). h2. [Setup](#setup) Enable connection tracking in your Nuxt config using [`connectionTracking`](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/skew-protection/api/config#connectiontracking-boolean): nuxt.config.ts ``` export default defineNuxtConfig({ skewProtection: { connectionTracking: true } }) ``` h3. [Limitations](#limitations) - **Single instance only**: Stats are per-server process. With horizontal scaling, each instance only sees its own connections. - **SSE/WS only**: Does not work with `polling` strategy or external adapters (Pusher/Ably). - **Bot traffic excluded**: When `@nuxtjs/robots` is installed, bot/crawler traffic is automatically excluded from connection counts. See [**Bot Traffic Filtering**](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/guides/performance#bot-traffic-filtering). h2. [Using the Composable](#using-the-composable) The [`useActiveConnections()`](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/skew-protection/api/use-active-connections) composable provides reactive access to connection statistics: ``` <script setup lang="ts"> import { useActiveConnections } from '#imports' const { total, versions } = useActiveConnections() </script> <template> <div> <p>Active users: {{ total }}</p> <ul> <li v-for="(count, version) in versions" :key="version"> {{ version.slice(0, 8) }}: {{ count }} users </li> </ul> </div> </template> ``` h3. [Return Values](#return-values) | **Property** | **Type** | **Description** | | --- | --- | --- | | `total` | `ComputedRef<number>` | Total active connections | | `versions` | `ComputedRef<Record<string, number>>` | Map of buildId → connection count | h2. [Example: Admin Dashboard](#example-admin-dashboard) ``` <script setup> const { total, versions } = useActiveConnections() const currentBuildId = useRuntimeConfig().app.buildId const stats = computed(() => { const current = versions.value[currentBuildId] || 0 const outdated = total.value - current return { total: total.value, current, outdated, percentUpToDate: total.value ? Math.round((current / total.value) * 100) : 100 } }) </script> <template> <div class="grid grid-cols-4 gap-4"> <div class="stat"> <span class="label">Total Users</span> <span class="value">{{ stats.total }}</span> </div> <div class="stat"> <span class="label">On Latest</span> <span class="value text-green">{{ stats.current }}</span> </div> <div class="stat"> <span class="label">On Old Version</span> <span class="value text-orange">{{ stats.outdated }}</span> </div> <div class="stat"> <span class="label">Rollout Progress</span> <span class="value">{{ stats.percentUpToDate }}%</span> </div> </div> </template> ``` h2. [Custom Behavior with Hooks](#custom-behavior-with-hooks) For advanced use cases, you can use hooks directly instead of the composable. h3. [Client-Side: `skew:message`](#client-side-skewmessage) Listen to all messages from the SSE/WebSocket connection: plugins/custom-stats.client.ts ``` export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hooks.hook('skew:message', (message) => { if (message.type === 'stats') { // Custom handling console.log('Connections:', message.total) console.log('Versions:', message.versions) } }) }) ``` h3. [Server-Side: Connection Lifecycle](#server-side-connection-lifecycle) Build custom tracking logic with Nitro hooks: server/plugins/custom-tracking.ts ``` import { defineNitroPlugin } from 'nitropack/runtime' export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('skew:connection:open', ({ id, version, send }) => { console.log(\`Client ${id} connected on ${version}\`) // Send custom welcome message send({ type: 'welcome', serverTime: Date.now() }) }) nitroApp.hooks.hook('skew:connection:close', ({ id }) => { console.log(\`Client ${id} disconnected\`) }) }) ``` See [**Nuxt Hooks**](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/skew-protection/api/nuxt-hooks) and [**Nitro Hooks**](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/skew-protection/nitro-api/nitro-hooks) for full documentation. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/2.guides/6.live-connections.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/skew-protection/guides/live-connections.md) **Did this page help you? ** [**Cookie Consent** Information about cookie consent requirements for using Nuxt Skew Protection.](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/skew-protection/guides/cookie-consent) [**Nuxt Hooks** Learn how to use Nuxt hooks to respond to skew protection events.](https://nuxtseo.com/docs/skew-protection/guides/live-connections/docs/skew-protection/api/nuxt-hooks) **On this page** - [Setup](#setup) - [Using the Composable](#using-the-composable) - [Example: Admin Dashboard](#example-admin-dashboard) - [Custom Behavior with Hooks](#custom-behavior-with-hooks) --- ### External Providers · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/providers/external Description: Use third-party WebSocket providers for real-time update notifications on any platform. **Providers** h1. **External Providers** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs External providers (adapters) allow you to use third-party WebSocket services for real-time update notifications via the [`updateStrategy`](https://nuxtseo.com/docs/skew-protection/providers/external/docs/skew-protection/api/config#updatestrategy-polling--sse--ws) config. This enables instant updates on **any platform**, including static/prerendered sites. h2. [Why Use External Providers?](#why-use-external-providers) - **Static Sites**: Get real-time updates without a server - **Scalability**: Offload connection management to dedicated infrastructure - **Reliability**: Leverage battle-tested WebSocket services - **Simplicity**: No server-side WebSocket setup required h2. [Available Adapters](#available-adapters) | **Adapter** | **Service** | **Free Tier** | | --- | --- | --- | | [**Pusher**](#pusher) | [**pusher.com**](https://pusher.com) | 200k messages/day | | [**Ably**](#ably) | [**ably.com**](https://ably.com) | 6M messages/month | h2. [Pusher](#pusher) [**Pusher Channels**](https://pusher.com/channels) is a hosted WebSocket service. ``` npx nypm add pusher-js ``` h3. [Configuration](#configuration) nuxt.config.ts ``` import { pusherAdapter } from 'nuxt-skew-protection/adapters/pusher' export default defineNuxtConfig({ skewProtection: { updateStrategy: pusherAdapter({ // Required key: process.env.PUSHER_KEY, cluster: process.env.PUSHER_CLUSTER, appId: process.env.PUSHER_APP_ID, secret: process.env.PUSHER_SECRET, // Optional channel: 'skew-protection', // default event: 'VersionUpdated', // default }) } }) ``` h2. [Ably](#ably) [**Ably**](https://ably.com) is a realtime messaging platform. ``` npx nypm add ably ``` h3. [Configuration](#configuration-1) nuxt.config.ts ``` import { ablyAdapter } from 'nuxt-skew-protection/adapters/ably' export default defineNuxtConfig({ skewProtection: { updateStrategy: ablyAdapter({ // Required key: process.env.ABLY_KEY, // Optional channel: 'skew-protection', // default event: 'VersionUpdated', // default }) } }) ``` h2. [Custom Adapters](#custom-adapters) Create your own adapter by implementing the `SkewAdapter` interface: my-adapter.ts ``` import type { SkewAdapter } from 'nuxt-skew-protection/adapters' import { z } from 'zod' const schema = z.object({ apiKey: z.string(), }) export function myAdapter(config: z.infer<typeof schema>): SkewAdapter { return { name: 'my-adapter', config, schema, subscribe: (onMessage) => { // Connect to your WebSocket service // Call onMessage({ version }) when update received return () => { /* cleanup */ } }, broadcast: async (version) => { // Broadcast version to all connected clients }, } } ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/3.providers/0.external.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/providers/external/docs/skew-protection/providers/external.md) **Did this page help you? ** [**Nitro Hooks** Learn how to use Nitro hooks to track and respond to real-time connections.](https://nuxtseo.com/docs/skew-protection/providers/external/docs/skew-protection/nitro-api/nitro-hooks) [**Cloudflare** Configure Nuxt Skew Protection for Cloudflare Workers, Pages, and NuxtHub.](https://nuxtseo.com/docs/skew-protection/providers/external/docs/skew-protection/providers/cloudflare) **On this page** - [Why Use External Providers?](#why-use-external-providers) - [Available Adapters](#available-adapters) - [Pusher](#pusher) - [Ably](#ably) - [Custom Adapters](#custom-adapters) --- ### Cloudflare · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/providers/cloudflare Description: Configure Nuxt Skew Protection for Cloudflare Workers, Pages, and NuxtHub. **Providers** h1. **Cloudflare** Last updated **Dec 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: internal links](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/a30b08c9ddf1df718cf2e82b6a9f15b71ed48f14). [Copy for LLMs Cloudflare has several deployment options for Nuxt applications: | **Deployment Option** | [**Update Strategy**](https://nuxtseo.com/docs/skew-protection/providers/cloudflare/docs/skew-protection/guides/update-strategies) | [**Storage**](https://nuxtseo.com/docs/skew-protection/providers/cloudflare/docs/skew-protection/api/config#storage-storageoptions) | | --- | --- | --- | | [**Cloudflare Durable Objects**](#cloudflare-durable-objects) (recommended) | WebSocket (real-time) | Cloudflare KV | | [**Cloudflare Workers / Pages**](#cloudflare-workers--pages) | Polling | Filesystem or Cloudflare KV | | [**NuxtHub**](#nuxthub) | Polling (WebSocket with Durable) | Filesystem | **Cloudflare Durable Objects** is recommended for the best user experience, as it enables instant update notifications via WebSockets. h2. [Long-Lived Asset Storage](#long-lived-asset-storage) When using Cloudflare directly your local wrangler instance is used to write files to your default Cloudflare KV namespace during build time. As a fallback and when using NuxtHub, the module can also use filesystem storage with CI/CD caching. For filesystem storage, see [**Storage Configuration**](https://nuxtseo.com/docs/skew-protection/providers/cloudflare/docs/skew-protection/guides/storage-configuration#filesystem-storage) for setting up caching in your CI/CD pipeline. h3. [KV Binding](#kv-binding) To use a dedicated KV namespace for Nuxt Skew Protection, create a KV namespace in your Cloudflare dashboard and bind it to your Worker or Pages project as `SKEW_PROTECTION`. Then configure the module to use the binding: nuxt.config.ts ``` import { defineNuxtConfig } from 'nuxt/config' export default defineNuxtConfig({ // ... nitro: { // ... cloudflare: { wrangler: { kvNamespaces: [ { binding: 'SKEW_PROTECTION', id: 'your-kv-namespace-id' } // Get ID from Cloudflare dashboard ] } } }, }) ``` h2. [Cloudflare Durable Objects](#cloudflare-durable-objects) Cloudflare Durable Objects support **WebSockets** for real-time update notifications, giving instant updates to users. nuxt.config.ts ``` import { defineNuxtConfig } from 'nuxt/config' export default defineNuxtConfig({ modules: ['nuxt-skew-protection'], nitro: { preset: 'cloudflare-durable', experimental: { websocket: true, // Required for WebSocket strategy }, }, }) ``` WebSocket strategy requires `nitro.experimental.websocket: true` and the `cloudflare-durable` preset. h2. [Cloudflare Workers / Pages](#cloudflare-workers-pages) Cloudflare Workers / Pages use **polling** for update detection since they don't support persistent connections. nuxt.config.ts ``` import { defineNuxtConfig } from 'nuxt/config' export default defineNuxtConfig({ modules: ['nuxt-skew-protection'], nitro: { preset: 'cloudflare', }, skewProtection: { updateStrategy: 'polling', // Auto-detected }, experimental: { checkOutdatedBuildInterval: 5 * 60 * 1000, // 5 minutes }, }) ``` h2. [NuxtHub](#nuxthub) [**NuxtHub**](https://hub.nuxt.com) simplifies Cloudflare deployment by automatically managing KV namespaces, databases, and other resources. By default, the module uses **filesystem storage** which works with NuxtHub deployments. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxthub/core', 'nuxt-skew-protection', ], hub: { kv: true, // Enable KV for your app }, skewProtection: { // Uses filesystem storage by default - no configuration needed! }, }) ``` h3. [Real-Time Updates with NuxtHub](#real-time-updates-with-nuxthub) To enable real-time updates with NuxtHub, enable `websocket` in your hub configuration: nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ '@nuxthub/core', 'nuxt-skew-protection', ], nitro: { experimental: { websocket: true, }, }, hub: { kv: true, websocket: true, // Enable WebSocket support }, }) ``` For storage, see [**Storage Configuration**](https://nuxtseo.com/docs/skew-protection/providers/cloudflare/docs/skew-protection/guides/storage-configuration#filesystem-storage) for setting up caching in your CI/CD pipeline. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/3.providers/6.cloudflare.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/providers/cloudflare/docs/skew-protection/providers/cloudflare.md) **Did this page help you? ** [**External Providers** Use third-party WebSocket providers for real-time update notifications on any platform.](https://nuxtseo.com/docs/skew-protection/providers/cloudflare/docs/skew-protection/providers/external) **On this page** - [Long-Lived Asset Storage](#long-lived-asset-storage) - [Cloudflare Durable Objects](#cloudflare-durable-objects) - [Cloudflare Workers / Pages](#cloudflare-workers-pages) - [NuxtHub](#nuxthub) --- ### Configuration · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/api/config Description: Nuxt configuration reference for Nuxt AI Ready. **Nuxt API** h1. **Configuration** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: rework module](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/2535042559350f45518092731fbfdb3138864574). [Copy for LLMs nuxt.config.ts ``` export default defineNuxtConfig({ aiReady: { // options } }) ``` h2. [`enabled`](#enabled) - **Type:** `boolean` - **Default:** `true` ``` aiReady: { enabled: process.env.NODE_ENV === 'production' } ``` h2. [`debug`](#debug) - **Type:** `boolean` - **Default:** `false` Enable debug logging for module operations. h2. [`mdreamOptions`](#mdreamoptions) - **Type:** `HTMLToMarkdownOptions& { preset?: 'minimal' }` - **Default:** `{ preset: 'minimal' }` Configure [**mdream**](https://github.com/harlan-zw/mdream) HTML-to-markdown conversion. ``` export default defineNuxtConfig({ aiReady: { mdreamOptions: { preset: 'minimal' } } }) ``` h2. [`markdownCacheHeaders`](#markdowncacheheaders) - **Type:** `{ maxAge?: number, swr?: boolean }` - **Default:** `{ maxAge: 3600, swr: true }` Cache settings for runtime markdown endpoints. | **Option** | **Type** | **Default** | **Description** | | --- | --- | --- | --- | | `maxAge` | `number` | `3600` | Cache duration in seconds | | `swr` | `boolean` | `true` | Stale-while-revalidate | h2. [`llmsTxt`](#llmstxt) - **Type:** `LlmsTxtConfig` - **Default:** Auto-generated Configure llms.txt generation. ``` export default defineNuxtConfig({ aiReady: { llmsTxt: { sections: [ { title: 'API Reference', links: [ { title: 'REST API', href: '/docs/api', description: 'API docs' } ] } ], notes: 'Built with Nuxt AI Ready' } } }) ``` **LlmsTxtSection:** | **Property** | **Type** | **Description** | | --- | --- | --- | | `title` | `string` | Section title | | `description` | `string \| string[]` | Section description | | `links` | `{ title, href, description? }[]` | Links in section | **LlmsTxtConfig:** | **Property** | **Type** | **Description** | | --- | --- | --- | | `sections` | `LlmsTxtSection[]` | Custom sections | | `notes` | `string \| string[]` | Notes at end | h2. [`contentSignal`](#contentsignal) - **Type:** `false | { aiTrain?: boolean, search?: boolean, aiInput?: boolean }` - **Default:** `false` Content Signal directives for robots.txt. See [**Content Signals guide**](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/guides/content-signals). ``` export default defineNuxtConfig({ aiReady: { contentSignal: { aiTrain: false, // Block training search: true, // Allow search indexing aiInput: true, // Allow RAG/grounding } } }) ``` h2. [`mcp`](#mcp) - **Type:** `{ tools?: boolean, resources?: boolean }` - **Default:** `{ tools: true, resources: true }` Control MCP features when `@nuxtjs/mcp-toolkit` installed. ``` export default defineNuxtConfig({ aiReady: { mcp: { tools: true, // list_pages, search_pages_fuzzy resources: true, // pages resource } } }) ``` See [**MCP guide**](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/guides/mcp) for tool/resource details. h2. [`cacheMaxAgeSeconds`](#cachemaxageseconds) - **Type:** `number` - **Default:** `600` (10 minutes) Cache duration for llms.txt route handlers. Uses stale-while-revalidate. ``` export default defineNuxtConfig({ aiReady: { cacheMaxAgeSeconds: 3600 // 1 hour } }) ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/3.api/5.config.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/api/config.md) **Did this page help you? ** h3. **Related ** [**llms.txt Generation**](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/guides/llms-txt) [**Model Context Protocol (MCP)**](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/guides/mcp) [**Content Signals**](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/guides/content-signals) [**Nuxt Hooks** Nuxt hooks provided by nuxt-ai-ready for extending functionality.](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/api/nuxt-hooks) [**Nitro Hooks** Nitro runtime hooks for modifying markdown output.](https://nuxtseo.com/docs/ai-ready/api/config/docs/ai-ready/nitro-api/nitro-hooks) **On this page** - [enabled](#enabled) - [debug](#debug) - [mdreamOptions](#mdreamoptions) - [markdownCacheHeaders](#markdowncacheheaders) - [llmsTxt](#llmstxt) - [contentSignal](#contentsignal) - [mcp](#mcp) - [cacheMaxAgeSeconds](#cachemaxageseconds) --- ### Install Nuxt AI Ready · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/getting-started/installation Description: Get started with Nuxt AI Ready by installing the dependency to your project. **Getting Started** h1. **Install Nuxt AI Ready** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: sync](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/71282c76b050b356ed447c7e42176e5780eac034). [Copy for LLMs h2. [Setup Module](#setup-module) `npx nuxt module add ai-ready` `npm i nuxt-ai-ready` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-ai-ready', ], }) ``` `yarn add nuxt-ai-ready` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-ai-ready', ], }) ``` `pnpm i nuxt-ai-ready` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-ai-ready', ], }) ``` `bun i nuxt-ai-ready` You will need to manually add the module to your Nuxt config. nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-ai-ready', ], }) ``` Nuxt AI Ready requires a [**Nuxt SEO Pro**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/pro) license. ``` NUXT_SEO_PRO_KEY=your-license-key-here ``` [Sign in](https://nuxtseo.com/docs/ai-ready/getting-started/installation/pro) to see your license key. h2. [Verifying Installation](#verifying-installation) Zero-config module. After installation: - Visit any route with `.md` suffix (e.g., `/about.md`) to see markdown output - Run `nuxi build` then check `/llms.txt` and `/llms-full.txt` - `/robots.txt` shows [**Content Signals**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/content-signals) h2. [Configuration](#configuration) h3. [MCP Integration (Optional)](#mcp-integration-optional) Connect AI agents to your site via [**Model Context Protocol**](https://modelcontextprotocol.io/): ``` npx nuxi module add @nuxtjs/mcp-toolkit ``` nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-ai-ready', '@nuxtjs/mcp-toolkit', ], }) ``` Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS): ``` { "mcpServers": { "my-site": { "command": "npx", "args": ["-y", "@nuxtjs/mcp-client", "http://localhost:3000/mcp"] } } } ``` See the [**MCP guide**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/mcp) for tools, resources, and production setup. h3. [Rendering Mode Support](#rendering-mode-support) Works best with prerendering. Feature availability by mode: | **Feature** | **SSG** | **Hybrid** | **SSR-only** | **SPA** | | --- | --- | --- | --- | --- | | llms.txt with titles | ✅ | ✅ | ✅ | ❌ | | llms-full.txt | ✅ | ✅ Prerendered only | ❌ Placeholder | ❌ | | Runtime `.md` routes | ✅ | ✅ | ✅ | ❌ | | MCP tools/resources | ✅ | ✅ Partial | ❌ Empty | ❌ | **SSG**: Full support. All pages processed during `nuxi generate`. **Hybrid**: Page data only for prerendered pages. **SSR-only**: No page data generated. Runtime `.md` routes work. **SPA**: Not supported. Workaround: enable `nitro.prerender.routes` for specific pages. h2. [Next Steps](#next-steps) - [**Content Signals**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/content-signals) — Configure AI training/search permissions - [**llms.txt Guide**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/llms-txt) — Customize llms.txt output - [**MCP Guide**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/mcp) — Tools and resources for AI agents - [**Markdown Guide**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/markdown) — Configure HTML→markdown conversion [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/1.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/getting-started/installation.md) **Did this page help you? ** h3. **Related ** [**Content Signals**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/content-signals) [**llms.txt Generation**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/llms-txt) [**Model Context Protocol (MCP)**](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/mcp) [**Introduction** Make your Nuxt site discoverable by AI agents through llms.txt, MCP, and markdown APIs.](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/getting-started/introduction) [**AI Content Signals** Control how AI systems interact with your content through robots.txt directives.](https://nuxtseo.com/docs/ai-ready/getting-started/installation/docs/ai-ready/guides/content-signals) **On this page** - [Setup Module](#setup-module) - [Verifying Installation](#verifying-installation) - [Configuration](#configuration) - [Next Steps](#next-steps) --- ### AI Content Signals · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/guides/content-signals Description: Control how AI systems interact with your content through robots.txt directives. **Core Concepts** h1. **AI Content Signals** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: rework module](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/2535042559350f45518092731fbfdb3138864574). [Copy for LLMs Content Signals in `robots.txt` tell AI agents your permissions for training, output, and indexing. Relies on voluntary compliance. Major AI platforms increasingly respect them, but enforcement varies. h2. [Standards](#standards) **[**Content-Usage**](https://ietf-wg-aipref.github.io/drafts/draft-ietf-aipref-attach.html#name-robots-exclusion-protocol-c)** (values: `y`/`n`) - `train-ai` — foundation model training **[**Content-Signal**](https://contentsignals.org/)** (values: `yes`/`no`) - `search` — indexing/snippets - `ai-input` — RAG, grounding, AI search - `ai-train` — model training/fine-tuning See [**Nuxt Robots AI Directives**](https://nuxtseo.com/docs/ai-ready/guides/content-signals/docs/robots/guides/ai-directives) for full documentation. h2. [Enable](#enable) Disabled by default. Enable to allow AI training and indexing: nuxt.config.ts ``` export default defineNuxtConfig({ aiReady: { contentSignal: { aiTrain: true, search: true, aiInput: true } } }) ``` Produces: robots.txt ``` h1. Nuxt AI Ready Content Signals Content-Usage: train-ai=y Content-Signal: ai-train=yes, search=yes, ai-input=yes ``` Requires [`@nuxtjs/robots`](https://nuxtseo.com/robots) ≥5.6.0. h2. [Selective Permissions](#selective-permissions) Allow search indexing but block training: nuxt.config.ts ``` export default defineNuxtConfig({ aiReady: { contentSignal: { search: true, aiInput: false, aiTrain: false } } }) ``` Set `contentSignal: false` to disable entirely (default). [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/2.guides/0.content-signals.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/guides/content-signals/docs/ai-ready/guides/content-signals.md) **Did this page help you? ** h3. **Related ** [**llms.txt Generation**](https://nuxtseo.com/docs/ai-ready/guides/content-signals/docs/ai-ready/guides/llms-txt) [**Configuration**](https://nuxtseo.com/docs/ai-ready/guides/content-signals/docs/ai-ready/api/config) [**Robots.txt Guide**](https://nuxtseo.com/docs/ai-ready/guides/content-signals/learn-seo/nuxt/controlling-crawlers/robots-txt) [**Installation** Get started with Nuxt AI Ready by installing the dependency to your project.](https://nuxtseo.com/docs/ai-ready/guides/content-signals/docs/ai-ready/getting-started/installation) [**llms.txt Generation** Configure llms.txt and llms-full.txt output for AI discovery.](https://nuxtseo.com/docs/ai-ready/guides/content-signals/docs/ai-ready/guides/llms-txt) **On this page** - [Standards](#standards) - [Enable](#enable) - [Selective Permissions](#selective-permissions) --- ### llms.txt Generation · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/guides/llms-txt Description: Configure llms.txt and llms-full.txt output for AI discovery. **Core Concepts** h1. **llms.txt Generation** Last updated **Dec 20, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: clarify usage for llms.txt](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/d54ecfab8db39378d621291c7db83d1dcb14d141). [Copy for LLMs Nuxt AI Ready generates `/llms.txt` and `/llms-full.txt` during prerender following the [**llms.txt standard**](https://llmstxt.org/). h2. [`/llms.txt`](#llmstxt) Site overview with page links. Built from page metadata collected during prerender. Live example: [**nuxtseo.com/llms.txt**](https://nuxtseo.com/llms.txt) ``` h1. <Site Title> > <Site Description> h2. Pages - [Page Title](/page-link): Meta Description ... h2. <Section Title> - [Link Title](/link): Description ... <Notes> ``` h3. [Configuration](#configuration) Add custom sections: nuxt.config.ts ``` export default defineNuxtConfig({ aiReady: { llmsTxt: { sections: [ { title: 'API Reference', links: [ { title: 'REST API', href: '/docs/api', description: 'API documentation' } ] } ], notes: 'Built with Nuxt AI Ready' } } }) ``` Full config: [**Configuration**](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/api/config) h3. [Hook](#hook) Modify sections before generation: nuxt.config.ts ``` export default defineNuxtConfig({ hooks: { 'ai-ready:llms-txt': (payload) => { payload.sections.push({ title: 'Custom APIs', links: [{ title: 'Search', href: '/api/search', description: 'Search endpoint' }] }) payload.notes.push('Custom note') } } }) ``` Hook details: [**Nuxt Hooks**](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/api/nuxt-hooks#ai-ready-llms-txt) h2. [`/llms-full.txt`](#llms-fulltxt) Full markdown content for all pages. Streamed during prerender — each page appended as processed, no memory accumulation. Auto-generated from prerendered pages. Hook `ai-ready:page:markdown` fires per page if you need to modify content: nuxt.config.ts ``` export default defineNuxtConfig({ hooks: { 'ai-ready:page:markdown': (ctx) => { // ctx: { route, markdown, title, description } ctx.markdown = \`# ${ctx.title}\n\n${ctx.markdown}\` } } }) ``` h2. [How Pages Are Discovered](#how-pages-are-discovered) Page discovery uses a two-phase approach combining prerendering and sitemap crawling. h3. [Phase 1: Prerender Crawl](#phase-1-prerender-crawl) During `nuxi generate`, the module intercepts each prerendered page: 1. Nuxt plugin queues `.md` route for each rendered page 2. Middleware converts HTML → markdown with metadata extraction 3. `ai-ready:page:markdown` hook fires for each page 4. Data appended to `page-data.jsonl` + streamed to `llms-full.txt` Prerendered pages have full metadata (title, description, headings). h3. [Phase 2: Sitemap Crawl](#phase-2-sitemap-crawl) After prerendering completes, the module crawls `/sitemap.xml` for any pages not already processed: - Uses `sitemap:prerender:done` hook when `@nuxtjs/sitemap` is installed - Falls back to `prerender:done` hook otherwise - Fetches `.md` for each sitemap URL not in prerender set - Adds to `page-data.jsonl` but **skips** `llms-full.txt` (prevents duplicates) This catches SSR-only pages that weren't prerendered. h3. [Runtime Fallback](#runtime-fallback) In SSR-only mode (no prerendering), llms.txt dynamically fetches `/sitemap.xml` and lists URLs without titles. ``` Phase 1 (Prerender) Phase 2 (Sitemap) Runtime ───────────────────── ───────────────────── ───────────────────── app:rendered sitemap:prerender:done GET /llms.txt ↓ ↓ ↓ Queue .md routes Parse sitemap.xml fetchSitemapUrls() ↓ ↓ ↓ HTML → Markdown Fetch .md for SSR Combine prerendered ↓ pages + sitemap URLs Write JSONL + ↓ ↓ llms-full.txt Add to JSONL only Generate llms.txt ``` h2. [Customizing Page Processing](#customizing-page-processing) h3. [Filter or Modify Pages](#filter-or-modify-pages) Use the `ai-ready:page:markdown` hook to modify or filter pages during prerender: nuxt.config.ts ``` export default defineNuxtConfig({ hooks: { 'ai-ready:page:markdown': (ctx) => { // Skip draft pages if (ctx.route.startsWith('/drafts/')) { ctx.markdown = '' // Empty markdown = excluded from llms-full.txt return } // Add frontmatter ctx.markdown = \`--- route: ${ctx.route} title: ${ctx.title} --- ${ctx.markdown}\` } } }) ``` Context properties: - `route`: Page path (e.g., `/about`) - `markdown`: Converted content (mutable) - `title`: Extracted `<title>` - `description`: Extracted meta description - `headings`: Array of `{ level, text }` objects h3. [Modify Markdown Conversion](#modify-markdown-conversion) Use the `ai-ready:mdreamConfig` Nitro hook to customize HTML → markdown: server/plugins/mdream.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('ai-ready:mdreamConfig', (config) => { // Skip navigation elements config.ignoreSelectors = ['nav', '.sidebar', '.footer'] // Preserve code blocks config.preserveCodeBlocks = true }) }) ``` h3. [Post-Process Markdown at Runtime](#post-process-markdown-at-runtime) Use the `ai-ready:markdown` Nitro hook for runtime `.md` requests: server/plugins/markdown.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('ai-ready:markdown', (ctx) => { // Add source link ctx.markdown += \`\n\n---\nSource: ${ctx.route}\` }) }) ``` h2. [Sitemap Requirements](#sitemap-requirements) The module requires `@nuxtjs/sitemap` for page discovery: nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['@nuxtjs/sitemap', 'nuxt-ai-ready'] }) ``` If sitemap is missing or empty, llms.txt will have an empty pages section with a warning logged. h3. [Excluding Pages](#excluding-pages) Pages excluded from sitemap are automatically excluded from llms.txt. Use sitemap's `exclude` option: nuxt.config.ts ``` export default defineNuxtConfig({ sitemap: { exclude: ['/admin/**', '/api/**', '/drafts/**'] } }) ``` h2. [Dev Mode](#dev-mode) In development, llms.txt returns a notice about missing data. Page data is only available after `nuxi generate` or `nuxi build --prerender`. Runtime `.md` routes still work in dev for testing markdown conversion. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/2.guides/0.llms-txt.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/guides/llms-txt.md) **Did this page help you? ** h3. **Related ** [**Content Signals**](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/guides/content-signals) [**Nuxt Hooks**](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/api/nuxt-hooks) [**Configuration**](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/api/config) [**AI Content Signals** Control how AI systems interact with your content through robots.txt directives.](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/guides/content-signals) [**Markdown Conversion** How HTML pages are converted to markdown for AI-friendly content delivery.](https://nuxtseo.com/docs/ai-ready/guides/llms-txt/docs/ai-ready/guides/markdown) **On this page** - [/llms.txt](#llmstxt) - [/llms-full.txt](#llms-fulltxt) - [How Pages Are Discovered](#how-pages-are-discovered) - [Customizing Page Processing](#customizing-page-processing) - [Sitemap Requirements](#sitemap-requirements) - [Dev Mode](#dev-mode) --- ### RAG Setup · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/advanced/rag-example Description: Vectorize your site's markdown for semantic search and RAG pipelines. **Advanced** h1. **RAG Setup** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: rework module](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/2535042559350f45518092731fbfdb3138864574). [Copy for LLMs Nuxt AI Ready outputs clean markdown optimized for vectorizing. This guide shows how to build a RAG pipeline using `llms-full.txt`. h2. [Fetch Markdown Content](#fetch-markdown-content) `llms-full.txt` contains all pages as markdown, separated by `---` dividers with frontmatter: ``` const response = await fetch('https://yoursite.com/llms-full.txt') const content = await response.text() // Split into pages const pages = content.split(/^---$/m).filter(Boolean).map((block) => { const [, frontmatter, ...rest] = block.split(/^---$/m) const markdown = rest.join('---').trim() // Parse frontmatter const meta: Record<string, string> = {} frontmatter?.split('\n').forEach((line) => { const [key, ...val] = line.split(':') if (key?.trim()) meta[key.trim()] = val.join(':').trim() }) return { ...meta, markdown } }) ``` h2. [Generate Embeddings](#generate-embeddings) Use any embedding provider. Example with OpenAI: ``` import OpenAI from 'openai' const openai = new OpenAI() async function embed(text: string) { const response = await openai.embeddings.create({ model: 'text-embedding-3-small', input: text, }) return response.data[0].embedding } // Embed each page const vectors = await Promise.all( pages.map(async page => ({ id: page.route, embedding: await embed(page.markdown), metadata: { title: page.title, route: page.route } })) ) ``` h2. [Store in Vector DB](#store-in-vector-db) h3. [sqlite-vec (Local)](#sqlite-vec-local) ``` import Database from 'better-sqlite3' import * as sqliteVec from 'sqlite-vec' const db = new Database(':memory:') sqliteVec.load(db) db.exec(\` CREATE VIRTUAL TABLE pages USING vec0( id TEXT PRIMARY KEY, embedding FLOAT[1536] ) \`) const insert = db.prepare('INSERT INTO pages VALUES (?, ?)') for (const v of vectors) { insert.run(v.id, new Float32Array(v.embedding)) } ``` h3. [Upstash Vector (Serverless)](#upstash-vector-serverless) ``` import { Index } from '@upstash/vector' const index = new Index() await index.upsert(vectors.map(v => ({ id: v.id, vector: v.embedding, metadata: v.metadata }))) ``` h2. [Query](#query) ``` async function search(query: string, topK = 5) { const queryEmbedding = await embed(query) // sqlite-vec const results = db.prepare(\` SELECT id, distance FROM pages WHERE embedding MATCH ? ORDER BY distance LIMIT ? \`).all(new Float32Array(queryEmbedding), topK) return results } // Use in RAG prompt const relevant = await search('how do I configure meta tags?') const context = relevant.map(r => pages.find(p => p.route === r.id)?.markdown).join('\n\n') ``` h2. [Chunking Strategy](#chunking-strategy) By default, each page is one chunk. For large pages, split by heading: ``` function chunkByHeading(markdown: string, route: string) { const sections = markdown.split(/^##\s+/m) return sections.map((section, i) => ({ id: \`${route}#${i}\`, content: section.trim(), route })) } ``` | **Strategy** | **When to use** | | --- | --- | | Page-level | Small pages (<2k tokens), general search | | Heading-level | Long docs, precise retrieval needed | | Sliding window | Dense technical content, overlap matters | h2. [Build Script](#build-script) Run vectorization at build time: ``` // scripts/vectorize.ts import { readFileSync } from 'node:fs' const llmsFull = readFileSync('.output/public/llms-full.txt', 'utf-8') // ... parse and vectorize as above ``` Add to your build: ``` { "scripts": { "generate": "nuxt generate && tsx scripts/vectorize.ts" } } ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/3.advanced/0.rag-example.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/advanced/rag-example/docs/ai-ready/advanced/rag-example.md) **Did this page help you? ** h3. **Related ** [**llms.txt Configuration**](https://nuxtseo.com/docs/ai-ready/advanced/rag-example/guides/llms-txt) [**Markdown Output**](https://nuxtseo.com/docs/ai-ready/advanced/rag-example/guides/markdown) [**Model Context Protocol (MCP)** Connect AI agents like Claude to your Nuxt site via MCP servers with built-in tools and resources.](https://nuxtseo.com/docs/ai-ready/advanced/rag-example/docs/ai-ready/guides/mcp) [**Nuxt Hooks** Nuxt hooks provided by nuxt-ai-ready for extending functionality.](https://nuxtseo.com/docs/ai-ready/advanced/rag-example/docs/ai-ready/api/nuxt-hooks) **On this page** - [Fetch Markdown Content](#fetch-markdown-content) - [Generate Embeddings](#generate-embeddings) - [Store in Vector DB](#store-in-vector-db) - [Query](#query) - [Chunking Strategy](#chunking-strategy) - [Build Script](#build-script) --- ### Model Context Protocol (MCP) · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/guides/mcp Description: Connect AI agents like Claude to your Nuxt site via MCP servers with built-in tools and resources. **Core Concepts** h1. **Model Context Protocol (MCP)** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: rework module](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/2535042559350f45518092731fbfdb3138864574). [Copy for LLMs [**Model Context Protocol (MCP)**](https://modelcontextprotocol.io/) support via [`@nuxtjs/mcp-toolkit`](https://github.com/nuxt-modules/mcp-toolkit). Your site exposes tools and resources that AI agents like Claude can query for page data and search. h2. [Installation](#installation) ``` npx nuxi module add @nuxtjs/mcp-toolkit ``` nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ 'nuxt-ai-ready', '@nuxtjs/mcp-toolkit', ], }) ``` See [**@nuxtjs/mcp-toolkit docs**](https://github.com/nuxt-modules/mcp) for server configuration. h2. [Connection](#connection) Add to Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS): ``` { "mcpServers": { "my-site": { "command": "npx", "args": ["-y", "@nuxtjs/mcp-client", "https://example.com/mcp"] } } } ``` h2. [Tools](#tools) h3. [`list_pages`](#list_pages) Returns page metadata as JSON. Cached 1 hour. **Parameters:** None **Response:** ``` [ { "route": "/docs/getting-started", "title": "Getting Started", "description": "Quick start guide", "headings": "h1:Getting Started|h2:Installation", "updatedAt": "2025-01-15T10:30:00Z" } ] ``` h3. [`search_pages_fuzzy`](#search_pages_fuzzy) Fuzzy search across pages via [**Fuse.js**](https://www.fusejs.io/). Searches title, description, and route. Cached 5 minutes. **Parameters:** | **Param** | **Type** | **Description** | | --- | --- | --- | | `query` | `string` | Search query | | `limit` | `number` | Max results (default: 10) | **Response:** ``` [ { "route": "/docs/installation", "title": "Installation", "description": "Install the module", "score": 0.15 } ] ``` h2. [Resources](#resources) h3. [`resource://nuxt-ai-ready/pages`](#resourcenuxt-ai-readypages) Page listing as JSON. Same data as `list_pages` tool. Cached 1 hour. Use resources when agents need static data without parameters. h2. [Data Availability](#data-availability) MCP tools return data from prerendered pages stored in `page-data.jsonl`. | **Environment** | **Data Source** | | --- | --- | | **Dev mode** | Empty (no prerender data) | | **Production** | Virtual module reads JSONL | For full MCP functionality, test with a production build (`nuxi generate`). h2. [Configuration](#configuration) Disable specific features: nuxt.config.ts ``` export default defineNuxtConfig({ aiReady: { mcp: { tools: false, // Disable all tools resources: false, // Disable all resources }, } }) ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/2.guides/3.mcp.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/guides/mcp/docs/ai-ready/guides/mcp.md) **Did this page help you? ** h3. **Related ** [**Installation**](https://nuxtseo.com/docs/ai-ready/guides/mcp/docs/ai-ready/getting-started/installation) [**Configuration**](https://nuxtseo.com/docs/ai-ready/guides/mcp/docs/ai-ready/api/config) [**llms.txt Guide**](https://nuxtseo.com/docs/ai-ready/guides/mcp/learn-seo/nuxt/controlling-crawlers/llms-txt) [**Markdown Conversion** How HTML pages are converted to markdown for AI-friendly content delivery.](https://nuxtseo.com/docs/ai-ready/guides/mcp/docs/ai-ready/guides/markdown) [**RAG Setup** Vectorize your site's markdown for semantic search and RAG pipelines.](https://nuxtseo.com/docs/ai-ready/guides/mcp/docs/ai-ready/advanced/rag-example) **On this page** - [Installation](#installation) - [Connection](#connection) - [Tools](#tools) - [Resources](#resources) - [Data Availability](#data-availability) - [Configuration](#configuration) --- ### Protecting Vue Apps from Malicious Crawlers · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/routes-and-rendering/security Description: Robots.txt is a polite suggestion. Malicious crawlers ignore it. Here's how to actually protect your Vue app. h1. **Protecting Vue Apps from Malicious Crawlers** Robots.txt is a polite suggestion. Malicious crawlers ignore it. Here's how to actually protect your Vue app. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Nov 3, 2024** Updated **Dec 5, 2024** [**Robots.txt**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/security/learn-seo/vue/controlling-crawlers/robots-txt) and meta robots tags are polite suggestions. Malicious crawlers ignore them. You need actual security: block non-production environments, protect development assets, rate limit aggressive crawlers, authenticate sensitive routes, use HTTPS everywhere. Don't rely on robots.txt for sensitive data, IP blocking alone (easily bypassed), or user-agent detection (trivial to fake). h2. [Quick Setup](#quick-setup) Protect your Vue app from unwanted crawlers at the server level: Express Middleware ``` // server/middleware/security.js import express from 'express' const app = express() // Block non-production environments app.use((req, res, next) => { if (process.env.NODE_ENV !== 'production') { res.setHeader('X-Robots-Tag', 'noindex, nofollow') } next() }) // Enforce HTTPS app.use((req, res, next) => { if (req.headers['x-forwarded-proto'] !== 'https') { return res.redirect(301, \`https://${req.headers.host}${req.url}\`) } next() }) ``` Security Headers ``` // Add security headers to your server import helmet from 'helmet' app.use(helmet({ frameguard: { action: 'deny' }, contentSecurityPolicy: { directives: { defaultSrc: ['\'self\''], styleSrc: ['\'self\'', '\'unsafe-inline\''], scriptSrc: ['\'self\''] } }, referrerPolicy: { policy: 'strict-origin-when-cross-origin' } })) ``` Rate Limiting ``` import rateLimit from 'express-rate-limit' const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }) app.use('/api', limiter) ``` h2. [Environment Protection](#environment-protection) h3. [Development & Staging](#development-staging) Always block search engines in non-production environments: ``` // middleware/block-non-production.js app.use((req, res, next) => { const isProd = process.env.NODE_ENV === 'production' const isMainDomain = req.headers.host === 'mysite.com' if (!isProd || !isMainDomain) { res.setHeader('X-Robots-Tag', 'noindex, nofollow') // Also consider basic auth for staging const auth = req.headers.authorization if (!auth) { res.setHeader('WWW-Authenticate', 'Basic') return res.status(401).send('Authentication required') } } next() }) ``` h3. [Sensitive Routes](#sensitive-routes) Protect admin and user areas: ``` // middleware/protect-routes.js app.use((req, res, next) => { const protectedPaths = ['/admin', '/dashboard', '/user'] if (protectedPaths.some(path => req.path.startsWith(path))) { // Ensure user is authenticated if (!req.session?.user) { return res.redirect('/login') } // Block indexing of protected content res.setHeader('X-Robots-Tag', 'noindex, nofollow') } next() }) ``` h2. [Crawler Identification](#crawler-identification) h3. [Good vs Bad Crawlers](#good-vs-bad-crawlers) Identify legitimate crawlers through: - Reverse DNS lookup - IP verification - Behavior patterns - Request rate ``` // utils/verify-crawler.js import dns from 'node:dns' import { promisify } from 'node:util' const reverse = promisify(dns.reverse) export async function isLegitCrawler(ip, userAgent) { // Example: Verify Googlebot if (userAgent.includes('Googlebot')) { const hostnames = await reverse(ip) return hostnames.some(h => h.endsWith('googlebot.com')) } return false } ``` h3. [Rate Limiting](#rate-limiting) Implement tiered rate limiting: ``` import rateLimit from 'express-rate-limit' // Different limits for different paths const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }) const crawlerLimiter = rateLimit({ windowMs: 60 * 1000, max: 10, skip: req => !req.headers['user-agent']?.includes('bot') }) app.use('/api', apiLimiter) app.use(crawlerLimiter) ``` h2. [Infrastructure Security](#infrastructure-security) h3. [HTTPS Enforcement](#https-enforcement) Always redirect HTTP to HTTPS: ``` app.use((req, res, next) => { const proto = req.headers['x-forwarded-proto'] if (proto === 'http') { return res.redirect(301, \`https://${req.headers.host}${req.url}\`) } next() }) ``` h3. [Security Headers](#security-headers) Add security headers using helmet: ``` import helmet from 'helmet' app.use(helmet({ // Prevent clickjacking frameguard: { action: 'deny' }, // Prevent MIME type sniffing noSniff: true, // Control referrer information referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, // Enable strict CSP in production contentSecurityPolicy: process.env.NODE_ENV === 'production' ? { directives: { defaultSrc: ['\'self\''] } } : false })) ``` h2. [Monitoring & Detection](#monitoring-detection) h3. [Logging Suspicious Activity](#logging-suspicious-activity) ``` // middleware/crawler-monitor.js app.use((req, res, next) => { const ua = req.headers['user-agent'] const ip = req.ip // Log suspicious patterns if (isSuspiciousPattern(ua, ip)) { console.warn(\`Suspicious crawler: ${ip} with UA: ${ua}\`) // Consider blocking or rate limiting } next() }) ``` h3. [Using Web Application Firewalls](#using-web-application-firewalls) Services like Cloudflare or AWS WAF can: - Block malicious IPs - Prevent DDoS attacks - Filter suspicious requests - Monitor traffic patterns **Opinion:** If you're running a small blog, a WAF is overkill. Add it when you're actually getting attacked. h2. [Common Attacks](#common-attacks) h3. [Content Scraping](#content-scraping) Prevent automated content theft: ``` const requestCounts = new Map() app.use((req, res, next) => { const ip = req.ip const count = requestCounts.get(ip) || 0 if (count > 100) { return res.status(429).send('Too Many Requests') } requestCounts.set(ip, count + 1) // Add slight delays to automated requests if (isBot(req.headers['user-agent'])) { setTimeout(next, 500) } else { next() } }) ``` h3. [Form Spam](#form-spam) Protect forms from bot submissions: ``` // routes/contact.js app.post('/api/contact', async (req, res) => { const { website, ...formData } = req.body // Honeypot check if (website) { // hidden field return res.json({ success: false }) } // Rate limiting if (exceedsRateLimit(req.ip)) { return res.status(429).json({ error: 'Too many attempts' }) } // Process legitimate submission // ... }) ``` h2. [Using Nuxt?](#using-nuxt) If you're using Nuxt, check out [**Nuxt SEO**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/security/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically. [**Learn more about Security in Nuxt →**](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/security/learn-seo/nuxt/routes-and-rendering/security) --- [**Rendering Modes** How SPA, SSR, and SSG affect Google indexing. Which mode to use and when.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/security/learn-seo/vue/routes-and-rendering/rendering) [**SSR Frameworks** Compare Vue server-side rendering frameworks. Choose between Nuxt, Quasar, Vite SSR, and VitePress for better SEO and performance.](https://nuxtseo.com/learn-seo/vue/routes-and-rendering/security/learn-seo/vue/ssr-frameworks) **On this page** - [Quick Setup](#quick-setup) - [Environment Protection](#environment-protection) - [Crawler Identification](#crawler-identification) - [Infrastructure Security](#infrastructure-security) - [Monitoring & Detection](#monitoring-detection) - [Common Attacks](#common-attacks) - [Using Nuxt?](#using-nuxt) --- ### Markdown Conversion · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/guides/markdown Description: How HTML pages are converted to markdown for AI-friendly content delivery. **Core Concepts** h1. **Markdown Conversion** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: rework module](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/2535042559350f45518092731fbfdb3138864574). [Copy for LLMs [**mdream**](https://github.com/harlan-zw/mdream) converts HTML pages to markdown during prerendering and runtime. h2. [Build-Time](#build-time) During `nuxi generate`: 1. Nuxt plugin queues `.md` route for each rendered page (`/about/` → `/about/index.md`) 2. Prerender middleware fetches HTML, converts to markdown, extracts metadata 3. `ai-ready:page:markdown` hook fires per page 4. Content appended to `page-data.jsonl` and streamed to `llms-full.txt` 5. Route serves raw markdown Metadata extracted: title, description, headings, updatedAt (from `article:modified_time` etc). h2. [Runtime](#runtime) Markdown served when: - Path ends in `.md` (explicit), OR - `Accept` includes `text/markdown` AND NOT `text/html` AND `sec-fetch-dest` ≠ `document` Targets API clients (Claude Code, curl, Bun) while excluding browsers. ``` curl https://example.com/about.md curl -H "Accept: text/markdown" https://example.com/about ``` h2. [Configuration](#configuration) nuxt.config.ts ``` export default defineNuxtConfig({ aiReady: { mdreamOptions: { preset: 'minimal', }, markdownCacheHeaders: { maxAge: 3600, swr: true, }, }, }) ``` h2. [Hooks](#hooks) h3. [`'ai-ready:mdreamConfig'`](#ai-readymdreamconfig) Modify mdream options before conversion: server/plugins/mdream-config.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('ai-ready:mdreamConfig', (options) => { if (options.origin?.includes('/blog/')) options.ignoreElements = [...(options.ignoreElements || []), '.author-bio'] }) }) ``` h3. [`'ai-ready:markdown'`](#ai-readymarkdown) Modify markdown output at runtime: server/plugins/markdown-footer.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('ai-ready:markdown', (ctx) => { ctx.markdown = \`---\ntitle: ${ctx.title}\n---\n\n${ctx.markdown}\` }) }) ``` See [**Nitro Hooks**](https://nuxtseo.com/docs/ai-ready/guides/markdown/docs/ai-ready/nitro-api/nitro-hooks) for context types. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/2.guides/1.markdown.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/guides/markdown/docs/ai-ready/guides/markdown.md) **Did this page help you? ** h3. **Related ** [**Nitro Hooks**](https://nuxtseo.com/docs/ai-ready/guides/markdown/docs/ai-ready/nitro-api/nitro-hooks) [**Configuration**](https://nuxtseo.com/docs/ai-ready/guides/markdown/docs/ai-ready/api/config) [**AI-Optimized Content**](https://nuxtseo.com/docs/ai-ready/guides/markdown/learn-seo/nuxt/launch-and-listen/ai-optimized-content) [**llms.txt Generation** Configure llms.txt and llms-full.txt output for AI discovery.](https://nuxtseo.com/docs/ai-ready/guides/markdown/docs/ai-ready/guides/llms-txt) [**Model Context Protocol (MCP)** Connect AI agents like Claude to your Nuxt site via MCP servers with built-in tools and resources.](https://nuxtseo.com/docs/ai-ready/guides/markdown/docs/ai-ready/guides/mcp) **On this page** - [Build-Time](#build-time) - [Runtime](#runtime) - [Configuration](#configuration) - [Hooks](#hooks) --- ### llms.txt for Vue Sites · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/controlling-crawlers/llms-txt Description: Help AI assistants understand your Vue documentation with the llms.txt standard. Learn the file format, implementation, and when it matters. h1. **llms.txt for Vue Sites** Help AI assistants understand your Vue documentation with the llms.txt standard. Learn the file format, implementation, and when it matters. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)8 mins read Published **Dec 17, 2025** The [**llms.txt standard**](https://llmstxt.org/) provides AI assistants with a concise summary of your site's content. Think of it as robots.txt for AI inference. not blocking access, but guiding AI to your most useful documentation. [**Jeremy Howard**](https://github.com/AnswerDotAI/llms-txt) proposed the standard in September 2024. Unlike robots.txt, llms.txt uses Markdown and targets AI tools at inference time rather than crawlers at training time. h2. [When llms.txt Matters](#when-llmstxt-matters) llms.txt solves a specific problem: LLM context windows are too small to process entire websites. Your Vue documentation might be thousands of pages, but an AI assistant needs a curated entry point. **llms.txt is useful for:** - Documentation sites (API references, tutorials, guides) - Open source projects - Technical blogs with evergreen content - Sites where AI assistants frequently reference your content **llms.txt is overkill for:** - Marketing sites without technical docs - E-commerce product pages - News sites with time-sensitive content - Small sites with fewer than 10 pages h2. [llms.txt vs robots.txt](#llmstxt-vs-robotstxt) | **Feature** | **robots.txt** | **llms.txt** | | --- | --- | --- | | Purpose | Block/allow crawling | Guide AI to useful content | | Format | Custom syntax | Markdown | | When used | Training data collection | Inference (answering questions) | | Crawler support | All major crawlers | Limited. mostly AI coding tools | | Required | No | No | **Important:** [**AI crawlers don't currently request llms.txt**](https://www.longato.ch/llms-recommendation-2025-august/) during inference. GPTBot, ClaudeBot, and PerplexityBot use pre-built datasets and respect robots.txt, not llms.txt. The primary use case today is AI coding assistants (Cursor, Claude Code) and MCP servers that explicitly fetch llms.txt to understand project documentation. h2. [File Format](#file-format) llms.txt uses a structured Markdown format. Only the H1 title is required. everything else is optional. /llms.txt ``` h1. Vue Router Documentation > Vue Router is the official router for Vue.js. It deeply integrates with Vue.js core to make building Single Page Applications easy. Key concepts: route matching, nested routes, navigation guards, route meta fields. h2. Getting Started - [Installation](https://router.vuejs.org/installation.html): Install Vue Router via npm - [Quick Start](https://router.vuejs.org/guide/): Basic router setup - [Dynamic Routing](https://router.vuejs.org/guide/essentials/dynamic-matching.html): Route params and patterns h2. API Reference - [Router Instance](https://router.vuejs.org/api/#router-instance-methods): push, replace, go, back, forward - [Route Object](https://router.vuejs.org/api/#the-route-object): params, query, hash, matched - [Navigation Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html): beforeEach, beforeResolve, afterEach h2. Optional - [Changelog](https://github.com/vuejs/router/blob/main/CHANGELOG.md) - [Migration from Vue 2](https://router.vuejs.org/guide/migration/) ``` h3. [Required Sections](#required-sections) **H1 Title** . The only required element. Name of your project or site. h3. [Optional Sections](#optional-sections) **Blockquote** . Brief summary with key information for understanding the rest of the file. **Body content** . Paragraphs, lists, or any Markdown except headings. Provides context about the project. **H2 sections** . File lists with links to detailed documentation. Each entry is a Markdown link with optional description: ``` - [Link Text](https://url): Optional description of what this page covers ``` **Optional section** . An H2 titled "Optional" marks content that AI can skip if context is limited. h2. [Implementation](#implementation) h3. [Static File](#static-file) The simplest approach: create a static file in your public directory. ``` public/ llms.txt ``` public/llms.txt ``` h1. My Vue App > A Vue 3 application with TypeScript and Vite. h2. Documentation - [API Reference](/docs/api): REST API endpoints - [Components](/docs/components): Vue component library - [Getting Started](/docs/setup): Installation and configuration h2. Optional - [Changelog](/changelog) ``` Your file will be available at `https://yoursite.com/llms.txt`. h3. [Extended Version](#extended-version) The spec also supports `/llms-full.txt` for comprehensive documentation when context limits aren't a concern: public/llms-full.txt ``` h1. My Vue App - Full Documentation > Complete documentation including all API endpoints, components, and guides. [Full content of your docs here, potentially thousands of lines] ``` AI tools can choose between the concise `/llms.txt` or comprehensive `/llms-full.txt` based on their needs. h3. [Build-Time Generation](#build-time-generation) For large documentation sites, generate llms.txt from your content at build time: scripts/generate-llms-txt.ts ``` import { readdir, readFile, writeFile } from 'node:fs/promises' import { join } from 'node:path' interface DocPage { title: string path: string description?: string } async function generateLlmsTxt() { const docsDir = './docs' const pages: DocPage[] = [] // Scan docs directory for markdown files const files = await readdir(docsDir, { recursive: true }) for (const file of files) { if (!file.endsWith('.md')) continue const content = await readFile(join(docsDir, file), 'utf-8') const titleMatch = content.match(/^#\s+(.+)$/m) const descMatch = content.match(/^>\s+(.+)$/m) if (titleMatch) { pages.push({ title: titleMatch[1], path: \`/docs/${file.replace('.md', '')}\`, description: descMatch?.[1] }) } } // Generate llms.txt content const llmsTxt = \`# My Vue Documentation > API reference and guides for My Vue App. h2. Documentation ${pages.map(p => \`- [${p.title}](${p.path})${p.description ? \`: ${p.description}\` : ''}\`).join('\n')} \` await writeFile('./public/llms.txt', llmsTxt) } generateLlmsTxt() ``` Add to your build process: package.json ``` { "scripts": { "build": "npm run generate:llms && vite build", "generate:llms": "tsx scripts/generate-llms-txt.ts" } } ``` h2. [Framework Plugins](#framework-plugins) Several documentation frameworks have llms.txt plugins: h3. [VitePress](#vitepress) ``` npm install vitepress-plugin-llms ``` .vitepress/config.ts ``` import { defineConfig } from 'vitepress' import llmstxt from 'vitepress-plugin-llms' export default defineConfig({ vite: { plugins: [llmstxt()] } }) ``` h3. [Docusaurus](#docusaurus) ``` npm install docusaurus-plugin-llms ``` docusaurus.config.js ``` export default { plugins: ['docusaurus-plugin-llms'] } ``` h2. [Testing Your llms.txt](#testing-your-llmstxt) 1. **Check it loads**: Visit `https://yoursite.com/llms.txt` 2. **Validate format**: Ensure H1 exists and links are absolute URLs 3. **Test with AI**: Paste your llms.txt into ChatGPT or Claude and ask about your docs There's no official validator yet, but the [**llms-txt-hub**](https://github.com/thedaviddias/llms-txt-hub) directory lists sites implementing the standard. h2. [Who Uses llms.txt?](#who-uses-llmstxt) The standard has [**2,000+ GitHub stars**](https://github.com/AnswerDotAI/llms-txt) and growing adoption among documentation sites. Notable implementers include Answer.AI, fast.ai, and various open source projects. However, adoption by AI providers is limited. As of late 2025: - **No major AI crawler** (GPTBot, ClaudeBot, PerplexityBot) requests llms.txt during inference - **MCP servers** and AI coding tools can use it when explicitly configured - **Documentation frameworks** (VitePress, Docusaurus) generate it automatically The spec is still emerging. Implementing llms.txt today is forward-looking. it positions your docs for better AI integration as adoption grows. h2. [llms.txt and GEO](#llmstxt-and-geo) llms.txt complements [**Generative Engine Optimization**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/llms-txt/learn-seo/vue/launch-and-listen/ai-optimized-content) but serves a different purpose: | **GEO** | **llms.txt** | | --- | --- | | Optimizes content for AI citations | Provides structured entry point to docs | | Targets AI search (ChatGPT, Perplexity) | Targets AI coding tools and MCP servers | | Uses schema.org, content structure | Uses Markdown file format | | Improves visibility in AI responses | Improves AI understanding of your project | For maximum AI visibility, implement both: 1. Schema.org structured data for GEO 2. llms.txt for documentation discovery 3. Content structure optimized for extraction h2. [Using Nuxt?](#using-nuxt) Nuxt has dedicated support for llms.txt via [**nuxt-llms**](https://github.com/harlan-zw/nuxt-llms): nuxt.config.ts ``` export default defineNuxtConfig({ modules: ['nuxt-llms'], llms: { domain: 'https://example.com', title: 'My Nuxt App', description: 'Documentation for My Nuxt App', sections: [ { title: 'Getting Started', links: [ { title: 'Installation', href: '/docs/installation' }, { title: 'Configuration', href: '/docs/configuration' } ] } ] } }) ``` [**Learn more about AI optimization in Nuxt →**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/llms-txt/learn-seo/nuxt/launch-and-listen/ai-optimized-content) --- [**Duplicate Content** Duplicate content wastes crawl budget and splits ranking signals. Here's how to find and fix it with canonical tags, redirects, and parameter handling.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/llms-txt/learn-seo/vue/controlling-crawlers/duplicate-content) [**SPA SEO** Why Vue SPAs struggle with search engines and how to fix it. Learn when you need SSR, when prerendering works, and when SPA is fine as-is.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/llms-txt/learn-seo/vue/spa) **On this page** - [When llms.txt Matters](#when-llmstxt-matters) - [llms.txt vs robots.txt](#llmstxt-vs-robotstxt) - [File Format](#file-format) - [Implementation](#implementation) - [Framework Plugins](#framework-plugins) - [Testing Your llms.txt](#testing-your-llmstxt) - [Who Uses llms.txt?](#who-uses-llmstxt) - [llms.txt and GEO](#llmstxt-and-geo) - [Using Nuxt?](#using-nuxt) --- ### Install Nuxt SEO Pro · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation Description: Get the Pro modules running locally in minutes. Free to try—license required for production. **Getting Started** h1. **Install Nuxt SEO Pro** [Copy for LLMs h2. [Before You Start](#before-you-start) All modules are free to try in dev. You only need a [**license**](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation/pro#pricing) when deploying to production. Already have a license? [**Set up your license key**](#license-setup) after installation. h2. [Install the Modules](#install-the-modules) Install what you need. Each works independently. **Installation Progress** - Install AI Ready module - Install AI Kit module - Install Skew Protection module - Set up license key for production h3. [AI Ready](#ai-ready) Your entire site converted to AI-readable markdown, kept in sync automatically. ``` npx nuxi module add nuxt-ai-ready ``` **Verify it works:** Visit `/llms.txt` in your browser. h3. [AI Kit](#ai-kit) Conversational search that runs on your infrastructure, with the LLM of your choice. ``` npx nuxi module add nuxt-ai-kit ``` **Verify it works:** Check the AI Kit tab in Nuxt DevTools. h3. [Skew Protection](#skew-protection) Detects client/server mismatch and prompts users to refresh gracefully. ``` npx nuxi module add nuxt-skew-protection ``` **Verify it works:** Check the Skew Protection tab in Nuxt DevTools. h2. [License Setup](#license-setup) For production, add your license key: ``` NUXT_SEO_PRO_KEY=your-license-key-here ``` [Sign in](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation/pro) to see your license key. Check the [**Pro**](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation/pro) page if you need to purchase a license. No license key? Modules work in dev mode but show a warning in production. h2. [What's Next](#whats-next) - [**MCP Setup**](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation/docs/nuxt-seo-pro/mcp/installation) — Connect Claude Code or Cursor to your docs Need help? [**Open an issue**](https://github.com/harlan-zw/nuxt-seo-pro/issues) or reach out on Discord. [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/1.getting-started/1.installation.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation/docs/nuxt-seo-pro/getting-started/installation.md) **Did this page help you? ** [**Introduction** The futures SEO modules for todays Nuxters. Handle llms.txt, MCP, and version skew for your Nuxt site.](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation/docs/nuxt-seo-pro/getting-started/introduction) [**Pro MCP** MCP server with AI-powered SEO tools for Claude, Cursor, and other LLMs. Analyze pages, research keywords, generate schema.org and OG images.](https://nuxtseo.com/docs/nuxt-seo-pro/getting-started/installation/docs/nuxt-seo-pro/mcp) **On this page** - [Before You Start](#before-you-start) - [Install the Modules](#install-the-modules) - [License Setup](#license-setup) - [What's Next](#whats-next) --- ### Introduction: Pro SEO MCP · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp Description: MCP server with AI-powered SEO tools for Claude, Cursor, and other LLMs. Analyze pages, research keywords, generate schema.org and OG images. **Nuxt Seo Pro** h1. **Introduction: Pro SEO MCP** [Copy for LLMs SEO work is tedious. Writing meta tags, generating schema.org, researching keywords—all repetitive tasks that eat development time. The Nuxt SEO Pro MCP server gives Claude and other MCP-compatible LLMs the tools to do this work for you. Claude Code $claude "Improve SEO on my about page" ●nuxt-seo-pro ⚡analyze_page→ pages/about.vue ⚡generate_schema_org→ Person schema Missing structured data, no OG image, description too short (42 chars) ✓Fixed 3 SEO issues - 🔍 **Analyze & fix page SEO** — Scan Vue/Nuxt files for missing meta, schema.org, and OG images, then auto-generate the code - 📊 **Research keywords in your IDE** — Query volumes, difficulty scores, and SERP analysis without leaving your editor - ✍️ **Generate SEO content** — Create outlines and full articles from keyword research following your writing guide - 🎯 **Competitor gap analysis** — Compare rankings and find keywords competitors rank for that you don't h2. [What You Can Do](#what-you-can-do) h3. [Setup](#setup) Register your site to enable sitemap, ranking, and site-specific analysis tools. | **Tool** | **What It Does** | | --- | --- | | `init_site` | Register and profile your site (run once per site) | | `profile_site` | Refresh site profile (type, industry, audience) | | `get_sitemap_urls` | Get URLs from your sitemap for batch analysis | h3. [Fix Page SEO](#fix-page-seo) Ask your AI assistant to analyze and improve any Vue or Nuxt Content page. It reads your file, detects issues, and writes the fixes. | **Tool** | **What It Does** | | --- | --- | | `analyze_page` | Scans Vue SFCs for SEO issues and missing elements | | `analyze_content_page` | Same for Nuxt Content markdown files | | `generate_schema_org` | Writes ready-to-paste `useSchemaOrg()` code | | `generate_og_image_template` | Creates OG image components | | `extract_meta_tags` | Shows what actually renders (vs source code) | h3. [Research Keywords](#research-keywords) Find what to write about. Your AI queries DataForSEO for keyword volumes, difficulty scores, and SERP analysis. | **Tool** | **What It Does** | | --- | --- | | `research_keywords` | Long-tail keywords with volume and difficulty | | `analyze_serp` | Top 10 results, SERP features, AI Overview detection | | `check_rankings` | Keywords a domain ranks for (competitor analysis) | h3. [Market Research](#market-research) Validate ideas before building. Check social signals, domain availability, and competitor traffic. | **Tool** | **What It Does** | | --- | --- | | `analyze_social_signals` | GitHub repos, Reddit threads, sentiment analysis | | `check_domain_availability` | WHOIS lookup for up to 10 domains at once | | `estimate_domain_traffic` | Monthly organic traffic estimates for any domain | h3. [Generate Content](#generate-content) Create SEO-optimized articles from research. Your AI follows your writing guide to produce content that matches your voice. | **Prompt** | **What It Does** | | --- | --- | | `improve_page_seo` | Full analysis workflow—research → analyze → fix | | `content_brief` | Outline from keywords with H2 structure | | `article_generation` | Full article from outline | h2. [Example Workflows](#example-workflows) **"Set up mysite.com for SEO analysis"** Registers your site, profiles it to detect type and audience, and enables sitemap/ranking tools. You only do this once. **"Add schema.org to my product page"** Analyzes your file, detects it's an e-commerce page, and generates Product schema with price, availability, and review aggregation. **"What keywords should I target for a sitemap guide?"** Researches "sitemap" keywords via DataForSEO, returns volume/difficulty scores, and suggests long-tail opportunities like "nuxt sitemap generator" or "xml sitemap best practices". **"Write an article about Nuxt meta tags"** Runs keyword research, creates an outline, then generates a full article following your writing guide. Marks gaps with `[STAT NEEDED]` for human review. **"Compare my SEO to competitor.com"** Fetches competitor rankings, compares to your site, and identifies content gaps—keywords they rank for that you don't. **"Is coolapp.dev available?"** Checks domain availability via WHOIS. Returns registration status and, for taken domains, registrar and expiration date. **"How much traffic does competitor.com get?"** Estimates monthly organic search traffic, shows top-performing pages, and reveals which keywords drive the most visits. **"Is there demand for a Nuxt analytics tool?"** Analyzes social signals across GitHub and Reddit. Returns repo counts, star totals, thread activity, and trend direction. h2. [Requirements](#requirements) - Nuxt SEO Pro license (API key starts with `nsp_`) - Any MCP-compatible client (Claude Desktop, Claude Code, Cursor, etc.) Content intelligence tools (keyword research, SERP analysis) require the Pro API key. Dev assist tools work with any verified site. [**Install the MCP server →**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/docs/nuxt-seo-pro/mcp/installation) [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/0.index.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/docs/nuxt-seo-pro/mcp.md) **Did this page help you? ** [**Installation** Get the Pro modules running locally in minutes. Free to try—license required for production.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/docs/nuxt-seo-pro/getting-started/installation) [**Installation** Add the Nuxt SEO Pro MCP server to Claude Desktop, Claude Code, or Cursor. One config file change and your AI assistant gains SEO tools.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/docs/nuxt-seo-pro/mcp/installation) **On this page** - [What You Can Do](#what-you-can-do) - [Example Workflows](#example-workflows) - [Requirements](#requirements) --- ### AI Page Analysis Tools · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp/dev-assist-tools Description: MCP tools for AI-powered SEO analysis. Scan Vue files for issues, generate schema.org code, create OG image templates—through any MCP client. **Mcp** h1. **AI Page Analysis Tools** [Copy for LLMs AI-powered tools for page-level SEO. Your AI analyzes Vue and Nuxt Content files, identifies issues, and generates ready-to-paste code. Requires a Pro API key. h2. [extract_meta_tags](#extract_meta_tags) Extract meta tags from **rendered HTML output** (not source code). This tool shows what actually renders, including meta from plugins, config, and middleware. This tool requires the rendered HTML, not Vue/React source code. For source code analysis, use `analyze_page` (Vue) or `analyze_content_page` (Markdown). | **Parameter** | **Type** | **Description** | | --- | --- | --- | | `url` | `string` | URL to fetch and extract meta tags from | | `html` | `string` | Raw HTML to extract from (for local dev servers) | Provide either `url` or `html`, not both. ``` // From live URL extract_meta_tags({ url: 'https://nuxtseo.com/docs/sitemap/getting-started' }) // From local dev server // First fetch the rendered HTML: // curl http://localhost:3000/my-page > page.html extract_meta_tags({ html: '<html><head><title>My Page......' }) ``` Returns extracted Unhead input: ``` { "url": "https://nuxtseo.com/docs/sitemap/getting-started", "title": "Getting Started with Nuxt Sitemap", "meta": [ { "name": "description", "content": "Generate a sitemap for your Nuxt app..." }, { "property": "og:title", "content": "Getting Started with Nuxt Sitemap" } ], "link": [ { "rel": "canonical", "href": "https://nuxtseo.com/docs/sitemap/getting-started" } ], "script": [ { "type": "application/ld+json", "innerHTML": "..." } ] } ``` h3. [Use Cases](#use-cases) - **Audit live pages** - See what's actually rendered vs source code - **Debug plugin output** - Check if `@nuxtjs/seo` is adding expected tags - **Compare environments** - Verify staging matches production - **Test local changes** - Pass HTML from `$fetch('http://localhost:3000')` to test before deploy h2. [analyze_page](#analyze_page) Analyze a Vue file for SEO. Detects page type, existing SEO calls, and returns prioritized suggestions. Detects installed modules to avoid suggesting what's already handled. | **Parameter** | **Type** | **Description** | | --- | --- | --- | | `filePath` | `string` | Path to Vue file (e.g., `pages/about.vue`) | | `fileContent` | `string` | Content of the Vue file | | `nuxtConfig` | `string` | nuxt.config.ts content (optional, for module detection) | ``` analyze_page({ filePath: 'pages/blog/[slug].vue', fileContent: '...', nuxtConfig: '...' }) ``` Returns: ``` { "filePath": "pages/blog/[slug].vue", "pageType": "BlogPosting", "installedModules": ["@nuxtjs/seo"], "hasSeoMeta": true, "hasSchemaOrg": false, "hasOgImage": false, "hasH1": true, "suggestions": [ { "priority": "medium", "category": "schema", "issue": "No structured data found", "fix": "Add useSchemaOrg([defineBlogPosting({...})])" }, { "priority": "medium", "category": "og-image", "issue": "No OG image defined", "fix": "Add defineOgImage({ component: '...' }) or " } ] } ``` h3. [Page Type Detection](#page-type-detection) Detection uses both file path and content patterns: | **Path Pattern** | **Detected Type** | | --- | --- | | `/blog/`, `/posts/`, `/articles/` | `BlogPosting` | | `/tool`, `/generator`, `/converter`, `/checker` | `SoftwareApplication` | | `/about` | `Person` | | `/product`, `/shop` | `Product` | | `/faq` | `FAQPage` | | `/how-to`, `/tutorial`, `/guide` | `HowTo` | | `/recipe` | `Recipe` | | `/event` | `Event` | | `/company`, `/team` | `Organization` | Content patterns also influence detection—prices suggest Product, dates suggest Article. h3. [SEO Quality Checks](#seo-quality-checks) The tool now includes detailed SEO quality analysis: | **Check** | **Threshold** | **Suggestion** | | --- | --- | --- | | Title length | > 60 chars | Warns to shorten | | Title length | < 30 chars | Suggests longer title | | Description length | > 160 chars | Warns to shorten | | Description length | < 70 chars | Suggests longer description | | Multiple H1s | > 1 | Warns to use single H1 | | Images without alt | Any | Lists count, suggests fix | h3. [Suggestion Priorities](#suggestion-priorities) Suggestions are sorted by priority (high → medium → low), then by category importance: | **Priority** | **Category** | **Typical Issues** | | --- | --- | --- | | `high` | meta | Missing title, description, or `useSeoMeta()` | | `medium` | schema | No structured data | | `medium` | og-image | No OG image | | `medium` | meta | Title/description length issues | | `medium` | html | Missing H1, multiple H1s, no canonical URL | | `medium` | accessibility | Images missing alt attributes | | `low` | html, performance | No semantic elements, using `` instead of `` | Within the same priority level, schema suggestions appear before meta optimization suggestions. h2. [analyze_content_page](#analyze_content_page) Analyze a Nuxt Content markdown file for SEO. Parses frontmatter, analyzes content structure, and returns prioritized suggestions. Use this for `.md` files in the `content/` directory. For Vue SFCs (`.vue` files), use `analyze_page` instead. | **Parameter** | **Type** | **Description** | | --- | --- | --- | | `filePath` | `string` | Path to markdown file (e.g., `content/blog/my-post.md`) | | `fileContent` | `string` | Content of the markdown file | ``` analyze_content_page({ filePath: 'content/blog/nuxt-seo-guide.md', fileContent: '---\ntitle: SEO Guide\n---\n\n# Content here...' }) ``` Returns: ``` { "filePath": "content/blog/nuxt-seo-guide.md", "pageType": "BlogPosting", "frontmatter": { "fields": ["title", "description", "date"], "hasTitle": true, "hasDescription": true, "hasImage": false, "hasDate": true, "hasAuthor": false }, "content": { "wordCount": 1250, "h1Count": 1, "h2Count": 4, "imageCount": 2, "internalLinkCount": 3, "externalLinkCount": 1, "estimatedReadTime": 7 }, "suggestions": [ { "priority": "medium", "category": "og-image", "issue": "No cover/OG image specified", "fix": "Add image: \"/path/to/image.jpg\" to frontmatter" }, { "priority": "medium", "category": "meta", "issue": "Missing author information", "fix": "Add author: \"Author Name\" to frontmatter" } ] } ``` h3. [Page Type Detection](#page-type-detection-1) Detection uses file path and frontmatter: | **Path Pattern** | **Detected Type** | | --- | --- | | `/blog/`, `/posts/`, `/articles/` | `BlogPosting` | | `/learn/`, `/guides/`, `/tutorials/` | `Article` | | `/docs/`, `/documentation/` | `TechArticle` | | `/recipes/` | `HowTo` | | `/faq` | `FAQPage` | | `/snippets/`, `/examples/` | `Article` | h3. [Content Analysis](#content-analysis) The tool analyzes markdown structure: | **Metric** | **What It Checks** | | --- | --- | | `wordCount` | Total words (excluding code blocks) | | `h1Count` | Number of `#` headings (should be 0-1) | | `h2Count` | Number of `##` section headings | | `imageCount` | Markdown images `![](...)` | | `internalLinkCount` | Links to same site | | `externalLinkCount` | Links to other sites | | `estimatedReadTime` | Minutes at 200 wpm | h2. [generate_schema_org](#generate_schema_org) Generate ready-to-use `useSchemaOrg()` code. Pass metadata from `analyze_page` or provide manually. | **Parameter** | **Type** | **Description** | | --- | --- | --- | | `pageType` | `string` | `Article`, `BlogPosting`, `Product`, `Person`, `Organization`, `WebPage`, `FAQPage`, `HowTo`, `Recipe`, `Event`, `LocalBusiness`, `SoftwareApplication` | | `extractedMeta` | `object` | Metadata for the schema (see below) | | `options.includeWebPage` | `boolean` | Include `defineWebPage()` wrapper (default: `true`) | | `options.includeBreadcrumbs` | `boolean` | Include breadcrumb schema (default: `false`) | h3. [extractedMeta Properties](#extractedmeta-properties) | **Property** | **Types** | **Description** | | --- | --- | --- | | `title` | `string` | Page title / headline | | `description` | `string` | Page description | | `author` | `string` | Author name | | `publishedAt` | `string` | ISO date published | | `modifiedAt` | `string` | ISO date modified | | `image` | `string` | Primary image URL | | `price` | `string` | Product price | | `currency` | `string` | Currency code (USD, EUR) | | `name` | `string` | Entity name | | `jobTitle` | `string` | Person's job title | | `email` | `string` | Contact email | | `telephone` | `string` | Contact phone | | `address` | `string` | Physical address | | `socialUrls` | `string[]` | Social profile URLs | | `faqs` | `array` | FAQ questions/answers | | `steps` | `array` | HowTo steps | | `rating` | `number` | Aggregate rating value | | `reviewCount` | `number` | Number of reviews | | `applicationCategory` | `string` | App category (default: `DeveloperApplication`) | | `operatingSystem` | `string` | OS requirement (default: `Web`) | ``` generate_schema_org({ pageType: 'BlogPosting', extractedMeta: { title: 'How to Add Meta Tags in Nuxt', description: 'Guide to using useSeoMeta...', author: 'Harlan Wilton', publishedAt: '2024-01-15', image: '/blog/meta-tags.png' }, options: { includeBreadcrumbs: true } }) ``` Returns: ``` { "pageType": "BlogPosting", "code": "useSchemaOrg([\n defineWebPage({...}),\n defineBlogPosting({...}),\n defineBreadcrumb({...})\n])", "requires": "@nuxtjs/schema-org", "imports": "import { useSchemaOrg, defineWebPage, defineBlogPosting, defineBreadcrumb } from '@unhead/schema-org/vue'" } ``` h2. [generate_og_image_template](#generate_og_image_template) Generate a Vue component for OG images. Creates ready-to-use templates in `components/OgImage/`. | **Parameter** | **Type** | **Default** | **Description** | | --- | --- | --- | --- | | `pageType` | `string` | required | `blog`, `product`, `landing`, `docs`, `tool` | | `props` | `string[]` | `[]` | Additional props the template should accept | | `style` | `string` | `minimal` | `minimal`, `gradient`, `image-bg` | ``` generate_og_image_template({ pageType: 'blog', style: 'gradient', props: ['category'] }) ``` Returns: ``` { "filename": "OgImageBlog.vue", "path": "components/OgImage/OgImageBlog.vue", "code": "\n", "usage": "defineOgImage({ component: 'OgImageBlog', title: '...' })", "requires": "nuxt-og-image" } ``` h3. [Default Props by Page Type](#default-props-by-page-type) | **Page Type** | **Default Props** | | --- | --- | | `blog` | `title`, `description`, `author`, `date`, `readTime` | | `product` | `title`, `description`, `price`, `category` | | `landing` | `title`, `description`, `siteName` | | `docs` | `title`, `description`, `category` | | `tool` | `title`, `description`, `category` | [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/2.dev-assist-tools.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/dev-assist-tools/docs/nuxt-seo-pro/mcp/dev-assist-tools.md) **Did this page help you? ** [**Installation** Add the Nuxt SEO Pro MCP server to Claude Desktop, Claude Code, or Cursor. One config file change and your AI assistant gains SEO tools.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/dev-assist-tools/docs/nuxt-seo-pro/mcp/installation) [**Keyword Research** MCP tools for keyword research and SERP analysis. Find long-tail keywords, analyze competition, and discover content gaps—through your AI assistant.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/dev-assist-tools/docs/nuxt-seo-pro/mcp/content-intelligence) **On this page** - [extract_meta_tags](#extract_meta_tags) - [analyze_page](#analyze_page) - [analyze_content_page](#analyze_content_page) - [generate_schema_org](#generate_schema_org) - [generate_og_image_template](#generate_og_image_template) --- ### AI Keyword Research Tools · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp/content-intelligence Description: MCP tools for keyword research and SERP analysis. Find long-tail keywords, analyze competition, and discover content gaps—through your AI assistant. **Mcp** h1. **AI Keyword Research Tools** [Copy for LLMs Keyword research and competitive analysis through your AI. Ask for keywords, analyze SERPs, check what competitors rank for—all powered by DataForSEO. Requires a Pro API key. h2. [research_keywords](#research_keywords) Find long-tail keywords with search volume, difficulty, and CPC. Uses relaxed filters by default to return more results. Works best with consumer-facing search terms. Very technical or niche developer topics (e.g., "nuxt sitemap configuration") may return limited data. For those, use `analyze_serp` to see what content actually ranks. | **Parameter** | **Type** | **Default** | **Description** | | --- | --- | --- | --- | | `topic` | `string` | required | Seed topic or keyword | | `minVolume` | `number` | `10` | Minimum monthly search volume | | `maxVolume` | `number` | `10000` | Maximum monthly search volume | | `maxDifficulty` | `number` | `60` | Maximum keyword difficulty (0-100) | | `limit` | `number` | `20` | Max keywords to return (1-50) | | `includeRelated` | `boolean` | `true` | Include "searches related to" keywords | ``` research_keywords({ topic: 'nuxt meta tags', maxDifficulty: 30, limit: 10 }) ``` Returns: ``` { "topic": "nuxt meta tags", "keywords": [ { "keyword": "nuxt 4 meta tags", "volume": 320, "difficulty": 24, "intent": "informational", "cpc": 0.45 }, { "keyword": "useseoMeta nuxt", "volume": 210, "difficulty": 18, "intent": "informational", "cpc": 0.32 } ], "totalFound": 156, "filters": { "minVolume": 10, "maxVolume": 10000, "maxDifficulty": 30 } } ``` h3. [Keyword Difficulty Scale](#keyword-difficulty-scale) | **Difficulty** | **Meaning** | **Strategy** | | --- | --- | --- | | 0-20 | Easy | New sites can rank with quality content | | 21-40 | Medium | Needs solid content + some authority | | 41-60 | Hard | Requires established domain + links | | 61-100 | Very hard | Only high-authority sites compete | h2. [analyze_serp](#analyze_serp) Analyze SERP competition for a keyword. Returns top results with domain rank and SERP features present. | **Parameter** | **Type** | **Default** | **Description** | | --- | --- | --- | --- | | `keyword` | `string` | required | Keyword to analyze | | `depth` | `number` | `10` | Number of results (1-20) | ``` analyze_serp({ keyword: 'nuxt schema.org', depth: 10 }) ``` Returns: ``` { "keyword": "nuxt schema.org", "results": [ { "position": 1, "url": "https://nuxtseo.com/docs/schema-org/getting-started", "title": "Getting Started - Schema.org", "domain": "nuxtseo.com", "domainRank": 45 }, { "position": 2, "url": "https://nuxt.com/modules/schema-org", "title": "Schema.org Module", "domain": "nuxt.com", "domainRank": 78 } ], "serpFeatures": ["featured_snippet", "people_also_ask"], "hasAiOverview": false, "hasFeaturedSnippet": true, "hasLocalPack": false, "hasPeopleAlsoAsk": true } ``` `domainRank` may be `null` for newer or smaller domains that lack authority data in DataForSEO's database. h3. [SERP Feature Types](#serp-feature-types) | **Feature** | **What It Means** | | --- | --- | | `ai_overview` | Google AI Overview appears—harder to get clicks | | `featured_snippet` | Snippet opportunity—structure content for it | | `people_also_ask` | FAQ opportunity—answer related questions | | `local_pack` | Local results—need LocalBusiness schema | | `video` | Video results—consider video content | | `images` | Image pack—optimize image SEO | h2. [check_rankings](#check_rankings) Check what keywords a domain ranks for. Useful for competitor analysis and finding content gaps. | **Parameter** | **Type** | **Default** | **Description** | | --- | --- | --- | --- | | `domain` | `string` | required | Domain to check (without https://) | | `limit` | `number` | `50` | Max keywords to return (1-100) | | `minPosition` | `number` | `1` | Minimum ranking position | | `maxPosition` | `number` | `20` | Maximum ranking position | ``` check_rankings({ domain: 'competitor.com', maxPosition: 10, limit: 30 }) ``` Returns: ``` { "domain": "competitor.com", "keywords": [ { "keyword": "vue seo guide", "position": 3, "volume": 480, "traffic": 156, "url": "https://competitor.com/blog/vue-seo" }, { "keyword": "nuxt meta description", "position": 7, "volume": 320, "traffic": 42, "url": "https://competitor.com/blog/nuxt-meta" } ], "positionRange": { "min": 1, "max": 10 } } ``` h3. [Content Gap Analysis](#content-gap-analysis) Compare your rankings to competitors: 1. Run `check_rankings` on your domain 2. Run `check_rankings` on 2-3 competitors 3. Find keywords they rank for that you don't 4. Create content targeting those gaps h2. [get_sitemap_urls](#get_sitemap_urls) Get URLs from a verified site's sitemap. Useful for content audits and analysis. | **Parameter** | **Type** | **Default** | **Description** | | --- | --- | --- | --- | | `siteUrl` | `string` | required | Site URL (must be verified in your Pro account) | | `limit` | `number` | `15` | Max URLs to return (1-50) | ``` get_sitemap_urls({ siteUrl: 'https://mysite.com', limit: 10 }) ``` Returns: ``` { "siteId": "site_abc123", "siteName": "My Site", "urls": [ "https://mysite.com/blog/detailed-guide", "https://mysite.com/blog/another-post", "https://mysite.com/docs/getting-started" ], "total": 156 } ``` URLs are sorted by path depth (deeper = more likely to be content pages). Skips sitemap indexes, API routes, and internal routes. h3. [Site Verification](#site-verification) The site must be verified in your Pro account. Add sites in the [**Pro dashboard**](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/content-intelligence/pro) before using this tool. h2. [Data Freshness](#data-freshness) | **Tool** | **Cache Duration** | **Data Source** | | --- | --- | --- | | `research_keywords` | 24 hours | DataForSEO keyword database | | `analyze_serp` | 12 hours | Live SERP results | | `check_rankings` | 24 hours | DataForSEO ranking database | | `get_sitemap_urls` | 1 hour | Live sitemap fetch | Results are cached to reduce API costs and improve response time. h2. [Troubleshooting](#troubleshooting) h3. [Empty keyword results](#empty-keyword-results) If `research_keywords` returns no keywords: 1. **Topic too niche** - Developer-focused terms like "nuxt sitemap configuration" have limited keyword data. Try broader terms like "sitemap generator" or "xml sitemap" 2. **Try analyze_serp instead** - See what content actually ranks for your term, even without keyword volume data 3. **Use shorter phrases** - 2-3 word queries work better than long-tail phrases h3. [Filters return no matches](#filters-return-no-matches) If you specify strict filters (e.g., `minVolume: 100, maxVolume: 1000, maxDifficulty: 30`) and no keywords match, the tool returns an empty array with a helpful message: ``` { "topic": "seo tools", "keywords": [], "totalFound": 45, "filters": { "minVolume": 100, "maxVolume": 1000, "maxDifficulty": 30 }, "message": "No keywords matched filters (volume: 100-1000, difficulty: <30).", "tip": "Found 45 keywords but none matched. Try broader filters: minVolume: 10, maxVolume: 50000, maxDifficulty: 70" } ``` The `totalFound` field shows how many keywords were found before filtering, so you know whether to broaden your filters or try a different topic. h3. [Missing metrics (CPC, difficulty)](#missing-metrics-cpc-difficulty) Some keywords may have `null` values for CPC or difficulty: - **CPC** - Only populated for keywords with active ad bidding - **Difficulty** - Requires sufficient ranking data; new/niche terms may lack scores This is a DataForSEO data limitation, not a tool issue. h3. [domainRank missing in SERP results](#domainrank-missing-in-serp-results) The `domainRank` field in `analyze_serp` results depends on DataForSEO having authority data for that domain. Newer or smaller sites may not have scores. [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/3.content-intelligence.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/content-intelligence/docs/nuxt-seo-pro/mcp/content-intelligence.md) **Did this page help you? ** [**Page Analysis** MCP tools for AI-powered SEO analysis. Scan Vue files for issues, generate schema.org code, create OG image templates—through any MCP client.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/content-intelligence/docs/nuxt-seo-pro/mcp/dev-assist-tools) [**Content Prompts** Pre-built MCP prompts for SEO content creation. Generate article outlines, write full articles, and improve page SEO—through any MCP client.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/content-intelligence/docs/nuxt-seo-pro/mcp/prompts) **On this page** - [research_keywords](#research_keywords) - [analyze_serp](#analyze_serp) - [check_rankings](#check_rankings) - [get_sitemap_urls](#get_sitemap_urls) - [Data Freshness](#data-freshness) - [Troubleshooting](#troubleshooting) --- ### AI Content Generation Prompts · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp/prompts Description: Pre-built MCP prompts for SEO content creation. Generate article outlines, write full articles, and improve page SEO—through any MCP client. **Mcp** h1. **AI Content Generation Prompts** [Copy for LLMs Pre-built prompts that chain multiple MCP tools into complete workflows. Create content briefs, generate articles, or run full page SEO improvements—your AI handles the orchestration. h2. [improve_page_seo](#improve_page_seo) Analyze a Nuxt page and generate SEO improvements. Orchestrates multiple tools in sequence. | **Parameter** | **Type** | **Description** | | --- | --- | --- | | `filePath` | `string` | Path to Vue file (e.g., `app/pages/about.vue`) | | `fileContent` | `string` | Content of the Vue file | | `topic` | `string` | Page topic for keyword research (optional, auto-detected) | | `nuxtConfig` | `string` | nuxt.config.ts content (optional, for module detection) | | `liveUrl` | `string` | Live URL to extract rendered meta (optional, compares source vs output) | | `liveHtml` | `string` | Raw HTML from local dev server (optional, for non-deployed pages) | ``` // Basic usage improve_page_seo({ filePath: 'app/pages/tools/meta-checker.vue', fileContent: '...', topic: 'meta tag checker tool' }) // With live comparison improve_page_seo({ filePath: 'app/pages/tools/meta-checker.vue', fileContent: '...', liveUrl: 'https://mysite.com/tools/meta-checker' }) // With local dev HTML improve_page_seo({ filePath: 'app/pages/tools/meta-checker.vue', fileContent: '...', liveHtml: '...' // from $fetch('http://localhost:3000/tools/meta-checker') }) ``` h3. [Workflow](#workflow) The prompt runs these tools in sequence: 1. **extract_meta_tags** (optional) - Extract rendered meta from live URL or HTML 2. **analyze_page** - Detect page type, existing SEO, generate suggestions 3. **research_keywords** - Find long-tail keywords (may return empty for niche technical topics) 4. **generate_schema_org** - Generate structured data if missing 5. **generate_og_image_template** - Create OG image component if missing h3. [Output](#output) Returns: - **Rendered vs Source** (if `liveUrl`/`liveHtml` provided) - Comparison of what's in source vs what actually renders - Top 5 long-tail keywords with volume/difficulty - Current SEO status (what already exists) - Ready-to-paste code blocks: - `useSeoMeta()` with researched keywords - `useSchemaOrg()` for the page type - `defineOgImage()` configuration - Implementation notes (modules to install) --- h2. [Content Workflow](#content-workflow) The content prompts work together: ``` research_keywords → content_brief → article_generation ``` 1. **Research keywords** - Find target keywords for topic 2. **Create brief** - Generate structured outline 3. **Write article** - Generate full content following the style guide h2. [content_brief](#content_brief) Generate a structured outline for an article. Use after keyword research. | **Parameter** | **Type** | **Description** | | --- | --- | --- | | `topic` | `string` | Main topic for the article | | `targetKeywords` | `string` | Comma-separated target keywords (3-5) | | `competitorUrls` | `string` | Comma-separated URLs to differentiate from (optional) | | `pageType` | `string` | `technical`, `marketing`, or `tutorial` (default: `technical`) | ``` content_brief({ topic: 'Adding Schema.org to Nuxt Pages', targetKeywords: 'nuxt schema.org, useSchemaOrg nuxt, nuxt structured data', pageType: 'technical' }) ``` h3. [Output Structure](#output-structure) The prompt returns: 1. **Primary search intent** - What the user wants to accomplish 2. **Unique angle** - How to differentiate from competitors 3. **H2 sections** (4-6) - Each with: - Heading text - Bullet points to cover - Target keywords to use 4. **Key questions** - Questions users ask about the topic 5. **Frontmatter suggestion** - Title and related pages h3. [Rules Applied](#rules-applied) The brief follows the Writing Guide: - Code within first 3 scrolls - Parameters in tables - Primary keyword in H1 and first paragraph - H2s as useful navigation, not keyword lists - Some H2s as questions users ask h2. [article_generation](#article_generation) Generate a full article from an outline. Embeds the complete Writing Guide rules. | **Parameter** | **Type** | **Default** | **Description** | | --- | --- | --- | --- | | `outline` | `string` | required | Content brief from `content_brief` | | `targetWordCount` | `string` | `"1500"` | Target length (500-5000) | | `includeCodeExamples` | `string` | `"true"` | Include code examples (`true`/`false`) | | `sitemapUrls` | `string` | - | Comma-separated site URLs for internal linking | ``` article_generation({ outline: '# Content Brief\n\n## Primary Intent...', targetWordCount: '2000', includeCodeExamples: 'true', sitemapUrls: '/docs/schema-org/getting-started, /docs/seo-utils/api/use-seo-meta' }) ``` h3. [Embedded Rules](#embedded-rules) The prompt includes: **Banned words:** dive into, crucial, leverage, ensure, comprehensive... **Banned phrases:** "it's important to note", "in today's X", "let's explore"... **Quick fixes:** | **Slop** | **Fix** | | --- | --- | | It's important to note... | just state it | | This allows you to... | You can... | | In order to... | To... | **Voice rules:** - Developer-to-developer, casual but accurate - State opinions: "This is lazy", "overkill for most sites" - Say what NOT to do **Structure rules:** - Code within first 3 scrolls - Parameters in tables, not prose - No "Best practices" sections h3. [Gap Markers](#gap-markers) The output includes markers for content that needs follow-up: ``` [STAT NEEDED: percentage of sites with broken meta tags] [VERIFY: does this work in Nuxt 4?] [EXAMPLE NEEDED: real-world product schema] [LINK: internal link to related page] ``` Review the article and fill these gaps before publishing. h2. [Chaining Prompts](#chaining-prompts) A full workflow for creating a new article: ``` // 1. Research keywords const keywords = await research_keywords({ topic: 'nuxt meta tags' }) // 2. Create outline const brief = await content_brief({ topic: 'Adding Meta Tags in Nuxt', targetKeywords: keywords.keywords.map(k => k.keyword).slice(0, 5).join(', ') }) // 3. Generate article const article = await article_generation({ outline: brief, targetWordCount: '1500' }) ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/4.prompts.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/prompts/docs/nuxt-seo-pro/mcp/prompts.md) **Did this page help you? ** [**Keyword Research** MCP tools for keyword research and SERP analysis. Find long-tail keywords, analyze competition, and discover content gaps—through your AI assistant.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/prompts/docs/nuxt-seo-pro/mcp/content-intelligence) [**Social Signals** MCP tool for analyzing social proof and community activity. Validate market demand across GitHub, Reddit, and Twitter before building.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/prompts/docs/nuxt-seo-pro/mcp/social-signals) **On this page** - [improve_page_seo](#improve_page_seo) - [Content Workflow](#content-workflow) - [content_brief](#content_brief) - [article_generation](#article_generation) - [Chaining Prompts](#chaining-prompts) --- ### AI Social Signals Analysis · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp/social-signals Description: MCP tool for analyzing social proof and community activity. Validate market demand across GitHub, Reddit, and Twitter before building. **Mcp** h1. **AI Social Signals Analysis** [Copy for LLMs Validate market demand and community interest before building. Ask your AI to research social signals across GitHub, Reddit, and Twitter to understand if a niche is active. Requires a Pro API key. h2. [analyze_social_signals](#analyze_social_signals) Analyze social proof and community activity for a topic. Returns repo counts, star totals, thread activity, sentiment, and trend direction. | **Parameter** | **Type** | **Required** | **Description** | | --- | --- | --- | --- | | `topic` | `string` | Yes | Topic or product idea to research | | `platforms` | `string[]` | No | Filter to specific platforms: `github`, `reddit`, `twitter`. Default: all | ``` analyze_social_signals({ topic: 'nuxt seo', platforms: ['github', 'reddit'] }) ``` Returns: ``` { "topic": "nuxt seo", "github": { "repos": 45, "totalStars": 12500, "topRepos": [ { "name": "nuxt/seo", "fullName": "nuxt/seo", "stars": 8200, "lastCommit": "2025-01-02", "url": "https://github.com/nuxt/seo" } ] }, "reddit": { "threads": 23, "totalUpvotes": 890, "topSubreddits": ["r/vuejs", "r/webdev", "r/nuxtjs"], "sentiment": "positive", "topThreads": [ { "title": "Best SEO practices for Nuxt 4?", "upvotes": 142, "comments": 28, "subreddit": "vuejs", "url": "https://reddit.com/r/vuejs/...", "created": "2025-01-01" } ] }, "twitter": { "mentions": 0, "influencerMentions": 0, "note": "Twitter/X API integration coming soon." }, "summary": { "activityLevel": "high", "trend": "growing", "totalSignals": 68 } } ``` h3. [Platform Data](#platform-data) h4. [GitHub](#github) Searches public repositories matching your topic: | **Field** | **Description** | | --- | --- | | `repos` | Total matching repositories | | `totalStars` | Combined stars from top repos | | `topRepos` | Top 5 repos by stars with last commit date | h4. [Reddit](#reddit) Searches threads via Reddit's public JSON API: | **Field** | **Description** | | --- | --- | | `threads` | Matching thread count | | `totalUpvotes` | Combined upvotes from results | | `topSubreddits` | Most active subreddits for this topic | | `sentiment` | `positive`, `neutral`, `negative`, or `mixed` (based on upvote ratios) | | `topThreads` | Top 5 threads with engagement data | h4. [Twitter](#twitter) Twitter/X API integration is coming soon. The API is expensive and heavily restricted. For now, consider searching X directly for recent mentions. h3. [Activity Levels](#activity-levels) | **Level** | **Signal Count** | **Interpretation** | | --- | --- | --- | | `high` | 100+ | Active community, validated demand | | `medium` | 20-100 | Moderate interest, worth exploring | | `low` | <20 | Niche or emerging topic | h3. [Trend Detection](#trend-detection) The tool analyzes recency of activity to determine trend: | **Trend** | **Meaning** | | --- | --- | | `growing` | Recent activity exceeds older activity by 50%+ | | `stable` | Consistent activity over time | | `declining` | Recent activity is 50%+ lower than older activity | Trend is calculated from: - GitHub: Last commit dates on top repos - Reddit: Post creation dates on top threads h3. [Use Cases](#use-cases) 1. **Validate product ideas** - Check if people are actively discussing and building in your niche 2. **Find content opportunities** - Discover active subreddits and trending GitHub repos to reference 3. **Competitor research** - See what's popular and well-received in your space 4. **Timing decisions** - Growing trends suggest good timing; declining may indicate saturation h3. [Limitations](#limitations) - GitHub search returns max 1000 results; `repos` count reflects actual total - Reddit results limited to 25 threads per search - Sentiment analysis is based on upvote ratios, not NLP - Twitter data is placeholder pending API integration h3. [Data Freshness](#data-freshness) | **Platform** | **Cache Duration** | **Data Source** | | --- | --- | --- | | GitHub | 1 hour | GitHub Search API | | Reddit | 1 hour | Reddit public JSON API | | Twitter | N/A | Coming soon | [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/5.social-signals.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/social-signals/docs/nuxt-seo-pro/mcp/social-signals.md) **Did this page help you? ** [**Content Prompts** Pre-built MCP prompts for SEO content creation. Generate article outlines, write full articles, and improve page SEO—through any MCP client.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/social-signals/docs/nuxt-seo-pro/mcp/prompts) [**Domain Availability** MCP tool for checking domain name availability. Research product names, verify domain status, and find registration details for taken domains.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/social-signals/docs/nuxt-seo-pro/mcp/domain-tools) --- ### Claude Code Plugin · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/ai/claude-code-plugin Description: SEO-focused writing skills for Claude Code. Research keywords, write docs, and avoid AI slop. **Ai** h1. **Claude Code Plugin** [Copy for LLMs Claude Code plugins for SEO content writing and documentation. h2. [Installation](#installation) ``` /plugin marketplace add nuxt-seo-pro/claude-plugins /plugin install nuxtseo-content ``` h2. [Requirements](#requirements) Requires [**Nuxt SEO Pro MCP**](https://nuxtseo.com/docs/nuxt-seo-pro/ai/claude-code-plugin/docs/nuxt-seo-pro/mcp/installation) — API key starts with `nsp_`. Some skills use site-specific tools (`get_sitemap_urls`, `analyze_content_page`). Run `init_site` first: ``` Set up https://mysite.com for SEO analysis ``` h2. [Available Skills](#available-skills) h3. [site-setup](#site-setup) Initialize site context for all writing skills. Extracts site config, fetches pages, analyzes existing content for style patterns. **Triggers:** "setup site", "configure site", "initialize project" **Generates:** - `.claude/context/site-config.md` — URL, name, industry, audience, competitors - `.claude/context/site-pages.md` — Available pages from llms.txt/sitemap - `.claude/context/writing-style.md` — Per-category voice, structure, terminology h3. [content-writing](#content-writing) Unified content creation. Detects content type and loads appropriate patterns. **Triggers:** "write docs", "write article", "landing page", "comparison post", "blog post", "tutorial" **Content Types:** | **Type** | **Trigger** | | --- | --- | | docs | "write docs", "API reference" | | educational | "write article", "blog post", "tutorial" | | landing | "landing page", "hero section" | | comparison | "X vs Y", "alternatives to" | | sales | "sales page", "pricing page" | h3. [research](#research) Unified research for keyword discovery, market validation, and competitive analysis. **Triggers:** "keyword research", "market research", "competitor analysis", "validate product idea" **Research Types:** | **Type** | **Trigger** | | --- | --- | | content | "what to write", "content gaps", "topic ideas" | | market | "is there demand", "validate idea", "should I build" | | competitor | "competitor analysis", "who ranks for" | h3. [content-audit](#content-audit) Content review and improvement. Detects content type and applies appropriate audit patterns. **Triggers:** "audit content", "fix links", "check SEO", "improve content", "test sales page" **Audit Types:** | **Type** | **Trigger** | | --- | --- | | style | "check voice", "tone consistency" | | linking | "fix links", "internal linking" | | seo | "check SEO", "keyword optimization" | | geo | "AI optimization", "ChatGPT visibility" | | conversion | "test sales page", "objection handling" | h2. [Usage](#usage) ``` h1. First time setup "setup site" h1. Research before writing "research keywords for meta tags in nuxt" h1. Validate a product idea "research demand for a vue component library" h1. Write documentation "write docs for the sitemap module" h1. Write educational content "write an article about dynamic OG images" h1. Write comparison content "write nuxt vs next comparison" h1. Write landing page copy "write hero section for my SEO tool" h1. Audit existing content "audit /docs/getting-started for SEO" ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/4.ai/claude-code-plugin.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/ai/claude-code-plugin/docs/nuxt-seo-pro/ai/claude-code-plugin.md) **Did this page help you? ** [**Traffic Analysis** MCP tools for estimating domain traffic and understanding competitor performance. Size competitors, benchmark against market leaders, and identify top-performing content.](https://nuxtseo.com/docs/nuxt-seo-pro/ai/claude-code-plugin/docs/nuxt-seo-pro/mcp/traffic-analysis) **On this page** - [Installation](#installation) - [Requirements](#requirements) - [Available Skills](#available-skills) - [Usage](#usage) --- ### Traffic Analysis · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp/traffic-analysis Description: MCP tools for estimating domain traffic and understanding competitor performance. Size competitors, benchmark against market leaders, and identify top-performing content. **Mcp** h1. **Traffic Analysis** [Copy for LLMs Estimate organic search traffic for any domain through your AI assistant. Understand competitor performance, identify top-performing pages, and benchmark against market leaders. Powered by DataForSEO. Requires a Pro API key. h2. [estimate_domain_traffic](#estimate_domain_traffic) Estimate monthly organic search traffic for a domain. Aggregates traffic estimates from all ranked keywords. | **Parameter** | **Type** | **Required** | **Description** | | --- | --- | --- | --- | | `domain` | `string` | Yes | Domain to analyze (without https://) | ``` estimate_domain_traffic({ domain: 'competitor.com' }) ``` Returns: ``` { "domain": "competitor.com", "traffic": { "monthly": 45000, "trend": "growing", "changePercent": 12 }, "topPages": [ { "path": "/docs/getting-started", "traffic": 8500, "keywords": 34 }, { "path": "/blog/tutorial", "traffic": 3200, "keywords": 18 } ], "topCountries": [ { "country": "US", "percent": 100 } ], "domainRank": 62 } ``` h3. [Response Fields](#response-fields) | **Field** | **Description** | | --- | --- | | `traffic.monthly` | Estimated monthly organic visits from Google | | `traffic.trend` | Direction over last 3 months: `growing`, `stable`, or `declining` | | `traffic.changePercent` | Percentage change (positive = growth, negative = decline) | | `topPages` | Pages driving the most traffic with keyword counts | | `topCountries` | Geographic distribution (currently US data only) | | `domainRank` | Domain authority score (0-100), `null` if insufficient data | h3. [Use Cases](#use-cases) **Competitor sizing** - Understand how much traffic competitors receive before entering a market. **Benchmark analysis** - Compare your traffic against industry leaders to set realistic goals. **Content strategy** - Identify which pages drive traffic for competitors, then create better versions. **Market research** - Estimate total addressable organic traffic in a niche by analyzing top players. h3. [Combining with Other Tools](#combining-with-other-tools) For deeper competitor analysis: 1. Run `estimate_domain_traffic` to get traffic overview 2. Use `check_rankings` to see specific keywords they rank for 3. Use `research_keywords` to find related opportunities 4. Use `analyze_serp` to understand competition difficulty h2. [Data Freshness](#data-freshness) | **Tool** | **Cache Duration** | **Data Source** | | --- | --- | --- | | `estimate_domain_traffic` | 24 hours | DataForSEO keyword ranking database | Results are cached to reduce API costs and improve response time. h2. [Limitations](#limitations) - **US traffic only** - Geographic data reflects US Google search traffic. International traffic is not included. - **Organic search only** - Does not include paid, social, direct, or referral traffic. - **Estimates, not actuals** - Traffic is estimated from keyword rankings and click-through rate models. Actual traffic may vary. - **New domains** - Recently launched domains may show incomplete data until they accumulate ranking history. - **Trend accuracy** - Trend direction requires sufficient monthly search volume data. Low-volume keywords may show as `stable` even if changing. [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/7.traffic-analysis.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/traffic-analysis/docs/nuxt-seo-pro/mcp/traffic-analysis.md) **Did this page help you? ** [**Domain Availability** MCP tool for checking domain name availability. Research product names, verify domain status, and find registration details for taken domains.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/traffic-analysis/docs/nuxt-seo-pro/mcp/domain-tools) [**Claude Code Plugin** SEO-focused writing skills for Claude Code. Research keywords, write docs, and avoid AI slop.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/traffic-analysis/docs/nuxt-seo-pro/ai/claude-code-plugin) **On this page** - [estimate_domain_traffic](#estimate_domain_traffic) - [Data Freshness](#data-freshness) - [Limitations](#limitations) --- ### Domain Availability · Nuxt Nuxt SEO Pro · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo-pro/mcp/domain-tools Description: MCP tool for checking domain name availability. Research product names, verify domain status, and find registration details for taken domains. **Mcp** h1. **Domain Availability** [Copy for LLMs Check domain availability through your AI assistant. Validate product names, compare domain options, and get registration details for taken domains. Powered by DataForSEO WHOIS database. Requires a Pro API key. h2. [check_domain_availability](#check_domain_availability) Check availability status for up to 10 domains at once. Returns registration status, registrar, and expiration dates for taken domains. | **Parameter** | **Type** | **Required** | **Description** | | --- | --- | --- | --- | | `domains` | `string[]` | Yes | List of domains to check (max 10) | ``` check_domain_availability({ domains: ['coolapp.dev', 'coolapp.io', 'mycoolapp.com'] }) ``` Returns: ``` { "results": [ { "domain": "coolapp.dev", "available": true }, { "domain": "coolapp.io", "available": false, "registrar": "Cloudflare, Inc.", "expiration": "2026-03-15", "created": "2019-08-22" }, { "domain": "mycoolapp.com", "available": true, "note": "Not found in WHOIS database - likely available" } ] } ``` h3. [Response Fields](#response-fields) | **Field** | **Description** | | --- | --- | | `domain` | The domain checked | | `available` | `true` if available for registration, `false` if taken | | `registrar` | Registrar name (taken domains only) | | `expiration` | Expiration date YYYY-MM-DD (taken domains only) | | `created` | Registration date YYYY-MM-DD (taken domains only) | | `note` | Additional context when applicable | h3. [Use Cases](#use-cases) **Product naming** - Quickly validate domain availability when brainstorming product or company names. **Brand research** - Check multiple TLD variations at once (.com, .io, .dev, .app) to find available options. **Expiration tracking** - See when a desired domain expires; it may become available for registration. **Competitive research** - Check when competitor domains were registered to understand market timing. h3. [Combining with Other Tools](#combining-with-other-tools) For comprehensive brand research: 1. Run `check_domain_availability` to verify domain options 2. Use `research_keywords` to check search volume for brand terms 3. Use `analyze_social_signals` to see if the name is already associated with other projects h2. [Data Freshness](#data-freshness) | **Tool** | **Cache Duration** | **Data Source** | | --- | --- | --- | | `check_domain_availability` | 1 hour | DataForSEO WHOIS database | Results are cached to reduce API costs and improve response time. h2. [Limitations](#limitations) - **Database coverage** - DataForSEO tracks 260M+ domains but may miss very new registrations or obscure TLDs. Domains not found are reported as likely available. - **Pricing not included** - Domain pricing varies by registrar and is not provided. Check your preferred registrar for current prices. - **No reservation** - Checking availability does not reserve the domain. Register promptly if needed. - **Expiration dates** - An expired domain may not be immediately available due to redemption periods. [Edit this page](https://github.com/nuxt-seo-pro/nuxtseo.com/edit/main/docs/content/3.mcp/6.domain-tools.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/domain-tools/docs/nuxt-seo-pro/mcp/domain-tools.md) **Did this page help you? ** [**Social Signals** MCP tool for analyzing social proof and community activity. Validate market demand across GitHub, Reddit, and Twitter before building.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/domain-tools/docs/nuxt-seo-pro/mcp/social-signals) [**Traffic Analysis** MCP tools for estimating domain traffic and understanding competitor performance. Size competitors, benchmark against market leaders, and identify top-performing content.](https://nuxtseo.com/docs/nuxt-seo-pro/mcp/domain-tools/docs/nuxt-seo-pro/mcp/traffic-analysis) **On this page** - [check_domain_availability](#check_domain_availability) - [Data Freshness](#data-freshness) - [Limitations](#limitations) --- ### useSiteConfig() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/api/use-site-config Description: How to access site config within a Nuxt context. **Nuxt API** h1. **useSiteConfig()** Last updated **Sep 5, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: sync with nuxt-seo](https://github.com/harlan-zw/nuxt-site-config/commit/bf2957a8103bc874726b2b20ac87023e178ba571). [Copy for LLMs Access the current site config within a Nuxt context. h2. [Usage](#usage) component.vue ``` ``` h2. [API](#api) h3. [debug](#debug) - Type: `boolean` - Default: `false` Will provide a `_context` object that can be used to track the source of what is setting the site config. ``` export default defineNuxtConfig({ site: { name: 'My Site', }, }) ``` ``` const siteConfig = useSiteConfig({ debug: true }) console.log(siteConfig.name, siteConfig._context.name) // My Site, 'nuxt.config.ts' ``` h3. [`resolveRefs`](#resolverefs) - Type: `boolean` - Default: `false` Should any ref values within the site config be resolved when returned. h3. [`skipNormalize`](#skipnormalize) - Type: `boolean` - Default: `false` Skips the normalizing of the site config, will return it in a raw format in how it was provided. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.api/0.use-site-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/api/use-site-config/docs/site-config/api/use-site-config.md) **Did this page help you? ** [**Multi-Tenancy** Learn how to serve multiple sites with different configurations based on the hostname.](https://nuxtseo.com/docs/site-config/api/use-site-config/docs/site-config/guides/multi-tenancy) [**updateSiteConfig()** Learn how to modify site config at runtime.](https://nuxtseo.com/docs/site-config/api/use-site-config/docs/site-config/api/update-site-config) **On this page** - [Usage](#usage) - [API](#api) --- ### v3.0.0 · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/releases/v3 Description: Release notes for v3.0.0. **Releases** h1. **v3.0.0** Last updated **Nov 24, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: lint](https://github.com/harlan-zw/nuxt-site-config/commit/676068f3502d28ec91c24f44d06983698ff4dab2). [Copy for LLMs h2. [⚠️ Breaking Changes](#️-breaking-changes) h3. [App Config Support Removed](#app-config-support-removed) If you were configuring your site config using the [**app.config.ts**](https://nuxt.com/docs/guide/directory-structure/app-config) file, you will need to move your configuration to the `nuxt.config.ts` file or set up [**runtime site config**](https://nuxtseo.com/docs/site-config/releases/v3/docs/site-config/guides/runtime-site-config). nuxt.config ``` export default defineNuxtConfig({ site: { url: 'https://example.com', name: 'My Website', } }) ``` h3. [Deprecated `nuxt-site-config-kit` and `site-config-stack`](#deprecated-nuxt-site-config-kit-and-site-config-stack) The `nuxt-site-config-kit` and `site-config-stack` packages should no longer be used or depended on directly. Instead, the subpath exports should be used. ``` -import {} from 'nuxt-site-config-kit' +import {} from 'nuxt-site-config/kit' -import {} from 'site-config-stack' +import {} from 'nuxt-site-config/utils' ``` h3. [Removed `` component](#removed-sitelink-component) The `` component has been removed, please use your own [**Custom Link Component**](https://nuxt.com/docs/api/components/nuxt-link#custom-link-component). h3. [Removed `assertSiteConfig` function](#removed-assertsiteconfig-function) The `assertSiteConfig` function has been removed, please validate site config using your own methods. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.releases/5.v3.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/releases/v3/docs/site-config/releases/v3.md) **Did this page help you? ** [**Nitro Hooks** Learn how to use Nitro Hooks to customize your site config.](https://nuxtseo.com/docs/site-config/releases/v3/docs/site-config/nitro-api/nitro-hooks) --- ### Nuxt I18n · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/guides/i18n Description: How to use the Nuxt Site Config module with Nuxt I18n. **Core Concepts** h1. **Nuxt I18n** Last updated **Mar 3, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix: `@nuxtjs/i18n` v8 regression Fixes #43](https://github.com/harlan-zw/nuxt-site-config/commit/02157951dc58295f4387860a63e0aa1b7f948ae8). [Copy for LLMs Out of the box, the Site Config module will integrate directly with [**@nuxtjs/i18n**](https://i18n.nuxtjs.org/). h2. [Usage](#usage) By default, it will extract the following properties from the i18n module config. - `url` - The base URL, configured as `baseUrl` in the i18n module. - `currentLocale` - The current locale for the request. This will use the `defaultLocale` if no locale is set. For example, consider the following config: nuxt.config.ts ``` export default defineNuxtConfig({ i18n: { baseUrl: 'https://example.com', defaultLocale: 'en', locales: [ { code: 'en', language: 'en-US' }, { code: 'fr', language: 'fr-FR' }, ], }, }) ``` The following site config will be inferred: ``` { "url": "https://example.com", "currentLocale": "en" } ``` Additionally, it will detect if you have a `nuxtSiteConfig` translation object and use the following properties: - `name` - Name of the site - `description` - Description of the site For example: ``` export default { nuxtSiteConfig: { name: 'My Site', description: 'My site description', } } ``` ``` { "nuxtSiteConfig": { "name": "My Site", "description": "My site description" } } ``` The following site config will be inferred for an English request: ``` { "url": "https://example.com", "currentLocale": "en", "name": "My Site", "description": "My site description" } ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/2.guides/3.i18n.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/guides/i18n/docs/site-config/guides/i18n.md) **Did this page help you? ** [**How it works** Learn how the Nuxt Site Config module works, so you can get the most out of it.](https://nuxtseo.com/docs/site-config/guides/i18n/docs/site-config/guides/how-it-works) [**Runtime Site Config** Learn how to set site config at runtime in your Nuxt app.](https://nuxtseo.com/docs/site-config/guides/i18n/docs/site-config/guides/runtime-site-config) --- ### Troubleshooting Nuxt Site Config · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/getting-started/troubleshooting Description: Debug Nuxt Site Config issues using DevTools, config options, and minimal reproductions. **Getting Started** h1. **Troubleshooting Nuxt Site Config** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix broken links, improve descriptions, and add tool links (#72) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/72). [Copy for LLMs h2. [Debugging](#debugging) h3. [Nuxt DevTools](#nuxt-devtools) The best tool for debugging is the Nuxt DevTools integration with Nuxt Site Config. This will show you the current site config. h3. [Debug Config](#debug-config) Nuxt Site Config comes with a Nuxt DevTools integration. The easiest way to debug is to open your DevTools and navigate to the Site Config tab. If you'd like to debug outside of development, you will need to enable the debug mode. nuxt.config.ts ``` export default defineNuxtConfig({ site: { debug: true, }, }) ``` h3. [Debugging Runtime](#debugging-runtime) Visit the endpoint `/__site-config__/debug.json` to see the current site config debug output. h3. [Debugging Build Time](#debugging-build-time) A static file `/__site-config__/debug.json` is generated at build time. You can view this file to see the build time site config debug output. h2. [Submitting an Issue](#submitting-an-issue) When submitting an issue, it's important to provide as much information as possible. The easiest way to do this is to create a minimal reproduction using the Stackblitz playgrounds: - [**Basic**](https://stackblitz.com/edit/nuxt-starter-zycxux?file=public%2F_robots.txt) h2. [Debugging Tools](#debugging-tools) - [**Meta Tag Checker**](https://nuxtseo.com/docs/site-config/getting-started/troubleshooting/tools/meta-tag-checker) - Verify site URL appears correctly in meta tags [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/0.getting-started/3.troubleshooting.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/getting-started/troubleshooting/docs/site-config/getting-started/troubleshooting.md) **Did this page help you? ** [**Installation** Get started with Nuxt Site Config by installing the dependency to your project.](https://nuxtseo.com/docs/site-config/getting-started/troubleshooting/docs/site-config/getting-started/installation) [**Recommended Config** Learn how to set site config in your Nuxt app.](https://nuxtseo.com/docs/site-config/getting-started/troubleshooting/docs/site-config/guides/setting-site-config) **On this page** - [Debugging](#debugging) - [Submitting an Issue](#submitting-an-issue) - [Debugging Tools](#debugging-tools) --- ### Runtime Site Config · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/guides/runtime-site-config Description: Learn how to set site config at runtime in your Nuxt app. **Core Concepts** h1. **Runtime Site Config** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply linking opportunities (#73) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/73). [Copy for LLMs Site config can be set from many different sources from each environment. The most flexible way to set your site config is at runtime. This gives you the ability to set it based on any condition you want, even allowing [**multi-tenancy**](https://nuxtseo.com/docs/site-config/guides/runtime-site-config/docs/site-config/guides/multi-tenancy). h2. [Caveats](#caveats) When setting site config at runtime, it's important to set it as early as possible on the server. This is because modules will be using site config to generate your app, for example if you're using Nuxt SEO, site config is used for `sitemap.xml` and `robots.txt` generation. Therefore, it's recommended to set it either within a Nitro plugin or middleware. h2. [Plugin](#plugin) A Nitro plugin is useful when you want to set site config based on a static condition, such as the environment or based on a result from a database. Because Site Config is attached to a H3 Request context, you will need to use the `site-config:init` hook to set it. For example, here we are setting site config based on a database query: server/plugins/update-site-config-from-db.ts ``` export default defineNitroPlugin(async (nitroApp) => { const site = await $fetch('/db/site-config', { params: { env: import.meta.env.NUXT_SITE_ENV }, }) nitroApp.hooks.hook('site-config:init', ({ event, siteConfig }) => { siteConfig.push(site) }) }) ``` h2. [Middleware](#middleware) If you prefer a simpler API, you can use a Nitro middleware instead with `updateSiteConfig` function. For example, here we are setting site config based on an admin host: server/middleware/update-site-config.ts ``` import { getNitroOrigin, updateSiteConfig } from '#site-config/server/composables' export default defineEventHandler((e) => { const host = getNitroOrigin(e) if (host.startsWith('https://admin.')) { updateSiteConfig(e, { name: 'Admin', indexable: false, url: 'https://admin.example.com' }) } }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/2.guides/3.runtime-site-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/guides/runtime-site-config/docs/site-config/guides/runtime-site-config.md) **Did this page help you? ** [**Nuxt I18n** How to use the Nuxt Site Config module with Nuxt I18n.](https://nuxtseo.com/docs/site-config/guides/runtime-site-config/docs/site-config/guides/i18n) [**Multi-Tenancy** Learn how to serve multiple sites with different configurations based on the hostname.](https://nuxtseo.com/docs/site-config/guides/runtime-site-config/docs/site-config/guides/multi-tenancy) **On this page** - [Caveats](#caveats) - [Plugin](#plugin) - [Middleware](#middleware) --- ### createSitePathResolver() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/api/create-site-path-resolver Description: Create a function to resolve a path relative to the site. **Nuxt API** h1. **createSitePathResolver()** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix broken links, improve descriptions, and add tool links (#72) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/72). [Copy for LLMs A utility function to resolve a path in a number of ways while taking into account the `url`, `trailingSlash` and `baseURL` config. h2. [Usage](#usage) ``` import { createSitePathResolver } from '#imports' const resolvePath = createSitePathResolver({ canonical: true, }) resolvePath('/about') // https://www.example.com/about ``` h2. [API](#api) h3. [`canonical`](#canonical) - Type: `boolean` - Default: `true` Should the path be resolved to the canonical URL using the site config `url`. When false, it will resolve to the request host using [**getNitroOrigin**](https://nuxtseo.com/docs/site-config/api/create-site-path-resolver/docs/site-config/api/get-nitro-origin). h3. [`absolute`](#absolute) - Type: `boolean` - Default: `false` Should the path be resolved to an absolute URL. h3. [`withBase`](#withbase) - Type: `boolean` - Default: `false` Should the path include the base URL from the Nuxt app config. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.api/4.create-site-path-resolver.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/api/create-site-path-resolver/docs/site-config/api/create-site-path-resolver.md) **Did this page help you? ** [**updateSiteConfig()** Learn how to modify site config at runtime.](https://nuxtseo.com/docs/site-config/api/create-site-path-resolver/docs/site-config/api/update-site-config) [**getNitroOrigin()** Get the runtime origin URL safely across development, prerendering, and production environments.](https://nuxtseo.com/docs/site-config/api/create-site-path-resolver/docs/site-config/api/get-nitro-origin) **On this page** - [Usage](#usage) - [API](#api) --- ### How it works · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/guides/how-it-works Description: Learn how the Nuxt Site Config module works, so you can get the most out of it. **Core Concepts** h1. **How it works** Last updated **Nov 24, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: drop support for app.config (#35)](https://github.com/harlan-zw/nuxt-site-config/pull/35). [Copy for LLMs h1. **Config Sources** Site config is resolved from the following sources, in order of precedence: h2. [Build Time](#build-time) h3. [1. System](#_1-system) System details relate to the environment the app is running in. ``` export default { name: rootDirBaseName, // e.g. '/home/harlan/projects/my-nuxt-app' -> 'my-nuxt-app' indexable: process.env.NODE_ENV === 'production' } ``` h3. [2. Package.json](#_2-packagejson) Site config provided by the package.json file. ``` export default { name: pkgJson.name, description: pkgJson.description, } ``` h3. [3. Vendor Environment Variables](#_3-vendor-environment-variables) Environment variables provided by the hosting providers. ``` export default { url: [ // vercel, netlify process.env.NUXT_ENV_VERCEL_URL, process.env.URL, // cloudflare pages process.env.CF_PAGES_URL, ], // vercel, netlify name: [process.env.NUXT_ENV_VERCEL_GIT_REPO_SLUG, process.env.SITE_NAME] } ``` h3. [4. Module overrides](#_4-module-overrides) Build time site config provided by modules. h3. [5. Nuxt Config `site` key](#_5-nuxt-config-site-key) Site config provided by the user in the Nuxt config. h3. [6. Runtime Config and Environment Variables](#_6-runtime-config-and-environment-variables) Site config provided by the user at runtime. `runtimeConfig.public.site` and associated environment variables. This will also attempt to use legacy environment variables from Nuxt: - Private / public runtime config camel cased `siteUrl`, `public.siteUrl` - Environment variables with and without `PUBLIC`: `NUXT_ENV_SITE_URL`, `NUXT_ENV_PUBLIC_SITE_URL` h3. [7. Nuxt Hook](#_7-nuxt-hook) The `site-config:resolve` hook is called to allow any final build-time modifications to the config. h2. [SSR / Nitro Runtime](#ssr-nitro-runtime) h3. [1. Request URL](#_1-request-url) The request URL is used to determine the site URL at runtime. h3. [2. Build Time Site Config](#_2-build-time-site-config) Config resolved in the build step. This is stored on the `runtimeConfig.public.site` key. h3. [3. Route Rules](#_3-route-rules) Config resolved from the route rules of the request path, the `site` key. This allows for multi-site support. h2. [CSR Runtime](#csr-runtime) h3. [1. Browser Context](#_1-browser-context) Uses the site URL from the `window.location.origin`. h3. [2. SSR Runtime Config](#_2-ssr-runtime-config) The SSR runtime config created in the SSR step. This is sent to the client-side to avoid hydration errors. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/2.guides/3.how-it-works.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/guides/how-it-works/docs/site-config/guides/how-it-works.md) **Did this page help you? ** [**Recommended Config** Learn how to set site config in your Nuxt app.](https://nuxtseo.com/docs/site-config/guides/how-it-works/docs/site-config/guides/setting-site-config) [**Nuxt I18n** How to use the Nuxt Site Config module with Nuxt I18n.](https://nuxtseo.com/docs/site-config/guides/how-it-works/docs/site-config/guides/i18n) **On this page** - [Build Time](#build-time) - [SSR / Nitro Runtime](#ssr-nitro-runtime) - [CSR Runtime](#csr-runtime) --- ### Multi-Tenancy · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/guides/multi-tenancy Description: Learn how to serve multiple sites with different configurations based on the hostname. **Core Concepts** h1. **Multi-Tenancy** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: apply linking opportunities (#73) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/73). [Copy for LLMs h2. [Introduction](#introduction) Multi-tenancy allows you to serve multiple sites with different configurations using a single Nuxt application. This is useful when you need to: - Host multiple domains with different branding - Serve region-specific content - Maintain multiple sites with shared codebase You can configure multi-tenancy using static configuration or dynamically at [**runtime**](https://nuxtseo.com/docs/site-config/guides/multi-tenancy/docs/site-config/guides/runtime-site-config). h2. [Usage](#usage) To use build-time multi-tenancy configuration you can use the `multiTenancy` option: ``` export default defineNuxtConfig({ site: { multiTenancy: [ { hosts: ['www.example.com', 'example.com', 'local.example.com'], config: { name: 'Example', url: 'example.com' // canonical }, }, { hosts: ['www.foo.com', 'foo.com', 'local.foo.com'], config: { name: 'Foo', url: 'foo.com', // canonical description: 'Foo description', }, }, ] } }) ``` Each multi-tenant configuration requires: - `hosts`: An array of hostnames that should use this configuration It's recommended to add any non-canonical hostnames to the `hosts` array to avoid duplicate content issues. For example, [**www**](http://www).* and non- [**www.***](http://www.*) hostnames should use the same configuration. - `config`: The site configuration to apply when the hostname matches The config object supports all standard site configuration options plus any custom properties you need. h2. [How it works](#how-it-works) 1. When a request is received, the module checks the hostname against the configured hosts arrays 2. If a match is found, the corresponding config is applied 3. The configuration is made available through the `useSiteConfig()` composable h2. [Runtime Multi-Tenancy](#runtime-multi-tenancy) Runtime multi-tenancy is useful when you need to: - Set configuration based on complex runtime conditions - Load configuration from external sources - Handle dynamic subdomains - Implement A/B testing scenarios To use get started with runtime multi-tenancy you'll need to create a Nitro plugin. server/plugins/site-config.ts ``` import { getNitroOrigin } from '#site-config/server/composables' export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('site-config:init', ({ event, siteConfig }) => { const origin = getNitroOrigin(event) // Example: Set configuration based on subdomain if (origin.startsWith('fr.')) { // push whatever config you'd like for this request siteConfig.push({ name: 'Mon Site', url: 'https://fr.example.com', defaultLocale: 'fr', currentLocale: 'fr', }) } }) }) ``` Check the [`site-config:init`](https://nuxtseo.com/docs/site-config/guides/multi-tenancy/docs/site-config/nitro-api/nitro-hooks#site-config-init) hook documentation for more information. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/2.guides/multi-tenancy.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/guides/multi-tenancy/docs/site-config/guides/multi-tenancy.md) **Did this page help you? ** [**Runtime Site Config** Learn how to set site config at runtime in your Nuxt app.](https://nuxtseo.com/docs/site-config/guides/multi-tenancy/docs/site-config/guides/runtime-site-config) [**useSiteConfig()** How to access site config within a Nuxt context.](https://nuxtseo.com/docs/site-config/guides/multi-tenancy/docs/site-config/api/use-site-config) **On this page** - [Introduction](#introduction) - [Usage](#usage) - [How it works](#how-it-works) - [Runtime Multi-Tenancy](#runtime-multi-tenancy) --- ### getNitroOrigin() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/api/get-nitro-origin Description: Get the runtime origin URL safely across development, prerendering, and production environments. **Nuxt API** h1. **getNitroOrigin()** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix broken links, improve descriptions, and add tool links (#72) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/72). [Copy for LLMs A utility function to get the Nitro origin from the request headers. This is a replacement for `useRequestOrigin()` which has edge-cases issues in development, prerendering and in some runtime environments. The nitro origin acts as the canonical origin for the site when a `url` has not been provided. h2. [Usage](#usage) ``` import { getNitroOrigin } from '#imports' const origin = getNitroOrigin() // https://www.example.com ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.api/5.get-nitro-origin.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/api/get-nitro-origin/docs/site-config/api/get-nitro-origin.md) **Did this page help you? ** [**createSitePathResolver()** Create a function to resolve a path relative to the site.](https://nuxtseo.com/docs/site-config/api/get-nitro-origin/docs/site-config/api/create-site-path-resolver) [**Nuxt Config** The config options available for Nuxt Site Config.](https://nuxtseo.com/docs/site-config/api/get-nitro-origin/docs/site-config/api/config) --- ### Optimizing Vue Content for AI Search · Nuxt SEO Source: https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content Description: Make your Vue site visible in AI Overviews, ChatGPT, and other generative search engines with structured data and content strategies. h1. **Optimizing Vue Content for AI Search** Make your Vue site visible in AI Overviews, ChatGPT, and other generative search engines with structured data and content strategies. [![Harlan Wilton](https://avatars.githubusercontent.com/u/5326365?v=4)Harlan Wilton](https://x.com/harlan_zw)10 mins read Published **Dec 17, 2025** AI search engines. ChatGPT, Google AI Overviews, Perplexity, Gemini. synthesize answers instead of listing links. Getting cited means appearing in their responses, not ranking on page one. Generative Engine Optimization (GEO) is the practice of making your content citable by AI. [**Princeton researchers coined the term**](https://arxiv.org/abs/2311.09735) in November 2023, and by 2025 it's standard practice alongside traditional SEO. h2. [Why GEO Matters](#why-geo-matters) Traditional search shows 10 blue links. AI search shows one synthesized answer citing 2-7 sources ([**Backlinko research**](https://backlinko.com/generative-engine-optimization-geo)). If you're not in those citations, you're invisible. The numbers are compelling: - Over 1 billion prompts sent to ChatGPT daily - [**71% of Americans**](https://www.tryprofound.com/guides/generative-engine-optimization-geo-guide-2025) use AI search for purchase research - [**Semrush predicts**](https://www.semrush.com/blog/google-ai-overview/) LLM traffic will overtake traditional Google search by 2027 For Vue developers: if your content isn't structured for AI extraction, you lose traffic to competitors who optimize for both. h2. [GEO vs SEO](#geo-vs-seo) GEO doesn't replace SEO. it extends it. AI engines like Google's Gemini (powering AI Overviews) still use ranking signals. [**80% of AI Overview citations**](https://www.theedigital.com/blog/how-to-rank-in-ai-overviews) come from pages already ranking in the top 3. | **Traditional SEO** | **Generative Engine Optimization** | | --- | --- | | Optimize for 10 blue links | Optimize for 2-7 citations | | Backlinks build authority | Third-party mentions build authority | | SERP snippets drive clicks | AI summaries drive (fewer) clicks | | Keyword targeting | Semantic topic coverage | | CTR matters | Citation rate matters | **Start with SEO fundamentals.** If your Vue site isn't crawlable and indexable, AI engines can't cite it either. h2. [What AI Engines Actually Cite](#what-ai-engines-actually-cite) Each AI platform favors different sources: | **Platform** | **Top Citation Sources** | | --- | --- | | ChatGPT | Wikipedia (47.9%), news sites, academic sources | | Gemini | Reddit, YouTube, official docs | | Perplexity | Reddit, community discussions, recent articles | | Google AI Overviews | Top-ranking organic results, structured data | [**Wikipedia dominates ChatGPT**](https://backlinko.com/generative-engine-optimization-geo) because it provides comprehensive, neutral, well-structured information. Reddit dominates Perplexity because it surfaces real user experiences. **Implications for Vue developers:** Create content that reads like documentation, not marketing. Include real code examples, acknowledge limitations, and cite your sources. h2. [Structured Data for AI](#structured-data-for-ai) Schema.org markup helps AI understand your content. [**Google confirmed at Search Central Live Madrid**](https://www.brightedge.com/blog/structured-data-ai-search-era) that Gemini (powering AI Overviews) uses structured data to understand content context. Pages with structured data are [**40% more likely**](https://greenbananaseo.com/structured-data-ai-ranking/) to appear in AI citations. h3. [Essential Schema Types](#essential-schema-types) For technical content, implement these schemas: ``` // composables/useArticleSchema.ts import { defineArticle, defineWebPage, useSchemaOrg } from '@unhead/schema-org/vue' export function useArticleSchema(article: { headline: string description: string datePublished: string dateModified?: string author: string }) { useSchemaOrg([ defineWebPage({ '@type': 'WebPage', 'name': article.headline, 'description': article.description }), defineArticle({ headline: article.headline, description: article.description, datePublished: article.datePublished, dateModified: article.dateModified || article.datePublished, author: { '@type': 'Person', 'name': article.author } }) ]) } ``` Use it in your Vue pages: ``` ``` h3. [FAQ and HowTo Schema](#faq-and-howto-schema) FAQ schema works well for question-based queries that trigger AI responses: ``` import { defineFAQPage, useSchemaOrg } from '@unhead/schema-org/vue' useSchemaOrg([ defineFAQPage({ mainEntity: [ { '@type': 'Question', 'name': 'How do I add meta tags in Vue?', 'acceptedAnswer': { '@type': 'Answer', 'text': 'Use the useHead composable from @unhead/vue to set meta tags reactively.' } } ] }) ]) ``` Note: Google [**deprecated FAQ rich results**](https://developers.google.com/search/blog/2023/08/howto-faq-changes) for most sites in August 2023, but the structured data still helps AI engines understand Q&A content. h2. [Content Structure for AI Extraction](#content-structure-for-ai-extraction) AI engines parse content differently than humans browse it. Structure your Vue documentation and articles for extraction: h3. [Lead with Summaries](#lead-with-summaries) Put the answer first. AI engines extract from the opening paragraph: ``` ❌ Bad: "In today's web development landscape, meta tags play a crucial role..." ✅ Good: "Use \`useHead()\` from @unhead/vue to set meta tags in Vue 3. It handles SSR, reactivity, and deduplication automatically." ``` h3. [Use Clear Headings](#use-clear-headings) AI engines use H2s and H3s to understand content hierarchy: ``` h2. How to Set Meta Tags in Vue ← Clear question format h3. Using useHead() ← Specific method h3. Dynamic Meta Tags per Route ← Common use case h3. Troubleshooting SSR Issues ← Problem-solving ``` h3. [Include Code Examples](#include-code-examples) Technical AI queries expect code. ChatGPT and Perplexity cite pages with working examples: ``` ``` h3. [Add Quotable Statistics](#add-quotable-statistics) AI engines love citable facts. Include specific numbers with sources: ``` ❌ Vague: "Most sites have SEO issues." ✅ Specific: "67.6% of websites have duplicate content issues (Semrush, 2024)." ``` h2. [Building External Citations](#building-external-citations) AI engines weight third-party mentions heavily. Wikipedia dominates ChatGPT because thousands of external sources cite it. h3. [Get Mentioned on Authoritative Sites](#get-mentioned-on-authoritative-sites) Target sites AI engines trust: - **Wikipedia** . Add citations to relevant articles (follow their guidelines) - **Reddit** . Answer questions in r/vuejs, r/webdev, r/SEO - **Stack Overflow** . Provide detailed answers with links to your documentation - **GitHub** . README files, discussions, and issues get crawled h3. [Create Link-Worthy Content](#create-link-worthy-content) Original research gets cited. Publish: - Benchmarks (Vue 3 vs Vue 2 performance) - Surveys (What tools do Vue developers use?) - Case studies (How we improved LCP by 50%) [**Backlinko saw 800% YoY increase**](https://backlinko.com/generative-engine-optimization-geo) in LLM referrals by creating original research content. h2. [Tracking AI Citations](#tracking-ai-citations) Traditional analytics miss AI traffic. Users get answers without clicking through. h3. [New Metrics to Track](#new-metrics-to-track) | **Metric** | **What It Measures** | **How to Track** | | --- | --- | --- | | AI citation rate | How often you're cited in AI responses | Manual sampling, brand monitoring | | Share of AI voice | % of AI answers mentioning your brand | Tools like Profound, Otterly | | Zero-click visibility | Impressions without clicks | Search Console impression data | h3. [Tools for GEO Monitoring](#tools-for-geo-monitoring) - **[**Profound**](https://www.tryprofound.com/)** . Tracks AI citations across ChatGPT, Perplexity, Gemini - **[**Otterly.ai**](https://www.otterly.ai/)** . Monitors brand mentions in AI responses - **Search Console** . High impressions with low clicks may indicate AI extraction h3. [Manual Citation Checking](#manual-citation-checking) Query AI engines directly with your target keywords: ``` ChatGPT: "How do I add meta tags in Vue?" Perplexity: "Best practices for Vue SEO" Google (AI Overview): "Vue meta tags tutorial" ``` Check if your content appears in citations. Screenshot and track monthly. h2. [Vue-Specific GEO Tips](#vue-specific-geo-tips) h3. [SSR is Non-Negotiable](#ssr-is-non-negotiable) Client-rendered Vue SPAs won't get cited. AI crawlers need server-rendered HTML: ``` // vite.config.ts with vite-plugin-ssr import vue from '@vitejs/plugin-vue' import ssr from 'vite-plugin-ssr/plugin' export default { plugins: [vue(), ssr()] } ``` Or use [**Nuxt**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content/learn-seo/nuxt), [**Quasar SSR**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content/learn-seo/vue/ssr-frameworks/nuxt-vs-quasar), or [**VitePress**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content/learn-seo/vue/ssr-frameworks/vitepress) for built-in SSR. h3. [Implement llms.txt](#implement-llmstxt) The [**llms.txt standard**](https://llmstxt.org/) tells AI crawlers what content to prioritize: ``` h1. llms.txt - placed at /llms.txt h1. Title: Vue SEO Guide h1. Description: Complete guide to SEO for Vue.js applications h2. Documentation - /docs/getting-started: Quick start guide for Vue SEO - /docs/meta-tags: Managing meta tags with useHead - /docs/structured-data: Schema.org implementation h2. Guides - /learn/vue-seo-basics: Vue SEO fundamentals - /learn/ssr-vs-spa: When to use server-side rendering ``` Serve it from your public directory. AI crawlers like GPTBot check for this file. h3. [robots.txt for AI Crawlers](#robotstxt-for-ai-crawlers) Control which AI engines can access your content: ``` h1. robots.txt h1. Allow Google (including AI Overview) User-agent: Googlebot Allow: / h1. Allow ChatGPT User-agent: GPTBot Allow: / h1. Allow Perplexity User-agent: PerplexityBot Allow: / h1. Block specific AI crawlers if needed User-agent: CCBot Disallow: / ``` See the [**robots.txt guide**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content/learn-seo/vue/controlling-crawlers/robots-txt) for full syntax. h2. [Quick Wins Checklist](#quick-wins-checklist) Start with these high-impact changes: - Add Article schema to blog posts and guides - Write TL;DR summaries at the top of long content - Include code examples in technical articles - Add specific statistics with source citations - Create an llms.txt file - Ensure SSR or prerendering for all content pages - Answer questions on Reddit/Stack Overflow linking to your content - Test queries in ChatGPT and Perplexity monthly h2. [Using Nuxt?](#using-nuxt) Nuxt SEO includes automatic schema.org generation, llms.txt support via [**nuxt-llms**](https://github.com/harlan-zw/nuxt-llms), and SSR out of the box. [**Learn more about AI optimization in Nuxt →**](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content/learn-seo/nuxt/launch-and-listen) --- [**Debugging** Fix meta tags not rendering, hydration mismatches, and indexing problems. Chrome DevTools workflow and GSC URL Inspection guide.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content/learn-seo/vue/launch-and-listen/debugging) [**SEO Checklist** Complete checklist for Vue SEO. pre-launch setup, post-launch verification, and ongoing monitoring. Links to detailed guides for every step.](https://nuxtseo.com/learn-seo/vue/launch-and-listen/ai-optimized-content/learn-seo/vue/launch-and-listen/checklist) **On this page** - [Why GEO Matters](#why-geo-matters) - [GEO vs SEO](#geo-vs-seo) - [What AI Engines Actually Cite](#what-ai-engines-actually-cite) - [Structured Data for AI](#structured-data-for-ai) - [Content Structure for AI Extraction](#content-structure-for-ai-extraction) - [Building External Citations](#building-external-citations) - [Tracking AI Citations](#tracking-ai-citations) - [Vue-Specific GEO Tips](#vue-specific-geo-tips) - [Quick Wins Checklist](#quick-wins-checklist) - [Using Nuxt?](#using-nuxt) --- ### Nuxt Config · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/api/config Description: The config options available for Nuxt Site Config. **Nuxt API** h1. **Nuxt Config** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: broken formatting](https://github.com/harlan-zw/nuxt-site-config/commit/4524b529c518bf45adcdfc6628b7931f0babed2a). [Copy for LLMs h2. [`enabled`](#enabled) - Type: `boolean` - Default: `true` Whether the site config is enabled. h2. [`debug`](#debug) - Type: `boolean` - Default: `false` Whether the debug mode of the site config is enabled. h2. [`multiTenancy`](#multitenancy) - Type: `{ hosts: string[]; config: SiteConfigInput }[]` - Default: `[]` Configure multiple sites with different configurations based on the host. Each site configuration requires: - `hosts`: An array of hostnames that should use this configuration - `config`: The site configuration to use when the hostname matches Example ``` export default defineNuxtConfig({ site: { multiTenancy: [ { hosts: ['www.example.com', 'example.com', 'local.example.com'], config: { name: 'Example', description: 'Example description', url: 'example.com', defaultLocale: 'en', currentLocale: 'en', }, }, { hosts: ['www.foo.com', 'foo.com', 'local.foo.com'], config: { url: 'foo.com', name: 'Foo', description: 'Foo description', }, }, ] } }) ``` h2. [`url`](#url) - Type: `string` The canonical site URL. h2. [`env`](#env) - Type: `string` - Default: `process.env.NODE_ENV` The environment the site is running in. See [**this issue**](https://github.com/nuxt/nuxt/issues/19819) on why we can't use `process.env.NODE_ENV`. h2. [`name`](#name) - Type: `string` The name of the site. h2. [`indexable`](#indexable) - Type: `boolean` - Default: `siteConfig.env === 'production' || process.env.NODE_ENV === 'production'` Can the site be indexed by search engines. h2. [`trailingSlash`](#trailingslash) - Type: `boolean` - Default: `false` Whether to add trailing slashes to the URLs. h2. [`defaultLocale`](#defaultlocale) - Type: `string` The default locale of the site. [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.api/config.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/api/config/docs/site-config/api/config.md) **Did this page help you? ** [**getNitroOrigin()** Get the runtime origin URL safely across development, prerendering, and production environments.](https://nuxtseo.com/docs/site-config/api/config/docs/site-config/api/get-nitro-origin) [**Nuxt Hooks** Learn how to use Nuxt Hooks to customize your site config.](https://nuxtseo.com/docs/site-config/api/config/docs/site-config/api/nuxt-hooks) **On this page** - [enabled](#enabled) - [debug](#debug) - [multiTenancy](#multitenancy) - [url](#url) - [env](#env) - [name](#name) - [indexable](#indexable) - [trailingSlash](#trailingslash) - [defaultLocale](#defaultlocale) --- ### Nuxt SEO Kit to Nuxt SEO · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit Description: Migrate from the nuxt-seo-kit package v1 to the new v2 @nuxtjs/seo. **Migration Guides** h1. **Nuxt SEO Kit to Nuxt SEO** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs **What you'll learn** - Package changed from `nuxt-seo-kit` to `@nuxtjs/seo` - `` and `useSeoKit()` removed—functionality is now automatic - Site config moved from `runtimeConfig.public` to `site` key h2. [Support](#support) If you get stuck with the migration or have post-migration bugs, please get in touch! - [**Jump in the Discord**](https://discord.com/invite/5jDAMswWwX) - [**Make a GitHub issue**](https://github.com/harlan-zw/nuxt-seo/issues) - [**Provide feedback**](https://github.com/harlan-zw/nuxt-seo/discussions/108) h2. [Module Rename](#module-rename) With v2 the module name and scope is clarified with the rename to Nuxt SEO. - 1.* - Nuxt SEO Kit `nuxt-seo-kit` (Nuxt Layer) - 2.x - Nuxt SEO `@nuxtjs/seo` (Nuxt Module) The v2 at its core allows you to use all SEO modules at runtime, prerendering is no longer required. It also comes with improved i18n compatibility. It has been renamed to provide a better base for growing out the Nuxt SEO ecosystem as well as to make the layer -> module change more obvious. ``` h1. remove nuxt-seo-kit pnpm remove nuxt-seo-kit && pnpm i -D @nuxtjs/seo ``` ``` yarn remove nuxt-seo-kit && yarn add -D @nuxtjs/seo ``` ``` npm remove nuxt-seo-kit && npm install -D @nuxtjs/seo ``` nuxt.config.ts ``` export default defineNuxtConfig({ - extends: ['nuxt-seo-kit'], modules: [ + '@nuxtjs/seo', ] }) ``` h2. [Breaking Changes](#breaking-changes) `` component and `useSeoKit()` composable are removed. Delete these from your code—the functionality is now automatic via a plugin. h3. [``, `useSeoKit()` Removed](#seokit-useseokit-removed) These APIs set up all the default meta and module configuration for you. In v2, they are no longer needed as functionality has been moved to a plugin. ``` ``` ``` ``` If you'd like to opt-out of the these v2 configurations, you can set [**automaticDefaults**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/nuxt-seo/api/config#automaticdefaults) to `false`. h2. [Site Config Changes](#site-config-changes) In v1, site config was set through runtime config. In v2, we have a dedicated module with helpers for handling this config called [**nuxt-site-config**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/site-config/getting-started/introduction). The move to a module is to allow greater flexible in changing site configuration at runtime. If you were specifying any static config in `runtimeConfig` previously, it's now recommended to move this to the `site` key. ``` export default defineNuxtConfig({ runtimeConfig: { public: { // you can remove environment variables, they'll be set automatically siteUrl: process.env.NUXT_PUBLIC_SITE_URL, siteName: 'My App' } } }) ``` ``` export default defineNuxtConfig({ site: { name: 'My App' } }) ``` When updating your config: - All keys are without the `site` prefix - The `language` config has been renamed to `defaultLocale` The behaviour for environment variables hasn't changed, it's recommended to read [**how site config works**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/site-config/getting-started/how-it-works) for more advanced configuration. h2. [Prerendering Changes](#prerendering-changes) In v1, it was required to prerender all pages, to ensure this happened your `nuxt.config` was modified. In v2, everything can be generated at runtime and the prerendering changes are no longer provided. If you'd like to keep the prerendering changes, you can add this to your nuxt.config. ``` export default defineNuxtConfig({ nitro: { prerender: { crawlLinks: true, routes: [ '/', ], }, }, }) ``` h2. [Module Upgrades](#module-upgrades) h3. [Nuxt Robots](#nuxt-robots) Upgraded from v1 to v3: - [**v2 release notes**](https://github.com/harlan-zw/nuxt-simple-robots/releases/tag/v2.0.0) - [**v3 release notes**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/robots/releases/v3) No breaking changes. h3. [Nuxt Sitemap](#nuxt-sitemap) Upgraded from v1 to v3: - [**v2 release notes**](https://github.com/nuxt-modules/sitemap/releases/tag/v2.0.0) - [**v3 release notes**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/sitemap/releases/v3) No breaking changes. h3. [Nuxt Schema.org](#nuxt-schemaorg) Upgraded from v2 to v3: - [**v3 release notes**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/schema-org/releases/v3) No breaking changes. h3. [Nuxt OG Image](#nuxt-og-image) Upgraded from v1 to v2: - [**v2 release notes**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/og-image/releases/v2) The following options have been removed from nuxt.config `ogImage`: - `host`, `siteUrl` - see [**installation**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/og-image/getting-started/installation) for details. - `forcePrerender` - removed, not needed - `satoriProvider` - removed use `runtimeSatori` - `browserProvider` - removed use `runtimeBrowser` - `experimentalInlineWasm` - removed, this is now automatic based on environment - `experimentalRuntimeBrowser` - removed, this is now automatic based on environment The following options have been deprecated from the `defineOgImage` options: - `static` - use `cache` instead If you were referencing the old default template, you will need to update it. - `OgImageBasic` - remove the property, allow the fallback to be selected automatically Composables & Components: - `defineOgImageStatic()` is deprecated, use `defineOgImage()` (default behaviour is to cache), if you want to be verbose you can use `defineOgImageCached()` or `` - `` is deprecated, use `` - `defineOgImageDynamic()` is deprecated, use `defineOgImageWithoutCache()` - `` is deprecated, use `` If you were using the runtime browser previously, you will need to manually opt-in for it to work in production. ``` export default defineNuxtConfig({ ogImage: { runtimeBrowser: true } }) ``` ``` ``` ``` ``` h3. [Nuxt Link Checker](#nuxt-link-checker) Upgraded from v1 to v2: - [**v2 release notes**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/link-checker/releases/v2) Changes to nuxt.config `linkChecker`: - `exclude` renamed to `excludeLinks` - `failOn404` renamed to `failOnError` h3. [Nuxt SEO Utils](#nuxt-seo-utils) The `nuxt-unhead` module has been renamed to `nuxt-seo-utils`. This is to better reflect the scope of the module. Upgraded from v1 to v3: - [**v2 release notes**](https://github.com/harlan-zw/nuxt-link-checker/releases/2.0.0) - [**v3 release notes**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/link-checker/releases/v3) If you were using the `unhead` key to configure the module, you will need to change it to `seo`. ``` export default defineNuxtConfig({ - unhead: { + seo: { } }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/6.migration-guide/3.nuxt-seo-kit.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/nuxt-seo/migration-guide/nuxt-seo-kit.md) **Did this page help you? ** [**v2 Beta to v2 RC** Migrate from the Nuxt SEO v2 beta to the v2 RC.](https://nuxtseo.com/docs/nuxt-seo/migration-guide/nuxt-seo-kit/docs/nuxt-seo/migration-guide/beta-to-rc) **On this page** - [Support](#support) - [Module Rename](#module-rename) - [Breaking Changes](#breaking-changes) - [Site Config Changes](#site-config-changes) - [Prerendering Changes](#prerendering-changes) - [Module Upgrades](#module-upgrades) --- ### v2 RC to v2 Stable · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable Description: Migrate from the Nuxt SEO v2 RC to the v2 stable. **Migration Guides** h1. **v2 RC to v2 Stable** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs h2. [Introduction](#introduction) **What you'll learn** - Config keys changed: `index` → `robots`, `rules` → `groups`, `cacheTtl` → `cacheMaxAgeSeconds` - Deprecated composables removed: `defineRobotMeta`, `RobotMeta` component - `nuxt-seo-experiments` renamed to `nuxt-seo-utils` This guide will help you migrate from the Nuxt SEO v2 RC to the v2 stable release. Please see the [**announcement**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable/announcement) post for details on the release. h2. [Support](#support) If you get stuck with the migration or have post-migration bugs, please get in touch! - [**Jump in the Discord**](https://discord.com/invite/5jDAMswWwX) - [**Make a GitHub issue**](https://github.com/harlan-zw/nuxt-seo/issues) h2. [Nuxt Site Config v3](#nuxt-site-config-v3) Nuxt Site Config is a module used internally by Nuxt Robots. The major update to v3.0.0 shouldn't have any direct effect on your site, however, you may want to double-check the [**breaking changes**](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v3.0.0). h2. [Nuxt SEO Utils v6](#nuxt-seo-utils-v6) In moving to the stable release, Nuxt SEO experiments has been renamed from `nuxt-seo-experiments` to `nuxt-seo-utils`. The original name of the module was `nuxt-seo-experiments`, hinting that the features weren't stable and that they would land in the Nuxt core. This is no longer the case, and the module has been renamed to reflect this. With this rename the module scope changes to include the random functionality that Nuxt SEO was previously providing: - `useBreadcrumbItems()` composable - Config: `redirectToCanonicalSiteUrl` - Config: `fallbackTitle` - Config: `automaticDefaults` As Nuxt SEO Utils shared the same config key as the Nuxt SEO module, no changes are required to your config, however, it's worth testing your site to ensure that everything is working as expected. h2. [Nuxt Sitemap v7](#nuxt-sitemap-v7) h3. [Removed `inferStaticPagesAsRoutes` config](#removed-inferstaticpagesasroutes-config) If you set this value to `false` previously, you will need to change it to the below: ``` export default defineNuxtConfig({ sitemap: { - inferStaticPagesAsRoutes: false, + excludeAppSources: ['pages', 'route-rules', 'prerender'] } }) ``` h3. [Removed `dynamicUrlsApiEndpoint` config](#removed-dynamicurlsapiendpoint-config) The `sources` config supports multiple API endpoints and allows you to provide custom fetch options, use this instead. ``` export default defineNuxtConfig({ sitemap: { - dynamicUrlsApiEndpoint: '/__sitemap/urls', + sources: ['/__sitemap/urls'] } }) ``` h3. [Removed `cacheTtl` config](#removed-cachettl-config) Please use the `cacheMaxAgeSeconds` as its a clearer config. ``` export default defineNuxtConfig({ sitemap: { - cacheTtl: 10000, + cacheMaxAgeSeconds: 10000 } }) ``` h3. [Removed `index` route rule / Nuxt Content support](#removed-index-route-rule-nuxt-content-support) If you were using the `index: false` in either route rules or your Nuxt Content markdown files, you will need to update this to use the `robots` key. ``` export default defineNuxtConfig({ routeRules: { // use the \`index\` shortcut for simple rules - '/secret/**': { index: false }, + '/secret/**': { robots: false }, } }) ``` h2. [Nuxt Robots v5](#nuxt-robots-v5) The `index`, `indexable`, and `rules` config options are removed. You must migrate to the `robots` key for route rules and `groups` for robots.txt configuration. h3. [Removed `rules` config](#removed-rules-config) The v4 of Nuxt Robots provided a backward compatibility `rules` config. As it was deprecated, this is no longer supported. If you're using `rules`, you should migrate to the `groups` config or use a robots.txt file. ``` export default defineNuxtConfig({ robots: { - rules: {}, + groups: {} } }) ``` h3. [Removed `defineRobotMeta` composable](#removed-definerobotmeta-composable) This composable didn't do anything in v4 as the robots meta tag is enabled by default. If you'd like to control the robot meta tag rule, use the [`useRobotsRule()`](https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable/docs/robots/api/use-robots-rule) composable. ``` - defineRobotMeta(true) + useRobotsRule(true) ``` h3. [Removed `RobotMeta` component](#removed-robotmeta-component) This component was a simple wrapper for `defineRobotMeta`, you should use [`useRobotsRule()`](https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable/docs/robots/api/use-robots-rule) if you wish to control the robots rule. h3. [Removed `index`, `indexable` config](#removed-index-indexable-config) When configuring robots using route rules or [**Nuxt Content**](https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable/docs/robots/guides/content) you could control the robot's behavior by providing `index` or `indexable` rules. These are no longer supported and you should use `robots` key. ``` export default defineNuxtConfig({ routeRules: { // use the \`index\` shortcut for simple rules - '/secret/**': { index: false }, + '/secret/**': { robots: false }, } }) ``` h2. [Features](#features) h3. [Config `blockAiBots`](#config-blockaibots) AI crawlers can be beneficial as they can help users finding your site, but for some educational sites or those not interested in being indexed by AI crawlers, you can block them using the `blockAIBots` option. nuxt.config.ts ``` export default defineNuxtConfig({ robots: { blockAiBots: true } }) ``` This will block the following AI crawlers: `GPTBot`, `ChatGPT-User`, `Claude-Web`, `anthropic-ai`, `Applebot-Extended`, `Bytespider`, `CCBot`, `cohere-ai`, `Diffbot`, `FacebookBot`, `Google-Extended`, `ImagesiftBot`, `PerplexityBot`, `OmigiliBot`, `Omigili` [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/6.migration-guide/0.rc-to-stable.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable/docs/nuxt-seo/migration-guide/rc-to-stable.md) **Did this page help you? ** [**Debugging Modules** Disable modules and create minimal reproductions for Nuxt SEO issues.](https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable/docs/nuxt-seo/guides/debugging-modules) [**v2 Beta to v2 RC** Migrate from the Nuxt SEO v2 beta to the v2 RC.](https://nuxtseo.com/docs/nuxt-seo/migration-guide/rc-to-stable/docs/nuxt-seo/migration-guide/beta-to-rc) **On this page** - [Introduction](#introduction) - [Support](#support) - [Nuxt Site Config v3](#nuxt-site-config-v3) - [Nuxt SEO Utils v6](#nuxt-seo-utils-v6) - [Nuxt Sitemap v7](#nuxt-sitemap-v7) - [Nuxt Robots v5](#nuxt-robots-v5) - [Features](#features) --- ### Nuxt Hooks · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/api/nuxt-hooks Description: Learn how to use Nuxt hooks to modify your Schema.org output. **Nuxt API** h1. **Nuxt Hooks** Last updated **Oct 21, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: nuxt seo stable](https://github.com/harlan-zw/nuxt-schema-org/commit/a7fdf2380bfb470f5d41d2195a1446ee9e2b8e08). [Copy for LLMs h2. [`'schema-org:meta'`](#schema-orgmeta) **Type:** `async (config: ModuleOptions) => void | Promise` You can hook into the generation of the meta-data used to generate the Schema.org data. For example, this can be useful for dynamically changing the host. my-nuxt-plugin.ts ``` import { defineNuxtPlugin } from '#app' export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hooks.hook('schema-org:meta', (meta) => { if (nuxtApp._route.path === '/plugin-override') { meta.host = 'https://override-example.com' meta.url = \`${meta.host}${meta.path}\` } }) }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/4.api/1.nuxt-hooks.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/api/nuxt-hooks/docs/schema-org/api/nuxt-hooks.md) **Did this page help you? ** [**useSchemaOrg()** Define Schema.org structured data for your pages using the useSchemaOrg composable.](https://nuxtseo.com/docs/schema-org/api/nuxt-hooks/docs/schema-org/api/use-schema-org) [**v5.0.0** Release notes for Nuxt Schema.org v5.](https://nuxtseo.com/docs/schema-org/api/nuxt-hooks/docs/schema-org/releases/v5) --- ### v3.0.0 · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/releases/v3 Description: Release notes for v3.0.0 of Nuxt Schema.org. **Releases** h1. **v3.0.0** Last updated **Nov 24, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: v4](https://github.com/harlan-zw/nuxt-schema-org/commit/d615fcd6d45049af377bc030fced4c8467e210b2). [Copy for LLMs h2. [Features 🚀](#features) h2. [⚠️ Breaking Changes](#️-breaking-changes) h3. [Module Site Config Removed](#module-site-config-removed) The `siteUrl` and `siteName` have been removed from the module options. Configuration has been moved to [**Site Config**](https://nuxtseo.com/docs/schema-org/releases/v3/docs/site-config/getting-started/introduction). nuxt.config ``` export default defineNuxtConfig({ site: { url: 'https://example.com', name: 'My Website', } }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/4.releases/5.v3.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/releases/v3/docs/schema-org/releases/v3.md) **Did this page help you? ** [**v4.0.0** Release notes for v4.0.0 of Nuxt Schema.org.](https://nuxtseo.com/docs/schema-org/releases/v3/docs/schema-org/releases/v4) **On this page** - [Features 🚀](#features) - [⚠️ Breaking Changes](#️-breaking-changes) --- ### v4.0.0 · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/releases/v4 Description: Release notes for v4.0.0 of Nuxt Schema.org. **Releases** h1. **v4.0.0** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix wrong descriptions and add schema validator links (#97) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-schema-org/pull/97). [Copy for LLMs h2. [Introduction](#introduction) The v4 major of Nuxt Schema.org is a simple release to remove deprecations and add support for the [**Nuxt SEO v2 stable**](https://nuxtseo.com/announcement). h2. [Breaking Features](#breaking-features) h3. [Site Config v3](#site-config-v3) Nuxt Site Config is a module used internally by Nuxt Schema.org. It's major update to v3.0.0 shouldn't have any direct affect your site, however, you may want to double-check the [**breaking changes**](https://github.com/harlan-zw/nuxt-site-config/releases/tag/v3.0.0). [Edit this page](https://github.com/harlan-zw/nuxt-schema-org/edit/main/docs/content/4.releases/4.v4.md) [Markdown For LLMs](https://nuxtseo.com/docs/schema-org/releases/v4/docs/schema-org/releases/v4.md) **Did this page help you? ** [**v5.0.0** Release notes for Nuxt Schema.org v5.](https://nuxtseo.com/docs/schema-org/releases/v4/docs/schema-org/releases/v5) [**v3.0.0** Release notes for v3.0.0 of Nuxt Schema.org.](https://nuxtseo.com/docs/schema-org/releases/v4/docs/schema-org/releases/v3) **On this page** - [Introduction](#introduction) - [Breaking Features](#breaking-features) --- ### Nuxt Config · Nuxt Schema.org · Nuxt SEO Source: https://nuxtseo.com/docs/schema-org/api/config Description: Configure the Nuxt Schema.org module. **Nuxt API** h1. **Nuxt Config** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix wrong descriptions and add schema validator links (#97) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-schema-org/pull/97). [Copy for LLMs h2. [`identity`](#identity) - Type: `'Person' | 'Organization' | Person| Organization` - Default: `undefined` The identity of your site. This will only have an effect when using `defaults`. h2. [`defaults`](#defaults) - Type: `boolean` - Default: `true` Whether the default Schema.org setup should be enabled or not. h2. [`minify`](#minify) - Type: `boolean` - Default: `process.env.NODE_ENV === 'production'` Whether the Schema.org output should be minified or not. Will slightly reduce your bundle size. h2. [`reactive`](#reactive) - Type: `boolean` - Default: `nuxt.options.dev|| !nuxt.options.ssr` Whether client-side reactivity should be enabled or not. This is not needed for SEO reasons when SSR but may be useful for debugging. h2. [`scriptAttributes`](#scriptattributes) - Type: `(ScriptBase& TagUserProperties)| false` - Default: `{ 'data-nuxt-schema-org':true }` The attributes to add to the ` ``` h2. [Events](#events) h3. [`@dismiss`](#dismiss-1) Emitted when notification is dismissed. ``` ``` h3. [`@reload`](#reload-1) Emitted when reload is triggered. ``` ``` h2. [Examples](#examples) See [**UI Examples**](https://nuxtseo.com/docs/skew-protection/api/skew-notification/docs/skew-protection/guides/ui-examples) for complete examples including Nuxt UI, toast notifications, banners, and more. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/3.api/2.skew-notification.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/api/skew-notification/docs/skew-protection/api/skew-notification.md) **Did this page help you? ** [**useSkewProtection()** Composable for accessing skew protection state and methods.](https://nuxtseo.com/docs/skew-protection/api/skew-notification/docs/skew-protection/api/use-skew-protection) [**useActiveConnections()** Composable for monitoring real-time connection statistics and version distribution.](https://nuxtseo.com/docs/skew-protection/api/skew-notification/docs/skew-protection/api/use-active-connections) **On this page** - [Basic Usage](#basic-usage) - [Slot Props](#slot-props) - [Props](#props) - [Events](#events) - [Examples](#examples) --- ### useActiveConnections() · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/api/use-active-connections Description: Composable for monitoring real-time connection statistics and version distribution. **Nuxt API** h1. **useActiveConnections()** Last updated **Dec 13, 2025** by [Harlan Wilton](https://github.com/web-flow) in [feat: view active connections (#6)](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/b03f3694ea7ad7e35bc486586585742250132f3c). [Copy for LLMs A composable for accessing real-time statistics about active SSE/WebSocket connections and their version distribution. This composable requires `connectionTracking: true` in your module config and only works with `sse` or `ws` update strategies. It does not support Pusher/Ably adapters. h2. [Setup](#setup) Enable connection tracking in your Nuxt config: nuxt.config.ts ``` export default defineNuxtConfig({ skewProtection: { connectionTracking: true } }) ``` h2. [Usage](#usage) ``` const { total, versions } = useActiveConnections() ``` h2. [Returns](#returns) h3. [`total`](#total) - Type: `ComputedRef` The total number of active connections across all versions. ``` const { total } = useActiveConnections() console.log(total.value) // 42 ``` h3. [`versions`](#versions) - Type: `ComputedRef>` A map of build IDs to connection counts, showing how many users are on each version. ``` const { versions } = useActiveConnections() console.log(versions.value) // { "abc123": 35, "def456": 7 } ``` h2. [Type Definitions](#type-definitions) ``` interface ConnectionStats { total: number versions: Record } ``` h2. [Examples](#examples) h3. [Admin Dashboard Widget](#admin-dashboard-widget) ``` ``` h3. [Deployment Safety Check](#deployment-safety-check) ``` const { total, versions } = useActiveConnections() const usersOnOldVersions = computed(() => { const currentBuildId = useRuntimeConfig().app.buildId return Object.entries(versions.value) .filter(([id]) => id !== currentBuildId) .reduce((sum, [, count]) => sum + count, 0) }) watch(usersOnOldVersions, (count) => { if (count === 0) { console.log('All users migrated to latest version!') } }) ``` h3. [Real-time Stats Display](#real-time-stats-display) ``` ``` h2. [Limitations](#limitations) - Only works with `updateStrategy: 'sse'` or `updateStrategy: 'ws'` - Does not support Pusher or Ably adapters (connections are managed by third-party) - Stats are per-server instance (not aggregated across horizontal scaling) - Requires server with persistent connections (not compatible with serverless/edge) [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/3.api/2.use-active-connections.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/api/use-active-connections/docs/skew-protection/api/use-active-connections.md) **Did this page help you? ** [**** Headless component for displaying update notifications.](https://nuxtseo.com/docs/skew-protection/api/use-active-connections/docs/skew-protection/api/skew-notification) [**Configuration** Complete configuration reference for Nuxt Skew Protection.](https://nuxtseo.com/docs/skew-protection/api/use-active-connections/docs/skew-protection/api/config) **On this page** - [Setup](#setup) - [Usage](#usage) - [Returns](#returns) - [Type Definitions](#type-definitions) - [Examples](#examples) - [Limitations](#limitations) --- ### Nitro Hooks · Nuxt Skew Protection · Nuxt SEO Source: https://nuxtseo.com/docs/skew-protection/nitro-api/nitro-hooks Description: Learn how to use Nitro hooks to track and respond to real-time connections. **Nitro API** h1. **Nitro Hooks** Last updated **Dec 13, 2025** by [Harlan Wilton](https://github.com/web-flow) in [feat: view active connections (#6)](https://github.com/nuxt-seo-pro/nuxt-skew-protection/commit/b03f3694ea7ad7e35bc486586585742250132f3c). [Copy for LLMs Nitro hooks allow you to build custom functionality on top of the SSE/WebSocket connections. h2. [`'skew:connection:open'`](#skewconnectionopen) **Type:** `(payload: { id: string, version: string, send: (data) => void }) => void` Triggered when a client establishes an SSE or WebSocket connection. | **Property** | **Description** | | --- | --- | | `id` | Unique connection identifier | | `version` | Client's build version (from cookie) | | `send` | Function to send data to this specific client | server/plugins/connections.ts ``` import { defineNitroPlugin } from 'nitropack/runtime' export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('skew:connection:open', ({ id, version, send }) => { console.log(\`Client ${id} connected on version ${version}\`) // Send a custom message to this client send({ type: 'welcome', message: 'Hello!' }) }) }) ``` h2. [`'skew:connection:close'`](#skewconnectionclose) **Type:** `(payload: { id: string }) => void` Triggered when a client disconnects from the SSE or WebSocket connection. server/plugins/connections.ts ``` import { defineNitroPlugin } from 'nitropack/runtime' export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('skew:connection:close', ({ id }) => { console.log(\`Client ${id} disconnected\`) }) }) ``` h2. [Recipes](#recipes) h3. [Custom Connection Tracking](#custom-connection-tracking) Build your own connection tracking without using the built-in `connectionTracking` option: server/plugins/custom-tracking.ts ``` import { defineNitroPlugin } from 'nitropack/runtime' const connections = new Map() export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('skew:connection:open', ({ id, version }) => { connections.set(id, { version, connectedAt: new Date() }) }) nitroApp.hooks.hook('skew:connection:close', ({ id }) => { connections.delete(id) }) }) ``` h3. [Broadcast to All Clients](#broadcast-to-all-clients) Send messages to all connected clients: server/plugins/broadcast.ts ``` import { defineNitroPlugin } from 'nitropack/runtime' type SendFn = (data: any) => void const clients = new Map() export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('skew:connection:open', ({ id, send }) => { clients.set(id, send) }) nitroApp.hooks.hook('skew:connection:close', ({ id }) => { clients.delete(id) }) // Expose broadcast function globally globalThis.broadcastToClients = (data: any) => { for (const send of clients.values()) { send(data) } } }) ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-skew-protection/edit/main/docs/content/3.nitro-api/nitro-hooks.md) [Markdown For LLMs](https://nuxtseo.com/docs/skew-protection/nitro-api/nitro-hooks/docs/skew-protection/nitro-api/nitro-hooks.md) **Did this page help you? ** [**isClientOutdated()** Server-side utility to check if a client's version is outdated.](https://nuxtseo.com/docs/skew-protection/nitro-api/nitro-hooks/docs/skew-protection/nitro-api/is-client-outdated) [**External Providers** Use third-party WebSocket providers for real-time update notifications on any platform.](https://nuxtseo.com/docs/skew-protection/nitro-api/nitro-hooks/docs/skew-protection/providers/external) **On this page** - ['skew:connection:open'](#skewconnectionopen) - ['skew:connection:close'](#skewconnectionclose) - [Recipes](#recipes) --- ### Nuxt Hooks · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/api/nuxt-hooks Description: Nuxt hooks provided by nuxt-ai-ready for extending functionality. **Nuxt API** h1. **Nuxt Hooks** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: rework module](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/2535042559350f45518092731fbfdb3138864574). [Copy for LLMs h2. [`'ai-ready:page:markdown'`](#ai-readypagemarkdown) **Type:** `(ctx: ParsedMarkdownResult & { route: string }) => void | Promise` Called per page during prerender. Modify markdown content before it's written to `llms-full.txt` and `page-data.jsonl`. nuxt.config.ts ``` export default defineNuxtConfig({ hooks: { 'ai-ready:page:markdown': (ctx) => { // Add frontmatter ctx.markdown = \`---\ntitle: ${ctx.title}\n---\n\n${ctx.markdown}\` } } }) ``` **Context:** | **Property** | **Type** | **Description** | | --- | --- | --- | | `route` | `string` | Page route (e.g., `/about`) | | `markdown` | `string` | Generated markdown (modify this) | | `title` | `string` | Page title | | `description` | `string` | Page description | | `headings` | `Array>` | Extracted headings from page | | `updatedAt` | `string?` | Last modified date (from sitemap) | Only fires during `nuxi generate` or `nuxi build --prerender`. h2. [`'ai-ready:llms-txt'`](#ai-readyllms-txt) **Type:** `(payload: { sections: LlmsTxtSection[], notes: string[]}) => void | Promise` Called before llms.txt generation. Add or modify sections and notes. nuxt.config.ts ``` export default defineNuxtConfig({ hooks: { 'ai-ready:llms-txt': (payload) => { payload.sections.push({ title: 'Custom APIs', description: 'Additional endpoints', links: [ { title: 'Search', href: '/api/search', description: 'Search endpoint' } ] }) payload.notes.push('Built with Nuxt AI Ready') } } }) ``` **Payload:** | **Property** | **Type** | **Description** | | --- | --- | --- | | `sections` | `LlmsTxtSection[]` | Sections with title, description, links | | `notes` | `string[]` | Notes at end of file | Mutable pattern — modify arrays directly, don't return values. [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/3.api/1.nuxt-hooks.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/api/nuxt-hooks/docs/ai-ready/api/nuxt-hooks.md) **Did this page help you? ** h3. **Related ** [**llms.txt Generation**](https://nuxtseo.com/docs/ai-ready/api/nuxt-hooks/docs/ai-ready/guides/llms-txt) [**Nitro Hooks**](https://nuxtseo.com/docs/ai-ready/api/nuxt-hooks/docs/ai-ready/nitro-api/nitro-hooks) [**RAG Setup** Vectorize your site's markdown for semantic search and RAG pipelines.](https://nuxtseo.com/docs/ai-ready/api/nuxt-hooks/docs/ai-ready/advanced/rag-example) [**Configuration** Nuxt configuration reference for Nuxt AI Ready.](https://nuxtseo.com/docs/ai-ready/api/nuxt-hooks/docs/ai-ready/api/config) **On this page** - ['ai-ready:page:markdown'](#ai-readypagemarkdown) - ['ai-ready:llms-txt'](#ai-readyllms-txt) --- ### Nitro Hooks · Nuxt AI Ready · Nuxt SEO Source: https://nuxtseo.com/docs/ai-ready/nitro-api/nitro-hooks Description: Nitro runtime hooks for modifying markdown output. **Nitro API** h1. **Nitro Hooks** Last updated **Dec 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [fix!: rework module](https://github.com/nuxt-seo-pro/nuxt-ai-ready/commit/2535042559350f45518092731fbfdb3138864574). [Copy for LLMs h2. [`'ai-ready:markdown'`](#ai-readymarkdown) **Type:** `(ctx: MarkdownContext) => void | Promise` Called during runtime HTML→markdown conversion. Modify markdown before response. server/plugins/markdown-footer.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('ai-ready:markdown', (ctx) => { ctx.markdown += '\n\n---\n*Generated with mdream*' }) }) ``` **MarkdownContext:** | **Property** | **Type** | **Description** | | --- | --- | --- | | `html` | `string` | Original HTML | | `markdown` | `string` | Generated markdown (modify this) | | `route` | `string` | Route being processed | | `title` | `string` | Page title | | `description` | `string` | Page description | | `isPrerender` | `boolean` | Whether during prerendering | | `event` | `H3Event` | H3 event object | h2. [`'ai-ready:mdreamConfig'`](#ai-readymdreamconfig) **Type:** `(config: HTMLToMarkdownOptions) => void | Promise` Called before HTML→markdown conversion. Modify mdream options per-request. server/plugins/mdream-config.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('ai-ready:mdreamConfig', (config) => { // Check origin from config.origin if available if (config.origin?.includes('/blog/')) config.ignoreElements = [...(config.ignoreElements || []), '.author-bio'] }) }) ``` Add custom plugins: server/plugins/custom-mdream.ts ``` export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('ai-ready:mdreamConfig', (config) => { config.plugins = config.plugins || [] config.plugins.push(myCustomPlugin()) }) }) ``` [Edit this page](https://github.com/nuxt-seo-pro/nuxt-ai-ready/edit/main/docs/content/3.nitro-api/2.nitro-hooks.md) [Markdown For LLMs](https://nuxtseo.com/docs/ai-ready/nitro-api/nitro-hooks/docs/ai-ready/nitro-api/nitro-hooks.md) **Did this page help you? ** h3. **Related ** [**Markdown Conversion**](https://nuxtseo.com/docs/ai-ready/nitro-api/nitro-hooks/docs/ai-ready/guides/markdown) [**Nuxt Hooks**](https://nuxtseo.com/docs/ai-ready/nitro-api/nitro-hooks/docs/ai-ready/api/nuxt-hooks) [**Configuration**](https://nuxtseo.com/docs/ai-ready/nitro-api/nitro-hooks/docs/ai-ready/api/config) [**Configuration** Nuxt configuration reference for Nuxt AI Ready.](https://nuxtseo.com/docs/ai-ready/nitro-api/nitro-hooks/docs/ai-ready/api/config) **On this page** - ['ai-ready:markdown'](#ai-readymarkdown) - ['ai-ready:mdreamConfig'](#ai-readymdreamconfig) --- ### updateSiteConfig() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/api/update-site-config Description: Learn how to modify site config at runtime. **Nuxt API** h1. **updateSiteConfig()** Last updated **Oct 30, 2024** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: broken links](https://github.com/harlan-zw/nuxt-site-config/commit/76fac9355f512e5d1e3a8893f951b1bdd3b5fa3d). [Copy for LLMs Modify the site config at runtime. You can provide any config to this function, such as the [**recommended config**](https://nuxtseo.com/docs/site-config/api/update-site-config/docs/site-config/guides/setting-site-config). Warning When using this, you will to run it as early as possible in the Nuxt lifecycle to avoid conflicts with other modules. It's recommended to use the Nitro [**updateSiteConfig**](https://nuxtseo.com/docs/site-config/api/update-site-config/docs/site-config/nitro-api/update-site-config) API instead. h2. [Usage](#usage) plugins/site-config.server.ts ``` import { updateSiteConfig } from '#imports' export default defineNuxtPlugin({ enforce: 'pre', // make it happen early setup() { updateSiteConfig({ name: 'My Site', url: 'https://example.com', }) } }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.api/1.update-site-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/api/update-site-config/docs/site-config/api/update-site-config.md) **Did this page help you? ** [**useSiteConfig()** How to access site config within a Nuxt context.](https://nuxtseo.com/docs/site-config/api/update-site-config/docs/site-config/api/use-site-config) [**createSitePathResolver()** Create a function to resolve a path relative to the site.](https://nuxtseo.com/docs/site-config/api/update-site-config/docs/site-config/api/create-site-path-resolver) --- ### useSiteConfig() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/nitro-api/use-site-config Description: How to use Site Config within Nitro. **Nitro API** h1. **useSiteConfig()** Last updated **May 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: explicit imports for nitro Fixes #56](https://github.com/harlan-zw/nuxt-site-config/commit/31cd76c1960c194ffa804e4ffb29a00c8db9430b). [Copy for LLMs Same as [**useSiteConfig**](https://nuxtseo.com/docs/site-config/nitro-api/use-site-config/docs/site-config/api/use-site-config) but you will need to provide the request context. h2. [Usage](#usage) serverMiddleware.ts ``` import { defineEventHandler } from '#imports' import { useSiteConfig } from '#site-config/server/composables' export default defineEventHandler((e) => { const siteConfig = useSiteConfig(e) console.log(siteConfig.name) }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.nitro-api/0.use-site-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/nitro-api/use-site-config/docs/site-config/nitro-api/use-site-config.md) **Did this page help you? ** [**Nuxt Hooks** Learn how to use Nuxt Hooks to customize your site config.](https://nuxtseo.com/docs/site-config/nitro-api/use-site-config/docs/site-config/api/nuxt-hooks) [**updateSiteConfig()** How to update Site Config within Nitro.](https://nuxtseo.com/docs/site-config/nitro-api/use-site-config/docs/site-config/nitro-api/update-site-config) --- ### Nuxt Hooks · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/api/nuxt-hooks Description: Learn how to use Nuxt Hooks to customize your site config. **Nuxt API** h1. **Nuxt Hooks** Last updated **Jan 14, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: broken `site-config:resolve` usage Fixes https://github.com/harlan-zw/nuxt-seo/issues/374](https://github.com/harlan-zw/nuxt-site-config/commit/f30cc62cbcb0d2a8dac81dc05391cdb5310c55f7). [Copy for LLMs h2. [`site-config:resolve`](#site-configresolve) **Type:** `async () => void | Promise` Modify the build time site config after it has been resolved. It's recommended to use runtime [**Nitro hooks**](https://nuxtseo.com/docs/site-config/api/nuxt-hooks/docs/site-config/nitro-api/nitro-hooks) if you need to modify the site config at runtime. ``` import { updateSiteConfig } from 'nuxt-site-config/kit' export default defineNuxtConfig({ hooks: { 'site-config:resolve': () => { if (process.env.FOO) { updateSiteConfig({ name: 'Bar' }) } }, }, }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.api/nuxt-hooks.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/api/nuxt-hooks/docs/site-config/api/nuxt-hooks.md) **Did this page help you? ** [**Nuxt Config** The config options available for Nuxt Site Config.](https://nuxtseo.com/docs/site-config/api/nuxt-hooks/docs/site-config/api/config) [**useSiteConfig()** How to use Site Config within Nitro.](https://nuxtseo.com/docs/site-config/api/nuxt-hooks/docs/site-config/nitro-api/use-site-config) --- ### updateSiteConfig() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/nitro-api/update-site-config Description: How to update Site Config within Nitro. **Nitro API** h1. **updateSiteConfig()** Last updated **May 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: explicit imports for nitro Fixes #56](https://github.com/harlan-zw/nuxt-site-config/commit/31cd76c1960c194ffa804e4ffb29a00c8db9430b). [Copy for LLMs Same as [**updateSiteConfig**](https://nuxtseo.com/docs/site-config/nitro-api/update-site-config/docs/site-config/api/update-site-config) but you will need to provide the request context. Warning When using this, you will to run it as early as possible within the request lifecycle to avoid conflicts. It's recommended to run this either in a Nitro plugin or a nitro middleware. h2. [Usage](#usage) serverMiddleware.ts ``` import { defineEventHandler } from '#imports' import { updateSiteConfig } from '#site-config/server/composables' export default defineEventHandler((e) => { updateSiteConfig(e, { name: 'My Site', url: 'https://example.com', }) }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.nitro-api/1.update-site-config.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/nitro-api/update-site-config/docs/site-config/nitro-api/update-site-config.md) **Did this page help you? ** [**useSiteConfig()** How to use Site Config within Nitro.](https://nuxtseo.com/docs/site-config/nitro-api/update-site-config/docs/site-config/nitro-api/use-site-config) [**getSiteIndexable()** Check if the current site is indexable within Nitro.](https://nuxtseo.com/docs/site-config/nitro-api/update-site-config/docs/site-config/nitro-api/get-site-indexable) --- ### createSitePathResolver() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/nitro-api/create-site-path-resolver Description: Create a function to resolve a path relative to the site within Nitro. **Nitro API** h1. **createSitePathResolver()** Last updated **May 18, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [doc: explicit imports for nitro Fixes #56](https://github.com/harlan-zw/nuxt-site-config/commit/31cd76c1960c194ffa804e4ffb29a00c8db9430b). [Copy for LLMs Same as [**createSitePathResolver()**](https://nuxtseo.com/docs/site-config/nitro-api/create-site-path-resolver/docs/site-config/api/create-site-path-resolver) but you will need to provide the request context. h2. [Usage](#usage) serverMiddleware.ts ``` import { createSitePathResolver } from '#site-config/server/composables' export default defineEventHandler((e) => { const resolvePath = createSitePathResolver(e, { canonical: true, }) resolvePath('/about') // https://www.example.com/about }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.nitro-api/4.create-site-path-resolver.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/nitro-api/create-site-path-resolver/docs/site-config/nitro-api/create-site-path-resolver.md) **Did this page help you? ** [**getSiteIndexable()** Check if the current site is indexable within Nitro.](https://nuxtseo.com/docs/site-config/nitro-api/create-site-path-resolver/docs/site-config/nitro-api/get-site-indexable) [**getNitroOrigin()** Get the runtime origin URL safely within Nitro server handlers.](https://nuxtseo.com/docs/site-config/nitro-api/create-site-path-resolver/docs/site-config/nitro-api/get-nitro-origin) --- ### Nitro Hooks · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/nitro-api/nitro-hooks Description: Learn how to use Nitro Hooks to customize your site config. **Nitro API** h1. **Nitro Hooks** Last updated **Dec 11, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: sync](https://github.com/harlan-zw/nuxt-site-config/commit/84be472bc9fcb069bee40b1c44a6427b6a3794a1). [Copy for LLMs h2. [`site-config:init`](#site-configinit) **Type:** ``` export interface HookSiteConfigInitContext { event: H3Event siteConfig: SiteConfigStack } ``` Modify site config after it's being initialized. server/plugins/site-config.ts ``` import { getNitroOrigin } from '#site-config/server/composables' export default defineNitroPlugin((nitroApp) => { nitroApp.hooks.hook('site-config:init', ({ event, siteConfig }) => { const origin = getNitroOrigin(event) if (origin.startsWith('fr.')) { siteConfig.push({ _context: 'french nitro plugin', // helps you debug name: 'Mon Site', url: 'https://fr.example.com', }) } }) }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.nitro-api/nitro-hooks.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/nitro-api/nitro-hooks/docs/site-config/nitro-api/nitro-hooks.md) **Did this page help you? ** [**getNitroOrigin()** Get the runtime origin URL safely within Nitro server handlers.](https://nuxtseo.com/docs/site-config/nitro-api/nitro-hooks/docs/site-config/nitro-api/get-nitro-origin) [**v3.0.0** Release notes for v3.0.0.](https://nuxtseo.com/docs/site-config/nitro-api/nitro-hooks/docs/site-config/releases/v3) --- ### getSiteIndexable() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/nitro-api/get-site-indexable Description: Check if the current site is indexable within Nitro. **Nitro API** h1. **getSiteIndexable()** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix broken links, improve descriptions, and add tool links (#72) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/72). [Copy for LLMs Determine if the site is indexable by search engines. This will use the `env`, if it is `production` then it will return `true`, otherwise it will return `false`. It can be overridden by providing a `indexable` property in the site config. This allows you to opt-out of indexing in production. h2. [Usage](#usage) ``` import { getSiteIndexable } from '#site-config/server/composables' const indexable = getSiteIndexable(e) // true ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.nitro-api/3.get-site-indexable.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/nitro-api/get-site-indexable/docs/site-config/nitro-api/get-site-indexable.md) **Did this page help you? ** [**updateSiteConfig()** How to update Site Config within Nitro.](https://nuxtseo.com/docs/site-config/nitro-api/get-site-indexable/docs/site-config/nitro-api/update-site-config) [**createSitePathResolver()** Create a function to resolve a path relative to the site within Nitro.](https://nuxtseo.com/docs/site-config/nitro-api/get-site-indexable/docs/site-config/nitro-api/create-site-path-resolver) --- ### getNitroOrigin() · Nuxt Site Config · Nuxt SEO Source: https://nuxtseo.com/docs/site-config/nitro-api/get-nitro-origin Description: Get the runtime origin URL safely within Nitro server handlers. **Nitro API** h1. **getNitroOrigin()** Last updated **Jan 5, 2026** by [Harlan Wilton](https://github.com/harlan-zw) in [docs: fix broken links, improve descriptions, and add tool links (#72) Co-authored-by: Claude Opus 4.5 ](https://github.com/harlan-zw/nuxt-site-config/pull/72). [Copy for LLMs Same as [**getNitroOrigin()**](https://nuxtseo.com/docs/site-config/nitro-api/get-nitro-origin/docs/site-config/api/get-nitro-origin) but you will need to provide the request context. h2. [Usage](#usage) serverMiddleware.ts ``` import { getNitroOrigin } from '#site-config/server/composables' export default defineEventHandler((e) => { const origin = getNitroOrigin(e) // https://www.example.com }) ``` [Edit this page](https://github.com/harlan-zw/nuxt-site-config/edit/main/docs/content/4.nitro-api/5.get-nitro-origin.md) [Markdown For LLMs](https://nuxtseo.com/docs/site-config/nitro-api/get-nitro-origin/docs/site-config/nitro-api/get-nitro-origin.md) **Did this page help you? ** [**createSitePathResolver()** Create a function to resolve a path relative to the site within Nitro.](https://nuxtseo.com/docs/site-config/nitro-api/get-nitro-origin/docs/site-config/nitro-api/create-site-path-resolver) [**Nitro Hooks** Learn how to use Nitro Hooks to customize your site config.](https://nuxtseo.com/docs/site-config/nitro-api/get-nitro-origin/docs/site-config/nitro-api/nitro-hooks) --- ### v2 Beta to v2 RC · Nuxt SEO Source: https://nuxtseo.com/docs/nuxt-seo/migration-guide/beta-to-rc Description: Migrate from the Nuxt SEO v2 beta to the v2 RC. **Migration Guides** h1. **v2 Beta to v2 RC** Last updated **Mar 30, 2025** by [Harlan Wilton](https://github.com/harlan-zw) in [chore: drop monorepo docs](https://github.com/harlan-zw/nuxt-seo/commit/f6815c52d51c989c905a4e6cd0406b1b9efffae3). [Copy for LLMs h2. [Support](#support) If you get stuck with the migration or have post-migration bugs, please get in touch! - [**Jump in the Discord**](https://discord.com/invite/5jDAMswWwX) - [**Make a GitHub issue**](https://github.com/harlan-zw/nuxt-seo/issues) - [**Provide feedback**](https://github.com/harlan-zw/nuxt-seo/discussions/108) h2. [Package Rename](#package-rename) In moving to the RC release, the package name has changed from `@nuxtseo/module` to `@nuxtjs/seo`. - 2-beta.x - Nuxt SEO Kit `@nuxtseo/module` - 2-rc.x - Nuxt SEO `@nuxtjs/seo` ``` pnpm remove @nuxtseo/module && pnpm i -D @nuxtjs/seo ``` ``` yarn remove @nuxtseo/module && yarn add -D @nuxtjs/seo ``` ``` npm remove @nuxtseo/module && npm install -D @nuxtjs/seo ``` nuxt.config.ts ``` export default defineNuxtConfig({ modules: [ - '@nuxtseo/module' + '@nuxtjs/seo', ] }) ``` h2. [Notable Changes](#notable-changes) h3. [Sitemap v5](#sitemap-v5) The sitemap module has been updated to v5, which itself included a package rename. - 4.x - `nuxt-simple-sitemap` - 5.x - `@nuxtjs/sitemap` No changes are required to your config. [Edit this page](https://github.com/harlan-zw/nuxt-seo/edit/main/docs/content/6.migration-guide/1.beta-to-rc.md) [Markdown For LLMs](https://nuxtseo.com/docs/nuxt-seo/migration-guide/beta-to-rc/docs/nuxt-seo/migration-guide/beta-to-rc.md) **Did this page help you? ** [**v2 RC to v2 Stable** Migrate from the Nuxt SEO v2 RC to the v2 stable.](https://nuxtseo.com/docs/nuxt-seo/migration-guide/beta-to-rc/docs/nuxt-seo/migration-guide/rc-to-stable) [**Nuxt SEO Kit to Nuxt SEO** Migrate from the nuxt-seo-kit package v1 to the new v2 @nuxtjs/seo.](https://nuxtseo.com/docs/nuxt-seo/migration-guide/beta-to-rc/docs/nuxt-seo/migration-guide/nuxt-seo-kit) **On this page** - [Support](#support) - [Package Rename](#package-rename) - [Notable Changes](#notable-changes) ---