Nuxt SEO

Nuxt ships server-side rendering by default, but that's a per-route setting, not a guarantee — full HTML to crawlers only on routes that keep it. Rendering modes, useSeoMeta(), the @nuxtjs/seo toolkit, hydration and Nitro caveats, Core Web Vitals, and the mistakes that quietly cost you.

First published: Jun 26, 2026 · Last updated: Jul 18, 2026 · Advanced
demand #4 in JavaScript Frameworks#23 in Platform SEO#142 in Technical SEO#194 on the site
1 evidence signal on this page

Nuxt server-renders pages by default, so a route that keeps that default gives crawlers a complete HTML document instead of the empty shell a plain Vue SPA ships — but it's a per-route setting: routeRules or a global ssr:false can flip any route to CSR, so verify the actual route rather than assuming from the framework name. The foundational decision is rendering mode per route — SSR (default), SSG via nuxt generate, or hybrid route rules — because meta tags, schema, and sitemaps all come after that. Use useSeoMeta() for meta, treat Harlan Wilton's @nuxtjs/seo bundle as an optional third-party toolkit (not Nuxt core) for robots/sitemap/OG/schema/canonical, never reach for dynamic rendering (Google deprecated it), verify hydration/payload and Nitro deployment-preset/cache behavior independently of server HTML, and remember that AI-crawler rendering contracts vary by provider — SSR/SSG puts content in raw HTML and maximizes coverage.

TL;DR — Nuxt’s default is server-side renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., so a route that keeps that default gets a complete DOM instead of the empty shell a plain Vue SPA ships — but that’s a per-route outcome: ssr: false or a routeRules override can turn any route into CSR or hybrid, so test the actual route, not the project name. RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. strategy is the foundational decision — SSR (default), SSG (nuxt generate), or hybrid routeRules — because meta, schema, and sitemapsA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. all come after it. Use useSeoMeta() for meta tagsMeta tags are HTML elements in a page's head that pass metadata about the page to search engines and browsers. For SEO only a few matter — the title element, the meta description, and the robots meta tag — while meta keywords and most others are ignored. (useHead() for the rest of the head, server-only variants when you don’t need reactivity), and treat Harlan Wilton’s @nuxtjs/seo bundle as an optional third-party toolkit for robots/sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing./OG/schema/canonical — not part of Nuxt core, and not proof its output is correct until you check it. Never use dynamic rendering (Google deprecated it). AI-crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. rendering varies by provider — SSR/SSG maximizes raw-HTML coverage. Beyond the initial HTML, verify hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers./payload, Nitro deployment presets and cachingCaching stores a copy of a page or resource — in a browser, a CDN edge node, or a search crawler's own cache — so it can be served again without regenerating or re-downloading it. It isn't a direct ranking factor, but it feeds page speed and crawl efficiency., and direct HTTP status independently — server HTML alone doesn’t prove any of those. The usual JS-SEO rules still apply: real <a href> links, don’t block JS/CSS, watch rendered vs. raw HTML.

Nuxt is Vue’s answer to the SPA problem

Vue, by itself, ships a single-page application: an HTML shell plus JavaScript that builds the DOM in the browser. Nuxt is the meta-framework on top of Vue (running on the Nitro server engine in Nuxt 3, with Nuxt 4 following in 2025), and its whole reason for existing — from an SEO standpoint — is that it renders on the server by default. Every page arrives as a fully-formed HTML document, which is exactly what GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. wants to read without having to execute JavaScript first. Plain Vue SEO is its own topic with its own failure modes; here I’m assuming you’ve picked Nuxt precisely so you don’t have to fight the SPA problem.

This is the same point I make about JavaScript generally: “any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines. Gatsby, Next, Nuxt, etc., are all great.” Nuxt’s defaults are pointed the right way. Most Nuxt SEONuxt SEO is the practice of optimizing Nuxt.js (Vue's meta-framework) apps so search engines and AI crawlers can crawl, render, and index them. Nuxt's big advantage is that it ships server-side rendering by default, so pages arrive as fully-formed HTML instead of an empty Vue SPA shell. problems are people turning those defaults off, or layering mistakes on top of them.

Rendering strategy is the foundation — decided per route, not per project

Before meta tagsMeta tags are HTML elements in a page's head that pass metadata about the page to search engines and browsers. For SEO only a few matter — the title element, the meta description, and the robots meta tag — while meta keywords and most others are ignored., before schema, before sitemaps, the question that decides everything is how does the HTML get produced for this specific route? Nuxt documents universal (server) rendering as the app-wide default, but that default isn’t a route-level guarantee: a global ssr: false switches the whole app to client rendering, and routeRules can assign a different mode to any URL pattern. “This is a Nuxt app” tells you nothing about how one particular page renders — you have to check the route. Nuxt gives you five strategies:

ModeHow you set itSEO impactBest for
Universal / SSRDefault (ssr: true)Excellent — full HTML every requestDynamic, personalized content
Static / SSGnuxt generateExcellent — HTML built at deploy timeBlogs, docs, marketing
HybridrouteRules per routeExcellent — mix per routeLarge mixed-content sites
SPA / CSRssr: falsePoor for indexedStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. contentDashboards, admin panels
Edge-sideDeployment targetExcellent — low TTFBTime to First Byte — the time from the start of a request to when the first byte of the response arrives. It's a diagnostic metric (not a Core Web Vital) and a major input to FCP and LCP; ≤0.8 s is good.Global performance

The official Nuxt docs are blunt about why client-side rendering is the wrong choice for content: IndexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. and updating the content delivered via client-side rendering takes more time”, whereas with server (universal) rendering “web crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. can directly index the page’s content.” Evidence for this claim Nuxt documents that universal rendering delivers HTML content immediately and allows crawlers to index it directly. Scope: Nuxt rendering; no indexing guarantee. Confidence: high · Verified: Nuxt: Rendering modes (Nuxt rendering docs.) CSR (ssr: false) is positioned for back-office, dashboards, and games — not anything you want indexed.

Hybrid rendering is the power move for large sites. Route rules in nuxt.config.ts let you set the rendering and caching mode per URL pattern:

routeRules: {
  '/blog/**':     { prerender: true },        // SSG for the blog
  '/product/**':  { swr: 3600 },              // ISR-style: regenerate hourly
  '/admin/**':    { ssr: false },             // SPA for the admin area
  '/checkout/**': { ssr: true },              // always-fresh SSR
}

swr (stale-while-revalidate) and isr (incremental static regeneration) generate a page statically then refresh it in the background — ideal for high page-count e-commerce or news where a full rebuild on every change isn’t practical. Nuxt Islands (<NuxtIsland>) render components without shipping client-side JavaScript, cutting hydration cost and helping INPInteraction to Next Paint — the input-to-paint latency at the 75th percentile of a page's interactions. ≤200 ms is good. — the Core Web Vital that’s most often the problem on Nuxt apps.

How to verify what you actually shipped: View Source shows the raw HTML the server sent; if your content is there, you’re server-rendering. DevTools’ Elements panel shows the rendered DOM. And GSC’s URL Inspection toolA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version. shows what Google actually fetched and rendered — the source of truth. Don’t trust “it looks fine in my browser.”

How Googlebot handles a Nuxt app

Google processes any JavaScript app in three phases — crawl, render, index — and the catch is timing: rendering happens in a queue, not instantly. As I put it in my JavaScript SEO guide, the renderer is patient — “there is no fixed timeout for the renderer… It’s really patient, and you should not be concerned.” But patient isn’t the same as fast for fresh content. If you ship a client-rendered page, your content doesn’t exist for Google until that render wave runs; SSR and SSG close that gap because the HTML is complete on the first fetch.

Two more things matter at scale. Rendering JavaScript is expensive — from my 2019 JavaScript SEO talk (Ungagged), crawl costs go up by roughly 20× once Google has to render (directional, but the order of magnitude still holds). And Google takes the most restrictive directive across raw and rendered HTML — so a noindex injected by JavaScript will win over an index in the raw HTML, and vice versa. A canonical injected via JavaScript is respected only if there’s no canonical in the raw HTML already. Keep your robots and canonical signals in the server-rendered HTML, which Nuxt does for you when SSR is on.

The AI-crawler reality

This is the 2026 wrinkle. AI crawlersAI crawlers are bots from AI companies that fetch web pages to train language models, build AI-search indexes, or answer live user questions. They come in three categories, each with its own user-agent tokens and its own robots.txt controls. — GPTBot, ClaudeBot, PerplexityBot, and the rest — generally don’t execute JavaScript at all. They index the raw HTML and nothing else. So a client-rendered Nuxt page is effectively invisible to AI answer engines. SSR or SSG isn’t just better for Google here; it’s the price of entry for answer engine optimization too. If you want your content cited by ChatGPT, Perplexity, or Claude, it has to be in the HTML on first fetch.

Beyond the initial HTML: payload, hydration, and status codes

Getting server HTML with your content in it is necessary but not sufficient — several things can still go wrong downstream of that first response, and “I checked View Source” doesn’t cover them:

  • Payload and hydration. Universal rendering sends the HTML and a serialized data payload the client uses to hydrate — attach event listeners and pick up where the server left off — without re-fetching. Server HTML looking right doesn’t prove hydration succeeded, that client navigation reproduces the same content, or that the payload isn’t stale. If a page feels fine on first load but breaks after a client-side route change, that’s a hydration/payload problem, not a rendering-mode problem.
  • <ClientOnly> and browser-only content. Wrapping something in <ClientOnly> — common for browser-API-dependent widgets — means it’s absent from the server response even on an otherwise-universal route. If your main content, a key link, or your meta tags end up inside a client-only boundary, crawlers and AI bots that only read the raw HTML miss it, regardless of your rendering mode setting. Inspect the actual response, not just the rendering mode config.
  • Status codes and redirectsA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't. aren’t self-certifying. A Nuxt error page rendering in the browser, or a navigateTo()/composable-driven redirect, doesn’t by itself prove what HTTP status the direct server response sent. Google’s guidance is explicit that meaningful status codes matter for crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. and indexing — confirm the actual header with curl -I, not what the client-rendered error page displays.

None of this is an argument against universal rendering — it’s the reminder that “the HTML is server-rendered” is the first check, not the last one.

Nitro, deployment presets, and cache boundaries

Nuxt’s server output is built by Nitro, and Nitro compiles differently depending on the deployment preset you target (Node server, Cloudflare, Vercel, Netlify, static, and others). That matters for SEO because presets and adapters can differ in runtime APIs available, caching behavior, streaming support, regional deployment, and filesystem access — a route rule or server handler that works under one preset isn’t guaranteed to behave identically under another. Two consequences worth testing explicitly rather than assuming:

  • Cache keys and invalidation are route-specific, not automatic. swr and isr route rules cache and regenerate output, but a wrong cache key, missing invalidation, or a caching header set by your deployment platform can serve stale, personalized, or inconsistent HTML to crawlers. Check the actual response age and any Cache-Control/Age headers on a live URL, not just the routeRules config.
  • Production parity isn’t guaranteed by staging behavior. A route rendering correctly in local dev or a preview deployment doesn’t confirm the production preset produces the same output — server routes, redirects, and error handling live in Nitro’s server layer, and that layer is the part most likely to differ by target. Test the production URL directly after any deployment change, the same way you’d verify rendering mode.

Meta tags: useSeoMeta() and useHead()

Nuxt’s head management runs on Unhead, and it gives you two composables for different jobs.

useSeoMeta() is the one to reach for for SEO and social meta tags. It’s a flat, type-safe API with typed parameters. Evidence for this claim useSeoMeta is a typed Nuxt API for SEO and social meta tags. Scope: Current Nuxt composable. Confidence: high · Verified: Nuxt: useSeoMeta It helps avoid the classic Open GraphOpen Graph (OG) tags are `<meta>` elements in a page's head, defined by the Open Graph protocol (ogp.me, created by Facebook), that describe a page as a shareable object — its title, description, image, URL, and type. They control how a link preview card looks when the page is shared on Facebook, LinkedIn, Slack, Discord, WhatsApp, and iMessage. They are not a direct Google ranking factor, though Google reads og:title, og:image, and og:site_name as inputs to how a result appears. bug of using name where you needed property:

useSeoMeta({
  title: 'My Page Title',
  ogTitle: 'My Page Title',
  description: 'Page description under ~160 chars',
  ogDescription: 'Page description under ~160 chars',
  ogImage: 'https://mysite.com/og-image.png', // must be an absolute URL
  twitterCard: 'summary_large_image',
})

useHead() is the general-purpose head tool for everything else — scripts, link tags, body attributes, and title templates:

useHead({
  titleTemplate: '%s · My Site Name',
  htmlAttrs: { lang: 'en' },
})

The reliable layering pattern is: static defaults (charset, viewport, faviconA favicon (\"favorite icon\") is the small square image that represents a website — shown in browser tabs, bookmarks, and history, and (when the requirements are met) next to a site's listing in Google and Bing search results. It's declared with an HTML <link rel=\"icon\"> tag in the <head>. It is not a ranking factor, but it affects brand recognition and click-through rate.) in nuxt.config.ts → site-wide title template and global OG defaults in app.vue → page-specific overrides via useSeoMeta() in the page component. A common bug is putting useSeoMeta() in a layout instead of the page, which overwrites specific page tags with generic ones. And since search engines read the initial load, SEO meta generally doesn’t need to be reactive — useServerHead() skips the client-side re-execution.

Which API, and what it actually proves:

APIScopeReactive?What it proves about output
useSeoMeta()Flat, typed SEO/social meta onlyYes (default)Sets typed properties correctly — not that the route is unique, canonical, or indexable; you still own that logic
useHead()Anything in <head> — scripts, links, attrs, title templateYes (default)General head control; same caveat — API use isn’t proof of a correct server response
useServerHead() / server-only callsSame as aboveNo — server only, skips client re-executionConfirms the tag ships once in server HTML; doesn’t confirm client navigation re-sets it if you rely on it there

Calling one of these composables tells you the API ran — it doesn’t by itself tell you what a direct server response or a client-side route change actually emits. Confirm with View Source or curl, not just “I called useSeoMeta().”

The @nuxtjs/seo module ecosystem (Harlan Wilton) — optional, not core

Nuxt core doesn’t ship a sitemap, a robots.txt, OG image generation, or Schema.org out of the box — those are outside Nuxt’s own primitives (useHead, useSeoMeta, route rules, rendering modes). The community fills that gap with Harlan Wilton’s @nuxtjs/seo — a separately-installed, third-party umbrella package (nuxtseo.com, currently at v5.x, actively maintained, targeting Nuxt 3.16+ and Nuxt 4) that bundles six modules. Installing it gives you sitemap, robots, OG-image, and schema generation — it doesn’t by itself guarantee the output is correct for your routes; verify what it produces the same way you’d verify anything else:

ModuleWhat it does
@nuxtjs/robotsrobots.txt + meta robots + X-Robots-Tag headers
@nuxtjs/sitemapAuto XML sitemapsAn XML sitemap is a UTF-8 file listing the canonical URLs on your site (with optional lastmod) so search engines can discover and prioritize them. It's a discovery and diagnostic aid, not a guarantee of indexing — and Google ignores its priority and changefreq tags. from pages + dynamic routes
nuxt-og-imageDynamic OG images (a Vue template → an image)
nuxt-schema-orgSchema.org JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. structured dataStructured data is a standardized way of labeling page content (using the schema.org vocabulary in JSON-LD, Microdata, or RDFa) so search engines can understand its meaning. It's not a direct ranking factor — its value is rich results and entity understanding.
nuxt-seo-utilsCanonical URLs, breadcrumbsBreadcrumbs are a secondary navigation trail (Home > Category > Page) that shows where a page sits in a site's hierarchy. They create internal links that pass PageRank, and when marked up with BreadcrumbList structured data they can drive the path Google shows in desktop search results., defaults
nuxt-link-checkerBuild-time broken-link detection

Install the whole bundle with npx nuxt module add seo, or grab individual modules (npx nuxt module add sitemap robots). A few behaviors worth knowing:

  • @nuxtjs/sitemap auto-generates from your pages/ directory plus dynamic routes, splits into a sitemap indexA sitemap index is a sitemap of sitemaps — a single file that lists your other sitemap files instead of listing URLs directly. It's how large sites stay under the 50,000-URL / 50MB-per-sitemap limit while submitting just one file. automatically past 50,000 URLs, supports i18n multi-language sitemaps, and has built-in IndexNowIndexNow is an open push protocol that lets you instantly tell participating search engines (Bing, Yandex, Naver, Seznam, and Yep) which URLs you've added, changed, or removed via a simple HTTP request — and one submission is shared across all of them. Google does not use it. support. One thing to get right: Google ignores changefreq and priority; only an accurate lastmod matters, and only when content genuinely changes.
  • @nuxtjs/robots generates robots.txt, the robots meta tagThe robots meta tag is an HTML element in a page's head — <meta name=\"robots\" content=\"noindex\"> — that tells search engines how to index and serve that page. It's crawl-then-obey: a page blocked in robots.txt is never fetched, so the tag is never seen., and the X-Robots-Tag header — and by default it disallows all crawlers on non-production environments, which is exactly the staging-indexation footgun that bites headless builds. It also gives you per-bot rules, so you can block GPTBot specifically while leaving everyone else alone (be intentional — block an AI crawler and it won’t cite you).
  • nuxt-seo-utils handles canonical URLs and, usefully, strips tracking parameters (utm_*, fbclid, gclid) from canonicals automatically.

nuxt/image and Core Web Vitals

@nuxt/image is the image module: automatic responsive srcset, modern formats (WebP/AVIF), built-in optimization through CDN providers, and lazy loadingLazy loading defers loading of off-screen or non-critical resources — usually images and iframes — until they're about to enter the viewport. The native way to do it is the loading=\"lazy\" HTML attribute, which needs no JavaScript.. Two rules carry most of the SEO value:

  • Never lazy-load the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. image. Your hero image should load eagerly; lazy-loading it delays your Largest Contentful Paint.
  • Always set width and height so the browser reserves space and you don’t take a Cumulative Layout Shift hit.
<NuxtImg
  src="/hero.jpg"
  width="1200"
  height="630"
  alt="Descriptive alt text"
  :loading="isHeroImage ? 'eager' : 'lazy'"
  format="webp"
/>

The 2026 targets to aim for (p75): LCP ≤ 2.5s, INP ≤ 200ms, CLSCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good. ≤ 0.1. On Nuxt apps, INP is the one that tends to suffer because hydration creates input lag — which is exactly what Nuxt Islands and <NuxtIsland> are for.

Nuxt Content + SEO

If you’re using @nuxt/content (the markdown/MDX content module), the integration with @nuxtjs/seo is straightforward but has one ordering gotcha: load @nuxtjs/seo before @nuxt/content in your modules array. You can then set SEO in content frontmatter (title, description, robots, ogImage, schemaOrg) and pull it into your [slug].vue template with useSeoMeta() after fetching the content. This is the Nuxt-native version of the headless-CMSA content management system (CMS) is software that lets users create, manage, and publish digital content — like blog posts and pages — without writing raw code. WordPress, Drupal, and Joomla are the most common open-source CMS platforms. pattern, and the same “rebuild what the plugin did” discipline applies.

Common Nuxt SEO mistakes

  1. Using SPA mode (ssr: false) for content you want indexed. The most expensive mistake — and the one that makes you invisible to AI crawlers.
  2. Relative URLs for OG images. ogImage must be an absolute URL or social previews break.
  3. useSeoMeta() in a layout instead of the page — generic tags overwrite page-specific ones.
  4. Not verifying rendered HTML — View Source ≠ DevTools ≠ what Google rendered. Use URL Inspection.
  5. Blocking JS/CSS in robots.txt — Google won’t render from blocked files.
  6. Leaving the staging noindex/disallow in place after launch (or, conversely, forgetting @nuxtjs/robots blocks non-prod by default and wondering why prod is fine but a custom env isn’t).
  7. Lazy-loading the LCP image — tanks your LCP.
  8. Missing width/height on images — CLS.
  9. changefreq/priority in sitemaps — Google ignores them; only lastmod counts.
  10. Reaching for dynamic rendering. Google deprecated it — “dynamic rendering is a workaround and not a long-term solution” — and it only serves the engines you configure it for, missing Bing and every AI crawler. With Nuxt you never need it: SSR and SSG already give crawlers complete HTML.

llms.txt and AEO

llms.txt is a plain-text file — robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere.’s cousin — that helps AI tools navigate your content. The nuxt-llms module auto-generates /llms.txt and /llms-full.txt from Nuxt Content. As of late 2025 its primary consumers are MCP servers and AI coding tools (Cursor, Claude Code) rather than ChatGPT or Perplexity directly, and it’s most useful for documentation sites and technical blogs — less so for e-commerce or news. Worth adding if you’re a docs/dev site; not a priority otherwise.

Where this fits

Nuxt SEO is really a concrete application of JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. through Nuxt’s own conventions — and it overlaps heavily with the SEO for a headless CMSA headless CMS decouples content storage and editing (the backend) from how that content is rendered and delivered (the frontend), serving content over an API instead of a built-in templated 'head'. Its SEO outcomes come almost entirely from how the separate frontend renders pages. topic when your Nuxt frontend pulls from a headless backend. The principles don’t change; Nuxt just gives you good defaults and a strong module ecosystem to implement them with.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.