Vue SEO and Nuxt SEO: when a Vue app needs server rendering, and the head, routing and sitemap settings
Vue's own docs say crawlers will not wait for a spinner; here is how plain Vue and Nuxt each get full HTML and correct meta tags.
Published by AI Knows Us · Updated 21 October 2026
Vue SEO comes down to one decision and a few settings. The decision is how your HTML is produced: a plain Vue app renders in the browser, so the first response is an almost empty page, while Nuxt, Vue's full-stack framework, renders on the server by default and sends finished HTML. Vue's own docs say that if content loads after a spinner, the crawler will not wait, so pages that need to rank should be server-rendered or prerendered. After that, the settings are unique titles and descriptions per route, history-mode URLs rather than hash URLs, a sitemap and a robots file. This page covers both plain Vue and Nuxt, from vuejs.org and nuxt.com as read on 22 October 2026, when npm listed Vue 3.5.44 and Nuxt 4.6.2.
What Vue's own guide says about search
Vue's server-side rendering guide gives the clearest statement of the problem. It says Google and Bing "can index synchronous JavaScript applications just fine", and then adds the catch: "If your app starts with a loading spinner, then fetches content via Ajax, the crawler will not wait for you to finish." So if a page that matters for search fetches its content asynchronously, SSR may be necessary.
The same guide offers a cheaper answer for many sites. If you only want SSR to improve SEO on a handful of marketing pages, such as the home, about and contact pages, it says you probably want static site generation (prerendering) instead. It adds that SSG suits documentation and blogs, and that vuejs.org itself is statically generated with VitePress. For a full SSR app, the guide recommends using a Vue framework with built-in SSR support rather than wiring it yourself. Nuxt is the main one.
Google's JavaScript SEO guide, last updated 4 March 2026, agrees in its own words: server-side or pre-rendering "is still a great idea" because not all bots can run JavaScript. Our JavaScript SEO guide explains Google's crawl, render and index queue.
If you run a plain Vue app
Many Vue apps are single-page apps built with Vite and Vue Router. They can rank, but three things need care.
Use history mode, not hash mode
Vue Router's docs say createWebHistory() is the recommended mode and produces normal URLs such as https://example.com/user/id. Hash mode, from createWebHashHistory(), puts a # before the path, and the router docs say it has a bad impact on SEO. Google's guide says the same thing from its side: use the History API, and do not use fragments to load different page content. History mode needs your server to send the app for every path, which the router docs explain for common hosts.
Set the title and description per route
Google's guide says you can set or change the title and meta description with JavaScript. In a plain Vue app, that means updating them on each route change, for example from route meta in a router guard. Do not do this for the canonical tag if you can avoid it. Google says the canonical is best set in the HTML, and that JavaScript must never change it to a different value from the original.
Avoid soft 404s
A single-page app often answers every path with status 200, even when the page does not exist. Google calls these soft 404 errors and gives two fixes: redirect with JavaScript to a URL that returns a real 404 status, or add a noindex robots meta tag to the error view.
If you use Nuxt
Nuxt's rendering docs say it uses universal rendering by default "to provide better user experience, performance and to optimize search engine indexing". The server runs your Vue code and returns a fully rendered HTML page, then Vue takes over in the browser. Nuxt's docs list SEO as a benefit of universal rendering and slower indexing as a downside of client-side rendering.
Titles and meta: useSeoMeta, useHead and app.head
Nuxt's head management is powered by Unhead. Its SEO and Meta docs give three tools:
app.headinnuxt.config.tsfor tags that never change, such as a default title, the language and the favicon. The docs note it cannot take reactive data.useHead, a composable that accepts reactive input for any head tag.useSeoMeta, which defines SEO meta tags as a typed object. The docs say it helps avoid mistakes such as usingnameinstead ofpropertyon Open Graph tags.
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`) useSeoMeta({ title: () => post.value?.title, description: () => post.value?.summary, ogTitle: () => post.value?.title, ogDescription: () => post.value?.summary,
})
</script>
Add a titleTemplate such as '%s - Site Title' in app.vue to append the brand to every page. Nuxt also provides head components (<Title>, <Meta>, <Link> and others) if you prefer writing tags in the template.
Nuxt adds two tags by default: charset set to utf-8 and a viewport of width=device-width, initial-scale=1.
Route rules: prerender what you can
Nuxt's hybrid rendering lets each group of routes render differently through routeRules in nuxt.config.ts. The docs list the options, including:
| Rule | What it does | Use it for |
|---|---|---|
prerender: true | Builds the route to static HTML at build time | Home page, landing pages, docs |
isr or swr | Renders on demand and caches the result | Blog posts, product pages |
ssr: false | Renders only in the browser | Admin and account areas |
redirect | Server-side redirect | Moved or renamed URLs |
One detail for static builds: the docs say the generated 404.html is an empty shell by default, so your error page only appears once the app boots. The experimental.prerenderErrorPages option renders it at build time instead.
Sitemap, robots and structured data modules
Nuxt core does not build a sitemap. The Nuxt modules directory lists @nuxtjs/sitemap and @nuxtjs/robots, and an umbrella @nuxtjs/seo package that installs nine modules in one step, including robots, sitemap, Schema.org, OG image and a link checker. It installs with npx nuxt module add seo, or you can add the sitemap module alone. Set your site URL in its config so the sitemap and canonical URLs use the right host.
A quick decision guide
- Marketing site or blog, content the same for everyone: prerender everything, with Nuxt's
nuxt generateor VitePress. - Store or large content site with frequent changes: Nuxt with universal rendering and
isrorswrcaching. - App behind a login with a few public pages: keep the app client-rendered and prerender the public pages.
Whichever you choose, check the result the way a crawler sees it: fetch the URL with curl, look for your heading and body text in the response, then use URL Inspection in Search Console. The same approach works on other component frameworks, as in our Angular SEO guide.
Questions about Vue and Nuxt SEO
Is Vue.js bad for SEO?
No. A plain client-rendered Vue app is harder to index when content loads after a spinner, which Vue's own docs point out. Server rendering or prerendering, through Nuxt or a static generator, sends crawlers complete HTML.
Do I need Nuxt for Vue SEO?
Not always. Vue's guide says a few marketing pages are better served by static generation than SSR. For a full server-rendered site, the guide recommends a framework with SSR built in, and Nuxt does that by default.
What is the difference between useHead and useSeoMeta?
useHead manages any head tag. useSeoMeta is focused on SEO and social meta tags, with full type safety, so typos and wrong attributes are caught early.
Does hash mode in Vue Router hurt SEO?
Yes. Vue Router's docs say hash mode has a bad impact on SEO, and Google asks single-page apps to use the History API. Use createWebHistory().
How do I add a sitemap to Nuxt?
Install the @nuxtjs/sitemap module, or the @nuxtjs/seo bundle that includes it, and set your site URL in its configuration.
What an assistant reads from a Vue site
Assistants that fetch pages while answering a question read your first HTML response, just as a fast crawler does, so a server-rendered Nuxt page is far easier for them to quote than an empty Vue shell. Whether they go on to name your business when a buyer asks for a recommendation is a different question, answered outside Search Console. Measuring that, across ChatGPT, Gemini, Perplexity, Claude and Google AI Overviews, is what AI Knows Us does.