Vue SEO
Vue 3 defaults to client-side rendering, so the content isn't in the raw HTML. This is how to make Vue apps crawlable and indexable — router mode, @unhead/vue, prerendering with vite-ssg, SSR/SSG with Nuxt, hydration, and Core Web Vitals.
Vue 3 defaults to client-side rendering, so a plain Vite + Vue app ships an empty HTML shell and builds the page in the browser. Google can render it — but rendering is delayed and async data can be missed, and Bing and social scrapers often can't run the JS at all. The fixes: use Vue Router history mode (not hash mode), manage <head> with @unhead/vue, and put content in the HTML with prerendering (vite-ssg), SSR, or SSG via Nuxt. Dynamic rendering is deprecated — don't build on it.
TL;DR — Vue builds your page in the browser by default. That means the raw HTML a search engine first downloads is nearly empty — the real content only shows up after JavaScript runs. Google can usually handle this, but Bing and the botsA 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. that make social-share previews often can’t. The fixes: use Vue Router’s “history” mode for clean URLs, add a small library called
@unhead/vuefor your titles and descriptions, and for pages that need to rank, build the HTML ahead of time (prerenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. or Nuxt) instead of only in the browser.
Why Vue needs special SEO attention
When you build a site with Vue 3 the normal way (Vite + Vue), the server sends a tiny HTML file — basically an empty container — and a big JavaScript bundle. The browser runs that JavaScript to draw the actual page. This is called client-side renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. (CSR). Evidence for this claim A Vue application can render its interface in the browser with client-side JavaScript. Scope: Vue client-side rendering architecture. Confidence: high · Verified: Vue: SSR guide
A human never notices. A search engine might. The first thing a 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. downloads is that nearly-empty container. It has to run your JavaScript to see anything — and not every crawler does that well.
- Google runs an up-to-date version of Chrome and can render your Vue app. But it does the rendering later, in a queue, and if your content waits on a slow data request, Google can move on before it loads.
- Bing, DuckDuckGo, and social-preview bots (the ones that make the little card when you paste a link into X, Slack, or iMessage) are much less reliable at running JavaScript. On a plain Vue app, your social previews can come up blank.
The three things to get right
-
Use “history” mode in Vue Router, not “hash” mode. Hash-mode URLs look like
example.com/#/about. Vue Router’s own docs say hash mode “has a bad impact in SEO.” UsecreateWebHistory()so your URLs look likeexample.com/about. Evidence for this claim Vue Router recommends HTML5 history mode for normal-looking URLs and warns that hash mode has a negative SEO effect. Scope: Vue Router history modes. Confidence: high · Verified: Vue Router: History modes -
Manage your titles and 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. with
@unhead/vue. Out of the box, every Vue page shares the same title and description.@unhead/vuelets each page set its own — including the 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. and Twitter tags that drive social previews. -
Put your content in the HTML for pages that need to rank. Instead of building everything in the browser, build it ahead of time so the content is already there when a crawler arrives. The easy on-ramp is prerendering with a tool called
vite-ssg. For bigger or more dynamic sites, Nuxt (the official Vue framework for this) does it for you.
Should you just use Nuxt?
For most sites where SEO actually matters, yes — and Vue’s own documentation points
you there. Nuxt handles the rendering, the 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., 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., and 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. for
you. If your site is fairly static (a marketing site, docs, a blog), vite-ssg is
a lighter option that doesn’t require switching frameworks.
Want the deeper version — how Google’s render queue works, the @unhead/vue code,
prerendering vs. SSR, hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. mismatches, and Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data.? Switch to the
Advanced tab.
TL;DR — Vue 3 is CSR by default, so the content isn’t in the raw HTML — Google renders it later via its evergreen Chromium WRSTurning HTML, CSS, and JavaScript into the final visual page and DOM., but the render is queued and async data fetched in
onMountedcan be missed, and Bing/social scrapers often don’t run the JS at all. Non-negotiables: Vue RoutercreateWebHistory()(hash mode has “a bad impact in SEO”),<head>via@unhead/vue/useSeoMeta(), and a renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. strategy that gets content into HTML —vite-ssgprerendering for content-stable sites, Nuxt for full SSR/SSG. Dynamic rendering is deprecated;prerender-spa-pluginis legacy. Watch hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. mismatches (a server/client content delta is an SEO problem, not just a perf one) and the CSR bundle’s hit on 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.. For the framework-agnostic version of all of this, see JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript..
The default Vite + Vue scaffold is client-side rendered
A plain npm create vue@latest app (Vite + Vue) ships a near-empty HTML shell and
a JavaScript bundle. The browser executes that bundle to build the DOM. This is
fine for users and a problem for 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.: the raw HTML contains almost no
content, and everything depends on rendering.
That’s a property of the default scaffold, not a ceiling on Vue itself — Vue core
also supports server rendering (createSSRApp) and static generation as
first-class paths, and Vue’s own SSR guide steers most production SSR/SSG work
toward Nuxt rather than a hand-rolled setup. So before reaching for a fix, check
what your app is actually shipping: a bare Vite SPA with no rendering config is
CSR; the same app behind Nuxt or vite-ssg isn’t.
Vue’s own SSR guide is direct about the upside of not doing this: with server-side rendering, “the search engine 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. will directly see the fully rendered page.” The flip side is what you ship by default — content that only exists after JavaScript runs. Evidence for this claim Vue server-side rendering sends rendered HTML so crawlers can directly see page content. Scope: Vue SSR benefits; does not guarantee indexing. Confidence: high · Verified: Vue: SSR guide
(Vue 2 reached end of life in December 2023; everything here is Vue 3.)
How Googlebot handles a Vue app
Google processes JavaScript in phases — crawl the raw HTML, then render it in an evergreen, headless Chromium instance (the Web Rendering Service), then indexStoring 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. the rendered result. Two things follow from that:
- Rendering is queued and delayed. A page sits in the render queue — usually seconds to minutes, but it can spike. Content that exists only after JS runs is not immediately indexable; content in the raw HTML is.
- Async data is the real risk. Synchronous JavaScript renders reliably. Data
fetched from an API after mount is not guaranteed to be caught. Vue’s SSR guide
says it plainly: “If your app starts with a loading spinner, then fetches content
via Ajax, the crawler will not wait for you to finish. This means if you have
content fetched asynchronously on pages where SEO is important, SSR might be
necessary.” Practically: data fetched in
onMountedis client-only and invisible to crawlers that don’t render — and at risk even with the ones that do.
John Mueller has described the failure mode for SPA-style setups where the static HTML is mostly identical and all the unique content depends on JavaScript: if that JS can’t be executed properly, the pages end up looking the same to Google, and the focus stays on the boilerplate HTML rather than the JS-loaded content. The lesson is the same as the broader JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. page: get the content into the DOM, fast and reliably.
Bing and social bots are worse at this than Google. Bing has no rendering pipeline comparable to Google’s, and social preview scrapers (X, Slack, iMessage) generally don’t run JavaScript at all — so a bare CSR Vue app produces empty share previews. If Bing traffic or social sharing matters, prerendering or SSR isn’t optional.
Vue Router: use createWebHistory(), not hash mode
Vue Router has two history modes. Hash mode (createWebHashHistory()) produces
URLs like example.com/#/about; Vue Router’s docs say it “does however have a bad
impact in SEO” because everything after the # is a fragment the server ignores. Evidence for this claim Vue Router hash history uses a URL fragment that is not sent to the server and is discouraged for SEO. Scope: Vue Router hash history. Confidence: high · Verified: Vue Router: History modes
Use HTML5 history mode:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [/* ... */],
})History mode gives clean URLs (example.com/about) but requires a server
fallback — any direct request to a route has to serve index.html so Vue can
take over, otherwise direct hits 404. Configure that catch-all on your host (Nginx
try_files, an SPA rewrite on Netlify/Vercel/Cloudflare, etc.).
Scope that rewrite carefully — a catch-all that’s too broad also serves
index.html for missing static assets, real API routes, or pages you actually
want to 404. That silently undoes the soft-404 guidance below and can hide a
broken link behind a page that looks like it “worked.”
For genuine not-foundA 302 (\"Found\") is a temporary redirect: it forwards users to a new URL while telling search engines the original URL should stay in the index. It's a weak canonicalization signal, not the zero-equity dead end of SEO folklore. states, avoid soft-404s — a client-side “not found” view that
still returns 200 can get indexed as an empty shell. Google’s guidance is to
either redirectA 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. to a URL that returns a real 404 status or add a
<meta name="robots" content="noindex"> to error pages, and to use the History API
for routing between views.
Head and meta management: @unhead/vue
By default every Vue route shares one <title> and one meta descriptionThe meta description is an HTML head tag — `<meta name=\"description\" content=\"…\">` — that suggests a short summary of the page for the search snippet. It's not a Google ranking factor, and Google rewrites it the majority of the time, but a good one can still lift click-through.. The tag
history here matters because the old answers are dead ends:
vue-meta— the Nuxt 2-era library. Legacy.@vueuse/head— its successor, now sunset.@unhead/vue— the current community standard, and what you should use for a non-Nuxt Vue 3 app.
The ergonomic entry point is the useSeoMeta() composable — type-safe, XSS-safe,
and aware of 100+ 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. including 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. and Twitter Card:
import { useSeoMeta } from '@unhead/vue'
useSeoMeta({
title: 'Vue SEO Guide',
description: 'How to make a Vue 3 app crawlable and indexable.',
ogTitle: 'Vue SEO Guide',
ogDescription: 'How to make a Vue 3 app crawlable and indexable.',
twitterCard: 'summary_large_image',
})Meta can be reactive — pass a getter or computed value and the tags update when your data does. For canonical tagsA rel=\"canonical\" annotation — in the HTML <head> or an HTTP Link header — that tells search engines which URL is the preferred version of duplicate or near-duplicate content., the safest pattern is still HTML, not JS: Google’s guidance is that “the best way to set the canonical URL is to use HTML,” and if you must inject it with JavaScript, always set it to the same value the HTML would. (I tested JS canonicals at Ahrefs and found Google does respect them — it even led Google to add an exception to its docs — but HTML is still the lower-risk path.)
Pick a rendering strategy
This is the decision that actually moves the needle. Same menu as the framework- agnostic JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. page, applied to Vue.
Client-side rendering (CSR) — the default, and the risky one. Acceptable for app-like pages behind a login, dashboards, and anything you don’t need indexed. Not acceptable for content that must rank or be shared.
Prerendering with vite-ssg. For content-stable Vue 3 SPAs, vite-ssg
generates static HTML at build time — swap your build script from vite build to
vite-ssg build. It ships @unhead/vue built in. Great for marketing sites, docs,
and blogs; not a substitute for SSR on highly dynamic, per-request pages. Note that
the old webpack-era prerender-spa-plugin is legacy and effectively
unmaintained — vite-ssg (or Nuxt) is the modern replacement.
Server-side rendering. Vue’s official guide covers a manual SSR setup with
@vue/server-renderer, but it explicitly steers most projects to a meta-framework
rather than rolling your own — and for simple cases it even says “if you’re only
investigating SSR to improve the SEO of a handful of marketing pages … then you
probably want SSG instead of SSR.” If you do roll manual SSR, create the app,
router, and store fresh per request: on a long-running Node server, module-level
singletons get reused across requests, and mutating shared state with one user’s
data can leak it into another user’s response. That’s one more reason Vue’s docs
point most projects at Nuxt, which handles per-request isolation for you.
Nuxt — the recommended full solution. Nuxt gives you SSR and SSG out of the
box, built-in useSeoMeta() and head management, and the @nuxtjs/seo module for
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., 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., and 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.. For most Vue projects where SEO matters,
this is the path Vue’s own docs point to. (Nuxt has its own depth — covered
separately; treat this as the cross-link, not the deep dive.)
Dynamic rendering — don’t. Serving prerendered HTML to bots and the SPA to users was always a workaround, and Google deprecated it in 2024, removing the implementation docs. Build on SSR or SSG instead.
For documentation specifically, VitePress is the Vue-native static site
generator. Every page ships as plain HTML — no JS rendering barrier — and SEO is
configured via frontmatter (title, description, head) or the transformHead
build hook for canonical/dynamic tags.
Hydration mismatches are an SEO problem, not just a perf one
When you do SSR/SSG, the client mounts with createSSRApp() — not the plain
createApp() — and hydrates the server-rendered HTML instead of rebuilding it
from scratch. If the server HTML and the client render diverge, Vue discards and
re-renders the mismatched nodes. In SEO terms, that means the content Google indexed
from the server render can differ from what the user sees after hydration — a
quality-signal inconsistency, not just a flicker. Common causes: invalid HTML
nesting, random values in templates, and date/time discrepancies between server and
client. Vue 3.5+ adds data-allow-mismatch to suppress the mismatch warning
selectively where a difference is intentional — it quiets the console, it doesn’t
make the server and client output equivalent, so don’t reach for it just to silence
a mismatch you haven’t actually diagnosed.
Core Web Vitals are a Vue-specific concern
Default CSR Vue ships a large JS bundle that has to download, parse, and execute before the largest content paints — so 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. suffers if you do nothing. Levers:
- Code splitting (Vite’s default) and lazy-loaded routes so you don’t ship the whole app up front.
fetchpriority="high"on the LCP hero image.- Vapor Mode — an opt-in compiler mode that bypasses the virtual DOM for eligible components, which can cut hydration cost on SSR pages. It’s Vue 3.6, not 3.5 — still experimental (beta/release-candidate as of mid-2026, not yet in the stable 3.5.x line) — worth watching, not something to depend on in production today.
Structured data in Vue
Google executes JavaScript before reading structured data, so injecting 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.
works — @unhead/vue’s useHead() with a script of type
application/ld+json is the reliable pattern. Because it depends on rendering,
confirm it with URL Inspection rather than assuming Google saw it.
Where this fits
Vue SEOVue SEO is the practice of making web apps built with Vue.js crawlable, renderable, and indexable. Because Vue 3 defaults to client-side rendering, the content isn't in the raw HTML — so router mode, head management, and a rendering strategy (CSR, prerendering, SSR, or SSG) all matter. is one instance of the general JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. problem — parity, interaction, state, timing — and it shares almost everything with the 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. situation, where the rendering mode of the frontend decides the outcome.
One caveat that applies to every option above: SSR and SSG get your content into the HTML, but neither guarantees 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., rankings, Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data., or that hydration will match the server output exactly. They remove the JavaScript-rendering barrier — the rest of SEO (and correctness) is still on you.
If you remember one thing: get your content into the HTML. Everything else is detail.
AI summary
A condensed take on the Advanced version:
- The default Vite + Vue scaffold is CSR — a plain
npm create vue@latestapp ships a near-empty HTML shell and builds the DOM in the browser. That’s a property of the bare scaffold, not a ceiling on Vue itself — Vue core also supports SSR (createSSRApp) and SSG as first-class paths. - Google renders Vue, but with caveats — its evergreen Chromium WRSTurning HTML, CSS, and JavaScript into the final visual page and DOM. runs the JS,
but renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. is queued/delayed and async data (e.g. fetched in
onMounted) can be missed. Vue’s SSR docs: a 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. “will not wait for you to finish” an Ajax fetch behind a spinner. - Bing and social scrapers are worse — they often don’t run JS at all, so a bare CSR Vue app produces empty share previews.
- Router: use
createWebHistory(), not hash mode (hash has “a bad impact in SEO”). History mode needs a serverindex.htmlfallback — scoped so it doesn’t also swallow real static assets, API routes, or intended 404s. Avoid soft-404s. - Head/meta:
@unhead/vuewithuseSeoMeta()is the current standard;vue-metais legacy,@vueuse/headis sunset. Prefer HTML canonicals. - Rendering strategy decides it: CSR (risky, OK for app pages) →
vite-ssgprerendering (content-stable sites) → SSR / SSG via Nuxt (the recommended full solution).prerender-spa-pluginis legacy; dynamic rendering is deprecated. If you roll manual SSR, build the app/router/store fresh per request to avoid cross-request state leaks. - VitePress for docs sites (plain static HTML).
- HydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. uses
createSSRApp(); mismatches create a server/client content delta — an SEO quality issue, not just a flicker. Vue 3.5+‘sdata-allow-mismatchsuppresses the warning for intentional cases — it doesn’t make the two renders equivalent. - Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data.: big CSR bundle hurts 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. — code-split, lazy routes,
fetchpriority="high", and (experimental, Vue 3.6 beta/RC — not stable yet) Vapor Mode. - 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. via
@unhead/vueuseHead()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.; verify with URL Inspection. - No option here guarantees 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., rankings, CWV, or hydration parity — SSR/SSG remove the JS-rendering barrier; the rest of SEO is still on you.
Official documentation
Primary-source documentation from Vue and the search engines.
Vue
- Server-Side Rendering (SSR) | Vue.js — the SEO benefit of SSR, the async/spinner caveat, and the SSG-for-marketing-pages recommendation.
- Different History modes | Vue Router —
createWebHistory()vs. hash mode, the SEO note, and the server fallback requirement. - useSeoMeta() · Unhead — the current
@unhead/vuecomposable for type-safe meta management. - Site Config | VitePress and Frontmatter Config | VitePress — SEO configuration for Vue-powered docs sites.
- antfu-collective/vite-ssg | GitHub — build-time prerenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. for Vue 3 SPAs.
- Understand the JavaScript SEO basics — two-phase processing, the History API recommendation, soft-404 handling, and HTML canonicals.
- Dynamic Rendering as a workaround — now flagged as deprecated; SSR/static renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM./hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. recommended instead.
- Introducing a new JavaScript SEO video series — Google’s official JS SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. series (includes the Vue.js episode).
Quotes from the source
On-the-record statements from Vue and Google. Each link is a deep link that jumps to the quoted passage on the source page.
Vue — SSR & SEO
- “Better SEO: the search engine 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. will directly see the fully rendered page.” — Vue.js SSR guide. Jump to quote
- “As of now, Google and Bing can indexStoring 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. synchronous JavaScript applications just fine. Synchronous being the key word there. If your app starts with a loading spinner, then fetches content via Ajax, the 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. will not wait for you to finish.” Jump to quote
- “If you’re only investigating SSR to improve the SEO of a handful of marketing pages … then you probably want SSG instead of SSR.” Jump to quote
Vue Router — history mode
- On hash mode: “It does however have a bad impact in SEO.” Jump to quote
Google — JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. basics
- On canonicals: “The best way to set the canonical URLHow search engines pick one canonical URL among duplicates and consolidate signals onto it. is to use HTML, but if you have to use JavaScript, make sure that you always set the canonical URL to the same value as the original HTML.” Jump to quote
Google — dynamic renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. (deprecated)
- “Dynamic renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. was a workaround and not a long-term solution for problems with JavaScript-generated content in search engines.” Jump to quote
Vue SEO checklist
A pass to confirm a Vue 3 app is crawlable and indexable:
- Vue Router uses
createWebHistory()(HTML5 history mode), notcreateWebHashHistory(). - Server has an
index.htmlfallback so direct route hits don’t 404. -
@unhead/vueis wired up; every page sets its own title, description, and canonical (not all sharing the default). - 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. / Twitter Card tags are present (and actually render for social scrapers — i.e. they’re in the served HTML, not CSR-only).
- Canonical is set in HTML where possible; if injected via JS, it matches.
- SEO-critical content is in the HTML via SSR/SSG/prerenderThe Speculation Rules API is a Chromium browser API (Chrome/Edge 109+) that lets a site tell the browser which same-site pages to prefetch (download the HTML document) or prerender (fully load and render in an invisible tab) before a visitor clicks — so the next navigation can be near-instant. It's a browser-side performance feature for real users, not a crawling, indexing, or ranking signal. — not fetched in
onMountedon a CSR page. - Client-side not-foundA 302 (\"Found\") is a temporary redirect: it forwards users to a new URL while telling search engines the original URL should stay in the index. It's a weak canonicalization signal, not the zero-equity dead end of SEO folklore. states return a real
404or anoindex(no soft-404 shells). - JavaScript and CSS are not blocked in
robots.txt. - No dynamic-renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. dependency in new builds (deprecated by Google).
- Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. pass — code splitting, lazy routes,
fetchpriority="high"on 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. - If SSR/SSG: no hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. mismatches (check the console; use
data-allow-mismatchonly for intentional ones). - 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. (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.) confirmed in the rendered HTML via URL InspectionA 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..
The mental models
1. The default is the trap. Vue 3 out of the box is CSR — content isn’t in the raw HTML. Everything in Vue SEOVue SEO is the practice of making web apps built with Vue.js crawlable, renderable, and indexable. Because Vue 3 defaults to client-side rendering, the content isn't in the raw HTML — so router mode, head management, and a rendering strategy (CSR, prerendering, SSR, or SSG) all matter. is about overriding that default for pages that need to be foundA 302 (\"Found\") is a temporary redirect: it forwards users to a new URL while telling search engines the original URL should stay in the index. It's a weak canonicalization signal, not the zero-equity dead end of SEO folklore..
2. RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. decides the outcome.
The single highest-leverage choice is how the HTML is produced: CSR (risky) →
prerenderThe Speculation Rules API is a Chromium browser API (Chrome/Edge 109+) that lets a site tell the browser which same-site pages to prefetch (download the HTML document) or prerender (fully load and render in an invisible tab) before a visitor clicks — so the next navigation can be near-instant. It's a browser-side performance feature for real users, not a crawling, indexing, or ranking signal./vite-ssg (content-stable) → SSR/SSG via Nuxt (dynamic, SEO-critical).
Pick the lightest option that puts your content in the HTML.
3. Synchronous in the HTML, not async after mount. Content that’s there synchronously renders reliably; data fetched after mount may be missed — by the 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. that render late, and entirely by the ones that don’t.
4. Two audiences of bots. Google renders (eventually). Bing and social scrapers largely don’t. Design for the worse one if Bing traffic or share previews matter — that means HTML, not CSR.
5. The decision tree for a new Vue project.
Pure app behind a login? CSR is fine. Content-stable marketing/docs/blog? vite-ssg
or VitePress. Dynamic, per-request, SEO-critical content? Nuxt. Never reach for
dynamic renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. or prerender-spa-plugin — both are dead ends in 2026.
6. Server and client must agree. HydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. mismatch isn’t just a perf nit — it’s a content delta between what Google 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. and what the user gets. Keep the two renders identical.
Vue SEO — cheat sheet
Router mode
| Mode | URL | SEO |
|---|---|---|
createWebHistory() | example.com/about | Use this (needs server fallback) |
createWebHashHistory() | example.com/#/about | ”Bad impact in SEO” — avoid |
Head / meta libraries
| Library | Status |
|---|---|
vue-meta | Legacy (Nuxt 2 era) |
@vueuse/head | Sunset |
@unhead/vue (useSeoMeta()) | Current standard |
RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. strategies
| Strategy | When | SEO risk |
|---|---|---|
| CSR (default) | App pages, behind login | Highest |
vite-ssg prerenderThe Speculation Rules API is a Chromium browser API (Chrome/Edge 109+) that lets a site tell the browser which same-site pages to prefetch (download the HTML document) or prerender (fully load and render in an invisible tab) before a visitor clicks — so the next navigation can be near-instant. It's a browser-side performance feature for real users, not a crawling, indexing, or ranking signal. | Content-stable SPAs (marketing/docs/blog) | Low |
| SSR (Nuxt) | Dynamic, per-request pages | Low |
| SSG (Nuxt / VitePress) | Content known at build time | Lowest |
prerender-spa-plugin | — | Legacy/unmaintained — don’t |
| Dynamic renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. | — | Deprecated by Google — don’t |
Fast facts
- Google renders Vue (evergreen Chromium WRS) — but delayed, and async data can be missed.
- Bing + social scrapers often don’t run JS — bare CSR = empty share previews.
- Canonicals: HTML first; JS canonicals work but are riskier.
- Vue 2 is EOL (Dec 2023) — Vue 3 only.
How should a Vue route render?
Choose a Vue SEO rendering path
Vue SEO mistakes
- Shipping search landing pages as a Vite SPA shell. Use Nuxt SSR/SSG or prerenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. so essential content is in source HTML.
- Using hash mode for public content routes. Fragments do not create normal server URLs. Use history routing with host fallback configured correctly.
- Setting head 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. only after client fetches. Generate metadata from server/build data through the head integration.
- Using dynamic renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. as the long-term fix. Maintain one consistent user/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 path instead of bot-specific output.
- Testing only with Google. Raw HTML matters to other search engines, social scrapers, and tools that do not execute the application fully.
Raw HTML contains only the Vue mount element
Likely cause: the route is CSR-only. Fix: move it to Nuxt SSR/SSG or prerenderThe Speculation Rules API is a Chromium browser API (Chrome/Edge 109+) that lets a site tell the browser which same-site pages to prefetch (download the HTML document) or prerender (fully load and render in an invisible tab) before a visitor clicks — so the next navigation can be near-instant. It's a browser-side performance feature for real users, not a crawling, indexing, or ranking signal. the finite route set. Confirm: curl returns the primary content and links.
Direct route requests return 404
Likely cause: Vue Router history mode lacks server fallback or the deployment omitted generated routes. Fix: configure host rewrites for SPA-only routes or deploy actual SSR/static route output. Confirm: refresh and direct requests return the intended status and page.
Titles update in the browser but not in source
Likely cause: metadata depends on client lifecycle or an asynchronous browser fetch. Fix: resolve data during SSR/SSG and render head 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. with @unhead/vue or Nuxt’s head APIs. Confirm: raw and rendered titles, canonicals, and robots directives agree.
Hydration replaces correct server content
Likely cause: server/client data or environment branches disagree. Fix: make initial data deterministic and remove browser-only conditions from essential markup. Confirm: hydrationTurning HTML, CSS, and JavaScript into the final visual page and DOM. completes without mismatch warnings or changing indexable content.
Compare Vue raw and rendered output
curl -fsSL https://example.com/page/ > raw.html
grep -Eio '<title>[^<]+|<h1[^>]*>[^<]+|<link[^>]+rel="canonical"[^>]*' raw.htmlRun this in DevTools Console after hydrationTurning HTML, CSS, and JavaScript into the final visual page and DOM.:
({title: document.title, h1: document.querySelector('h1')?.textContent.trim(), canonical: document.querySelector('link[rel="canonical"]')?.href, links: [...document.querySelectorAll('a[href]')].length});If the important values exist only in the Console result, SSR/prerendering is incomplete.
Find hash-based content links
[...document.querySelectorAll('a[href^="#"]')].map(a => ({text: a.textContent.trim(), href: a.getAttribute('href')}));Review the list manually: in-page navigation is fine; using fragments as separate content routes is the problem.
Patrick's relevant free tools
- Raw vs. Rendered HTML Checker — See what's in your page's initial HTML versus after JavaScript runs — headless-Chrome rendering only when the page actually needs it, a rendering-strategy verdict (SSR / prerendered / CSR / hybrid), ~15 calibrated JavaScript-SEO checks (noindex, canonicals, robots.txt blocking, links, soft 404s), a side-by-side raw-vs-rendered diff, and shareable reports.
- SEO Incident Simulator — Practice thirty deterministic technical SEO incident investigations — indexability, crawl controls, redirects, sitemaps, markup, caching, DNS, bot verification, rendering, hreflang, and faceted navigation — with clearly labeled fixture evidence and Find → Fix → Verify handoffs.
- Staging vs. Production SEO Diff — Compare matched release URLs across redirects, canonicals, robots directives, hreflang, selected headers, schema eligibility, and raw or optionally rendered content with honest not-evaluated states.
Tools for Vue SEO
- URL InspectionA 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. (Google Search ConsoleA free Google service that reports how a site performs in Google Search and surfaces problems with how Google crawls, indexes, and serves it. It's first-party data straight from Google — but you don't need it to appear in results.) — the source of truth. Run a live test and check the rendered HTML, screenshot, page resources (was any JS/CSS blocked?), and console messages. This is how you confirm your Vue content, meta, and 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. actually rendered.
- Rich ResultsRich results (formerly 'rich snippets') are enhanced search listings — stars, images, prices, breadcrumbs, video thumbnails, and more — that Google and Bing build from structured data. They're a display feature, not a ranking factor, and eligibility never guarantees they'll show. Test — a fast rendered-HTML + structured-data check for a single URL without verifying the site.
- Chrome DevTools — diff View Source (raw HTML, what a non-renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. botA 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. sees) against the Elements panel (the hydrated DOM). The Console surfaces hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. mismatch warnings.
- Vue Devtools — inspect component state and router mode while you debug what’s client-only vs. server-rendered.
vite-ssg— build-time prerendering for content-stable Vue 3 SPAs (swapvite build→vite-ssg build).- JavaScript-renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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. — Ahrefs Site Audit and Screaming Frog (JS-rendering mode) execute the JS so you can diff raw vs. rendered across the whole Vue site.
- LighthouseLighthouse is Google's free, open-source tool that audits a page under simulated lab conditions and scores it 0–100 across Performance, Accessibility, Best Practices, and SEO. It's lab data — useful for debugging, not a ranking signal. / PageSpeed InsightsPageSpeed Insights (PSI) is a free Google tool at pagespeed.web.dev that reports two kinds of data for a URL: real-user field data from the Chrome UX Report and a single Lighthouse lab run with the 0–100 Performance score. Only the field Core Web Vitals are what Google uses for ranking. — catch the CSR bundle’s hit on 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. and the Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. levers (code splitting, lazy routes, image priority).
Test yourself: Vue SEO
Five quick questions on making Vue apps crawlable and indexable. Pick an answer for each, then check.
Resources worth your time
My writing
- JavaScript SEO: A Definitive Guide — my full guide to renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., DOM parityWhether the rendered DOM matches what you expect the raw HTML to become., and the framework-level decisions; includes the Vue Router history-vs-hash guidance and my JS-canonical test.
- The Beginner’s Guide to Technical SEO — where renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. and JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. fit in the bigger picture.
My speaking
- How Search Works (SlideShare) — my walkthrough of 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., rendering, 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 ranking. (My standing disclaimer applies: “This is my understanding of systems… not going to be 100% complete or accurate.”)
From around the industry
- Server-Side Rendering (SSR) | Vue.js — the official guide: SSR’s SEO benefit, the async caveat, and when to pick SSG instead.
- Different History modes | Vue Router — why hash mode hurts SEO and how to configure history mode.
- useSeoMeta() · Unhead — the current
@unhead/vuemeta API. - antfu-collective/vite-ssg | GitHub — build-time prerendering for Vue 3 SPAs.
- Site Config | VitePress — SEO for Vue-powered documentation sites.
- How Nuxt.js solves the SEO problems in Vue.js | LogRocket — the case for Nuxt as the SSR/SSG path.
- Google no longer recommends using dynamic rendering | Search Engine Land — coverage of the dynamic-rendering deprecation.
- Vue.js And SEO: How To Optimize Reactive Websites | Smashing Magazine — a widely-cited 2019 piece; useful for the async-timing experiments, but dated on tooling (pre-evergreen Chromium).
Videos
- Technical SEOTechnical SEO is the practice of making a site easy for search engines to crawl, render, index, and (now) be eligible for AI answers. It's the foundation that lets your content and links rank — not a ranking trick of its own. tips for Vue.js by Martin Splitt (Google Search Central, 2019) — Google’s own Vue-specific JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. episode: making titles, descriptions, and URLs discoverable when you build with Vue. Watch
- Google Search Central (YouTube) — Martin Splitt’s broader JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. series covers renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., the render queue, and the failure modes that apply to any Vue app. Channel
Vue SEO
Vue SEO is the practice of making web apps built with Vue.js crawlable, renderable, and indexable. Because Vue 3 defaults to client-side rendering, the content isn't in the raw HTML — so router mode, head management, and a rendering strategy (CSR, prerendering, SSR, or SSG) all matter.
Related: JavaScript SEO, Headless CMS SEO
Vue SEO
Vue SEO is the practice of making web applications built with Vue.js discoverable, crawlable, and indexable by search engines. The catch is that Vue 3 defaults to client-side renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. (CSR) — a plain Vite + Vue app ships a near-empty HTML shell and builds the DOM in the browser, so the content isn’t in the raw HTML that 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. fetch first.
Google can render Vue apps with its evergreen Chromium-based Web RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. Service, but rendering is queued and delayed, and content behind slow async API calls may be missed. Other 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. — Bing, social preview scrapers — are far less reliable at executing JavaScript. So Vue SEO comes down to a few decisions: use Vue Router’s HTML5 history mode (createWebHistory()) rather than hash mode; manage <head> with @unhead/vue; and pick a rendering strategy that gets content into the HTML — prerendering (vite-ssg), full SSR, or SSG via Nuxt for anything where SEO matters.
Vue 2 reached end of life in December 2023, so current Vue SEO is Vue 3 work. Dynamic rendering — serving prerendered HTML to bots — is deprecated by Google and shouldn’t anchor a new build.
Related: JavaScript SEO, Headless CMS SEO
Build-time retrieval analysis plus live signals for this exact article. The automatic chunk report includes a deterministic readiness score and is ready without a model download.
Search Console
sampleGA4 traffic (28d)
sampleCloudflare traffic (7d)
sampledCrUX field data (28d, phone)
sampleGoogle NLP entities
localChangelog
Updated Jul 18, 2026.
Editorial summary and recorded change details.Summary
Fixed a wrong Vapor Mode version claim (it shipped experimental in the Vue 3.6 beta/RC line, not stable Vue 3.5), reframed the CSR-by-default claim to scope it to the plain Vite scaffold rather than Vue core, and added grounded guidance on SSR per-request state, hydration-suppression limits, router-fallback scoping, and the no-outcome-guarantee caveat for SSR/SSG.
Change details
- Before
Vapor Mode (Vue 3.5) bypasses the virtual DOM for eligible components, which can cut hydration cost on SSR pages.AfterCorrected Vapor Mode to Vue 3.6 (currently beta/RC, not yet stable) rather than 3.5, and marked it experimental rather than production-ready. -
Reworded the CSR-by-default framing to attribute the default to the plain Vite + Vue scaffold, not to Vue core, since Vue also supports SSR/SSG as first-class paths.
-
Added a caution that a Vue Router history-mode server fallback must not swallow real static assets, API routes, or intended 404 responses.
-
Added createSSRApp context and clarified that Vue 3.5+'s data-allow-mismatch suppresses the hydration-mismatch warning, not the underlying content difference.
-
Added per-request app/router/store guidance for manual SSR to avoid cross-request state pollution, grounded in Vue's official SSR docs.
-
Added an explicit caveat that SSR/SSG gets content into the HTML but doesn't guarantee indexing, ranking, Core Web Vitals, or hydration parity.
Full comparison unavailable — no prior snapshot was archived for this revision.