SEO for Vue: What’s Actually Different, and How to Fix It

seo for vue

Most of the difficulty in SEO for Vue comes down to one thing: a default Vue app renders in the browser, not on the server. When a crawler requests your page, the HTML it gets back is close to empty — a single <div id="app"> and a bundle of JavaScript that hasn’t run yet. Everything that matters for ranking, from your headings to your copy to your meta tags, only appears after that JavaScript executes.

Good SEO for Vue is really the work of making sure your content exists in the HTML before a crawler needs it, and that the URLs, meta tags, and speed around it hold up. Google can render JavaScript, but it does so on a delay and not on every visit, and the crawlers behind Bing, ChatGPT, and Perplexity are far less reliable at it. If your content only exists after hydration, you are gambling that every engine renders you correctly. Here’s how to stop gambling.

The rendering choice decides everything

Before you touch a single meta tag, you have to decide how Vue produces HTML, because that one decision constrains every other SEO move you can make. A plain Vue SPA built with Vite or Vue CLI ships client-rendered markup — fast to build, hostile to crawlers. Your three real options are server-side rendering (SSR), static site generation (SSG), or prerendering.

Nuxt is the practical answer for most teams. It’s the Vue meta-framework that gives you SSR and SSG out of the box, so each route arrives as fully-formed HTML with content, headings, and meta already in place. If you can’t move to Nuxt, a prerendering step like vite-plugin-prerender or a headless-Chrome build can bake static HTML for a known list of routes — fine for a marketing site, awkward once you have thousands of dynamic pages. Pure client-side rendering is the only option you should actively avoid for anything you want indexed.

URLs: use history mode, never hash mode

Vue Router ships two modes, and the wrong one quietly wrecks your indexing. Hash mode produces URLs like example.com/#/pricing, and search engines treat everything after the # as the same page — so all your routes collapse into one indexable URL. That’s a hard limitation, not a preference.

Use createWebHistory() so you get clean paths like example.com/pricing. The catch is that history mode requires server configuration: any deep link has to fall back to index.html, or a direct hit returns a 404. On Nuxt this is handled for you. On a static host you’ll need a rewrite rule or a catch-all fallback. Get this wrong and crawlers hitting your internal links directly will see errors where your pages should be.

Meta tags and structured data need to render server-side

Every page needs a unique title, meta description, canonical, and Open Graph tags — and in a SPA, none of those exist in the initial HTML. Setting them with client-side JavaScript works for a browser but is unreliable for crawlers that don’t fully render your page. This is the single most common mistake in Vue SEO: injecting meta tags after mount and assuming Google sees them.

In Nuxt, useHead() (built on Unhead) writes those tags into the server-rendered HTML, so they’re present on first byte. For a non-Nuxt build, @unhead/vue paired with SSR or prerendering does the same. The same rule applies to JSON-LD structured data: your Article, Product, or FAQ schema has to be in the rendered source, not appended by a script that a crawler may never execute. If you can view-source and see your meta and schema without opening DevTools, you’re in good shape.

Page speed and Core Web Vitals

Vue apps carry a real weight problem: the framework, the router, your components, and every dependency ship as JavaScript the browser must download, parse, and execute before the page is interactive. That shows up in Largest Contentful Paint and Interaction to Next Paint — two of the Core Web Vitals Google uses.

The highest-leverage fixes are route-level code splitting (lazy-load components with dynamic import() so a visitor only downloads the route they’re on), pruning heavy dependencies, and letting SSR or SSG deliver meaningful content before hydration finishes. Nuxt does much of this automatically, which is another reason it wins. A prerendered marketing page will almost always beat a client-rendered one on LCP, because the text is already there while the JavaScript catches up in the background. Watch third-party scripts too — analytics, chat widgets, and font loaders often do more damage to Vue’s vitals than the framework itself, and they’re easier to defer or drop.

Redirects, sitemaps, and the crawl basics

A SPA can’t issue a real server redirect on its own — a client-side router redirect returns a 200 and moves the user with JavaScript, which is not what a crawler wants for a moved page. Genuine 301s have to happen at the server or host level, or through Nuxt’s route rules. Vue also generates no sitemap by default; you’ll want @nuxtjs/sitemap or a build step that writes sitemap.xml from your route list, plus a robots.txt that actually points to it. If your routes are dynamic — say a page per product pulled from an API — make sure the sitemap is regenerated on each build, or new pages will exist without any crawl path to find them.

Here’s the short version of what has to be true before a Vue site is genuinely crawlable:

  • Rendering — content is in the HTML via SSR, SSG, or prerender, not client-only.
  • Routing — history mode, with a server fallback so deep links don’t 404.
  • Meta — unique title, description, canonical, and OG tags rendered server-side.
  • Schema — JSON-LD present in the source, not injected after mount.
  • Speed — routes code-split, LCP element rendered before hydration.
  • Redirects — real 301s at the server, not router-only.
  • Sitemap — generated sitemap.xml referenced in robots.txt.

Where SEO Rocket fits

The hard part of Vue SEO is that these failures are invisible in a browser — your site looks perfect while crawlers see an empty shell. A site audit catches that gap by fetching pages the way a crawler does and flagging missing meta, thin rendered HTML, broken canonicals, slow-loading routes, and 404s from history-mode misconfiguration.

Site Explorer in SEO Rocket — a full domain profile: Domain Rating, organic traffic and backlinks.
Site Explorer in SEO Rocket — a full domain profile: Domain Rating, organic traffic and backlinks.

Because you can just ask SEO Rocket to run the audit and read back the issues in plain language, you don’t have to interpret a raw crawl report. From there the same workflow handles the parts that aren’t Vue-specific at all — the keyword research, competitor gap analysis, and rank tracking that decide whether your fixed pages earn traffic. And since AI engines are the crawlers least forgiving of client-side rendering, its Brand Radar shows whether your Vue pages are being cited in AI answers once they’re rendered properly. Fix the rendering first; then let the tooling tell you if it worked.

Questions? Chat with us