Guide Nuxt SEO

Nuxt ships rendu côté serveur by par défaut, but that's a per-route setting, pas a guarantee — complet HTML to robots d’exploration seulement on routes que garder it. Rendering modes, useSeoMeta(), the @nuxtjs/seo toolkit, hydration and Nitro caveats, Core Web Vitals, and the mistakes que quietly cost vous.

Première publication : 26 juin 2026 · Dernière mise à jour : 3 août 2026 · Advanced
Langues
1 indice probant sur cette page

Nuxt server-renders pages by par défaut, so a route que garde que par défaut donne robots d’exploration a complet HTML document au lieu de the vide shell a plain Vue SPA ships — but it's a per-route setting: routeRules or a global ssr:faux peut flip quelconque route to CSR, so vérifier the réel route plutôt que assuming from the framework nom. The foundational decision is rendering mode per route — SSR (par défaut), SSG via nuxt generate, or hybrid route rules — parce que meta tags, schema, and sitemaps tout come après que. Utiliser useSeoMeta() pour meta, treat Harlan Wilton's @nuxtjs/seo bundle as an optional third-party toolkit (pas Nuxt core) pour robots/sitemap/OG/schema/canonical, jamais reach pour dynamic rendering (Google deprecated it), vérifier hydration/payload and Nitro deployment-preset/cache behavior independently of server HTML, and remember que AI-crawler rendering contracts vary by provider — SSR/SSG puts content in raw HTML and maximizes coverage.

TL;DR — Nuxt’s par défaut is rendu côté serveur, so a route que garde que par défaut obtient a complet DOM au lieu de the vide shell a plain Vue SPA ships — but that’s a per-route outcome: ssr: false or a routeRules override peut turn quelconque route into CSR or hybrid, so tester the réel route, pas the project nom. Rendering strategy is the foundational decision — SSR (par défaut), SSG (nuxt generate), or hybrid routeRules — parce que meta, schema, and sitemaps tout come après it. Utiliser useSeoMeta() pour meta tags (useHead() pour the rest of the head, server-only variants quand vous don’t besoin reactivity), and treat Harlan Wilton’s @nuxtjs/seo bundle as an optional third-party toolkit pour robots/sitemap/OG/schema/canonical — pas partie of Nuxt core, and pas proof its output is correct jusqu’à vous vérifier it. Jamais utiliser dynamic rendering (Google deprecated it). AI-crawler rendering varies by provider — SSR/SSG maximizes raw-HTML coverage. Au-delà the initial HTML, vérifier hydration/payload, Nitro deployment presets and mise en cache, and direct HTTP status independently — server HTML alone doesn’t prove quelconque of ceux. The usual JS-SEO rules encore appliquer: réel <a href> liens, don’t block JS/CSS, watch rendered vs. raw HTML.

Nuxt is Vue’s réponse to the SPA problem

Vue, by itself, ships a application monopage: an HTML shell plus JavaScript que builds the DOM in le navigateur. Nuxt is the meta-framework on top of Vue (running on the Nitro server engine in Nuxt 3, with Nuxt 4 suivant in 2025), and its whole raison pour existing — from an SEO standpoint — is que it renders on le serveur by par défaut. Every page arrives as a fully-formed HTML document, qui is exactly ce que Googlebot veut to lire sans having to execute JavaScript premier. Plain Vue SEO is its propre topic with its propre échec modes; ici I’m assuming you’ve picked Nuxt precisely so vous don’t have to fight the SPA problem.

Ce is the même point I faire à propos de JavaScript généralement: “quelconque kind of SSR, static rendering, and prerendering setup is going to be fine pour moteur de recherches. Gatsby, Suivant, Nuxt, etc., are tout great.” Nuxt’s defaults are pointed the correct façon. La plupart Nuxt SEO problems are personnes turning ceux defaults off, or layering mistakes on top of les.

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

Avant meta tags, avant schema, avant sitemaps, the question que decides everything is how fait the HTML obtenir produced pour ce spécifique route? Nuxt documents universal (server) rendering as the app-wide par défaut, but que par défaut isn’t a route-level guarantee: a global ssr: false switches the whole app to client rendering, and routeRules peut assign a différent mode to quelconque URL pattern. “This is a Nuxt app” indique vous nothing à propos de how un particulier page renders — vous have to vérifier the route. Nuxt donne vous five strategies:

ModeHow vous définir itSEO impactMeilleur pour
Universal / SSRPar défaut (ssr: true)Excellent — complet HTML every requêteDynamic, personalized content
Static / SSGnuxt generateExcellent — HTML construit at deploy tempsBlogs, docs, marketing
HybridrouteRules per routeExcellent — mix per routeGrand mixed-content sites
SPA / CSRssr: falsePoor pour indexé contentDashboards, admin panels
Edge-sideDeployment targetExcellent — low TTFBGlobal performances

The official Nuxt docs are blunt à propos de pourquoi rendu côté client is the incorrect choice pour content: “Indexation and updating le contenu delivered via rendu côté client takes plus temps”, whereas with server (universal) rendering “web robots d’exploration peut directement index lune 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 pour back-office, dashboards, and games — pas anything vous vouloir indexé.

Hybrid rendering is the power déplacer pour grand sites. Route rules in nuxt.config.ts let vous définir the rendering and mise en cache 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 une page statically alors refresh it in the background — ideal pour élevé page-count e-commerce or news où a complet rebuild on every modifier isn’t practical. Nuxt Islands (<NuxtIsland>) render components sans shipping client-side JavaScript, cutting hydration cost and helping INP — the Core Web Vital that’s la plupart souvent the problem on Nuxt apps.

How to vérifier ce que vous en réalité shipped: View Source montre the raw HTML the server sent; si votre content is là, you’re server-rendering. DevTools’ Elements panel montre the rendered DOM. And GSC’s Inspection d’URL outil montre ce que Google en réalité récupéré and rendered — the source of truth. Don’t trust “it semble fine in my navigateur.”

How Googlebot handles a Nuxt app

Google processes quelconque JavaScript app in three phases — explorer, render, index — and the catch is timing: rendering se produit in a queue, pas instantly. As I put it in my JavaScript SEO guide, the renderer is patient — “là is aucun fixed timeout pour the renderer… It’s really patient, and vous ne doit pas be concerned.” But patient isn’t the même as fast pour fresh content. Si vous ship a client-rendered page, votre content doesn’t exist pour Google jusqu’à que render wave runs; SSR and SSG fermer que gap parce que the HTML is complet on the premier récupérer.

Two plus choses matter at scale. Rendering JavaScript is expensive — from my 2019 JavaScript SEO talk (Ungagged), explorer costs go up by roughly 20× une fois Google has to render (directional, but the order of magnitude encore holds). And Google takes the la plupart restrictive directive à travers raw and rendered HTML — so a noindex injected by JavaScript va win over an index in the raw HTML, and vice versa. A canonical injected via JavaScript is respected seulement si there’s aucun canonical in the raw HTML déjà. Garder votre robots and canonical signals in le serveur-rendered HTML, qui Nuxt fait pour vous quand SSR is on.

The AI-crawler reality

Ce is the 2026 wrinkle. AI robots d’exploration — GPTBot, ClaudeBot, PerplexityBot, and the rest — généralement don’t execute JavaScript at tout. Ils index the raw HTML and nothing sinon. So a client-rendered Nuxt page is effectively invisible to AI réponse engines. SSR or SSG isn’t simplement meilleur pour Google ici; it’s the price of entry pour réponse engine optimization aussi. Si vous vouloir votre content cited by ChatGPT, Perplexity, or Claude, it has to be in the HTML on premier récupérer.

Au-delà the initial HTML: payload, hydration, and code d’états

Getting server HTML with votre content in it is necessary but pas sufficient — several choses peut encore go incorrect downstream of que premier réponse, and “I vérifié View Source” doesn’t cover les:

  • Payload and hydration. Universal rendering sends the HTML and a serialized données payload the client uses to hydrate — attach event listeners and pick up où le serveur left off — sans re-fetching. Server HTML looking correct doesn’t prove hydration succeeded, que client navigation reproduces the même content, or que the payload isn’t stale. Si une page feels fine on premier charger but breaks après a client-side route modifier, that’s a hydration/payload problem, pas a rendering-mode problem.
  • <ClientOnly> and browser-only content. Wrapping something in <ClientOnly> — courant pour browser-API-dependent widgets — signifie it’s absent from le serveur réponse même on an otherwise-universal route. Si votre principal content, a clé lien, or votre meta tags fin up à l’intérieur a client-only boundary, robots d’exploration and AI bots que seulement lire the raw HTML miss it, regardless of votre rendering mode setting. Inspect the réel réponse, pas simplement the rendering mode config.
  • Code d’états and redirections aren’t self-certifying. A Nuxt error page rendering in le navigateur, or a navigateTo()/composable-driven redirection, doesn’t by itself prove ce que HTTP status the direct server réponse sent. Google’s guidance is explicit que meaningful code d’états matter pour exploration and indexation — confirmer the réel header with curl -I, pas ce que the client-rendered error page displays.

None of ce is an argument contre universal rendering — it’s the reminder que “the HTML is server-rendered” is the premier vérifier, pas the dernier un.

Nitro, deployment presets, and cache boundaries

Nuxt’s server output is construit by Nitro, and Nitro compiles differently selon the deployment preset vous target (Node server, Cloudflare, Vercel, Netlify, static, and others). Que matters pour le SEO parce que presets and adapters peut differ in runtime APIs disponible, mise en cache behavior, streaming prise en charge, regional deployment, and filesystem accès — a route rule or server handler que fonctionne sous un preset isn’t guaranteed to behave identically sous un autre. Two consequences worth testing explicitly plutôt que assuming:

  • Cache keys and invalidation are route-specific, pas automatic. swr and isr route rules cache and regenerate output, but a incorrect cache clé, manquant invalidation, or a mise en cache header définir by votre deployment platform peut serve stale, personalized, or inconsistent HTML to robots d’exploration. Vérifier the réel réponse age and quelconque Cache-Control/Age headers on a live URL, pas simplement the routeRules config.
  • Production parity isn’t guaranteed by staging behavior. A route rendering correctement in local dev or a preview deployment doesn’t confirmer the production preset produces the même output — server routes, redirections, and error handling live in Nitro’s server couche, and que couche is the partie la plupart probable to differ by target. Tester the production URL directement après quelconque deployment modifier, the même façon you’d vérifier rendering mode.

Meta tags: useSeoMeta() and useHead()

Nuxt’s head management runs on Unhead, and it donne vous two composables pour différent jobs.

useSeoMeta() is the un to reach pour pour le 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 éviter the classic Ouvrir Graph bug of en utilisant name où vous nécessaire property:

useSeoMeta({
  title: 'My Page Title',
  ogTitle: 'My Page Title',
  description: 'Concise page-specific description with the key information first',
  ogDescription: 'Concise social description tailored to this page',
  ogImage: 'https://mysite.com/og-image.png', // must be an absolute URL
  twitterCard: 'summary_large_image',
})

useHead() is the general-purpose head outil pour everything sinon — scripts, lien tags, corps attributes, and title templates:

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

The reliable layering pattern is: static defaults (charset, viewport, favicon) in nuxt.config.ts → site-wide title template and global OG defaults in app.vue → page-specific overrides via useSeoMeta() in lune page component. A courant bug is putting useSeoMeta() in a layout au lieu de lune page, qui overwrites spécifique page tags with generic ones. And since moteur de recherches lire the initial charger, SEO meta généralement doesn’t besoin to be reactive — useServerHead() skips the client-side re-execution.

Qui API, and ce que it en réalité proves:

APIScopeReactive?Ce que it proves à propos de output
useSeoMeta()Flat, typed SEO/social meta seulementYes (par défaut)Sets typed properties correctement — pas que the route is unique, canonical, or indexable; vous encore propre que logic
useHead()Anything in <head> — scripts, liens, attrs, title templateYes (par défaut)General head contrôler; même caveat — API utiliser isn’t proof of a correct server réponse
useServerHead() / server-only callsMême as ci-dessusAucun — server seulement, skips client re-executionConfirms the tag ships une fois in server HTML; doesn’t confirmer client navigation re-sets it si vous rely on it là

Appel un of ces composables indique vous the API ran — it doesn’t by itself tell vous ce que a direct server réponse or a client-side route modifier en réalité emits. Confirmer with View Source or curl, pas simplement “I called useSeoMeta().”

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

Nuxt core doesn’t ship a sitemap, a robots.txt, OG image generation, or Schema.org out of the box — ceux are outside Nuxt’s propre primitives (useHead, useSeoMeta, route rules, rendering modes). The community fills que 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) que bundles six modules. Installing it donne vous sitemap, robots, OG-image, and schema generation — it doesn’t by itself guarantee the output is correct pour votre routes; vérifier ce que it produces the même façon you’d vérifier anything sinon:

ModuleCe que it fait
@nuxtjs/robotsrobots.txt + meta robots + X-Robots-Tag headers
@nuxtjs/sitemapAuto XML sitemaps from pages + dynamic routes
nuxt-og-imageDynamic OG images (a Vue template → an image)
nuxt-schema-orgSchema.org JSON-LD données structurées
nuxt-seo-utilsURL canoniques, breadcrumbs, 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 votre pages/ directory plus dynamic routes, splits into a sitemap index automatically past 50 000 URLs, supports i18n multi-language sitemaps, and has built-in IndexNow prise en charge. Un chose to obtenir correct: Google ignores changefreq and priority; seulement an accurate lastmod matters, and seulement quand content genuinely changements.
  • @nuxtjs/robots generates robots.txt, the robots meta tag, and the X-Robots-Tag header — and by par défaut it disallows tout robots d’exploration on non-production environments, qui is exactly the staging-indexation footgun que bites headless builds. It aussi donne vous per-bot rules, so vous pouvez block GPTBot specifically pendant que leaving everyone sinon alone (be intentional — block an AI robot d’exploration and it won’t cite vous).
  • nuxt-seo-utils handles URL canoniques 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 via CDN providers, and lazy chargement. Two rules carry la plupart of the SEO valeur:

  • Jamais lazy-load the LCP image. Votre hero image devrait charger eagerly; lazy-loading it delays votre Plus grand affichage de contenu.
  • Toujours définir width and height so le navigateur reserves space and vous don’t prendre a Décalage cumulatif de mise en page hit.
<NuxtImg
  src="/hero.jpg"
  width="1200"
  height="630"
  alt="Descriptive alt text"
  :loading="isHeroImage ? 'eager' : 'lazy'"
  format="webp"
/>

The 2026 targets to aim pour (p75): LCP ≤ 2,5s, INP ≤ 200ms, CLS ≤ 0,1. On Nuxt apps, INP is the un que tends to suffer parce que hydration creates input lag — qui is exactly ce que Nuxt Islands and <NuxtIsland> are pour.

Nuxt Content + SEO

Si you’re en utilisant @nuxt/content (the markdown/MDX content module), the integration with @nuxtjs/seo is straightforward but has un ordering gotcha: charger @nuxtjs/seo avant @nuxt/content in votre modules array. Vous pouvez alors définir SEO in content frontmatter (title, description, robots, ogImage, schemaOrg) and pull it into votre [slug].vue template with useSeoMeta() après fetching le contenu. Ce is the Nuxt-native version of the headless-CMS pattern, and the même “rebuild ce que the plugin did” discipline s’applique.

Courant Nuxt SEO mistakes

  1. En utilisant SPA mode (ssr: false) pour content vous vouloir indexé. The la plupart expensive mistake — and the un que rend vous invisible to AI robots d’exploration.
  2. Relative URLs pour OG images. ogImage doit be an absolute URL or social previews break.
  3. useSeoMeta() in a layout au lieu de lune page — generic tags overwrite page-specific ones.
  4. Pas verifying rendered HTML — View Source ≠ DevTools ≠ ce que Google rendered. Utiliser Inspection d’URL.
  5. Blocking JS/CSS in robots.txt — Google won’t render from blocked fichiers.
  6. Leaving the staging noindex/disallow in placer après launch (or, conversely, forgetting @nuxtjs/robots blocks non-prod by par défaut and wondering pourquoi prod is fine but a custom env isn’t).
  7. Lazy-loading the LCP image — tanks votre LCP.
  8. Manquant width/height on images — CLS.
  9. changefreq/priority in sitemaps — Google ignores les; seulement lastmod counts.
  10. Reaching pour dynamic rendering. Google deprecated it — “dynamic rendering is a workaround and not a long-term solution” — and it seulement sert the engines vous configurer it pour, manquant Bing and every AI robot d’exploration. With Nuxt vous jamais besoin it: SSR and SSG déjà give robots d’exploration complet HTML.

llms.txt and AEO

llms.txt is a plain-text fichier — robots.txt’s cousin — que helps AI outils navigate votre content. The nuxt-llms module auto-generates /llms.txt and /llms-full.txt from Nuxt Content. As of late 2025 its principal consumers are MCP servers and AI coding outils (Cursor, Claude Code) plutôt que ChatGPT or Perplexity directement, and it’s la plupart utile pour documentation sites and technical blogs — moins so pour e-commerce or news. Worth ajout si you’re a docs/dev site; pas a priority sinon.

Où ce fits

Nuxt SEO is really a concrete application of JavaScript SEO via Nuxt’s propre conventions — and it overlaps heavily with the SEO pour a CMS headless topic quand votre Nuxt frontend pulls from a headless backend. The principles don’t modifier; Nuxt simplement donne vous bon defaults and a strong module ecosystem to implement les with.

Add an expert note

Pin an expert quote

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