Vue & Nuxt SEO Guide: From CSR Pitfalls to SSR Success
Vue.js apps face the same SEO challenges as React. Learn how to fix them with Nuxt, and the key differences that make Nuxt the SEO powerhouse of the Vue ecosystem.
Vue.js is fantastic for building interactive UIs. But like React, a vanilla Vue SPA is invisible to search engines.
This guide covers SEO for both Vue (with workarounds) and Nuxt (the proper solution).
Part 1: Vue.js SPA SEO Challenges
The Core Problem
A Vue CLI or Vite-created Vue app renders client-side:
html<body> <div id="app"></div> <script type="module" src="/src/main.ts"></script> </body>
Search engines see an empty div.
Workaround 1: Vue Meta
Add meta tag management with @vueuse/head:
bashnpm install @vueuse/head
ts// main.ts import { createHead } from '@vueuse/head'; const head = createHead(); app.use(head);
vue<!-- pages/About.vue --> <script setup> import { useHead } from '@vueuse/head'; useHead({ title: 'About Us | MyApp', meta: [ { name: 'description', content: 'Learn about our mission' }, { property: 'og:title', content: 'About Us' }, ], }); </script>
Workaround 2: Prerendering
Use vite-plugin-prerender for static pages:
ts// vite.config.ts import prerender from 'vite-plugin-prerender'; export default { plugins: [ prerender({ routes: ['/', '/about', '/pricing'], }), ], };
When Workarounds Aren't Enough
If you need:
- Dynamic pages (blog posts, products)
- Real-time content updates
- Full SSR capabilities
It's time for Nuxt.
Part 2: Nuxt - Vue's SEO Solution
Nuxt is to Vue what Next.js is to React: the full-stack framework with SSR built in.
Setup
bashnpx nuxi init my-app cd my-app npm install npm run dev
The Power of useSeoMeta
Nuxt's useSeoMeta composable is SEO perfection:
vue<script setup> useSeoMeta({ title: 'About Us', description: 'Learn about our mission and team', ogTitle: 'About Us | MyApp', ogDescription: 'Learn about our mission and team', ogImage: '/og-about.png', twitterCard: 'summary_large_image', }); </script>
For dynamic pages:
vue<!-- pages/blog/[slug].vue --> <script setup> const route = useRoute(); const { data: post } = await useFetch(`/api/posts/${route.params.slug}`); useSeoMeta({ title: () => post.value?.title, description: () => post.value?.excerpt, ogTitle: () => post.value?.title, ogImage: () => post.value?.image, }); </script>
Nuxt Image Optimization
Install the official module:
bashnpm install @nuxt/image
ts// nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxt/image'], });
vue<template> <NuxtImg src="/hero.jpg" alt="Hero image" width="1200" height="630" loading="lazy" format="webp" /> </template>
Automatic Sitemap Generation
bashnpm install @nuxtjs/sitemap
ts// nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/sitemap'], sitemap: { hostname: 'https://mysite.com', }, });
Dynamic routes are automatically included!
robots.txt
ts// nuxt.config.ts export default defineNuxtConfig({ routeRules: { '/dashboard/**': { robots: false }, }, });
Or create public/robots.txt manually.
Common Nuxt SEO Mistakes
1. Using <script> Instead of <script setup>
The <script setup> syntax ensures proper SSR hydration.
2. Fetching Data Client-Side Only
vue<!-- Bad - won't be in initial HTML --> <script setup> const { data } = useFetch('/api/posts', { server: false }); </script> <!-- Good - SSR'd --> <script setup> const { data } = await useFetch('/api/posts'); </script>
3. Missing useHead for Static Meta
For truly static meta (like on the homepage):
ts// nuxt.config.ts export default defineNuxtConfig({ app: { head: { title: 'MyApp - The Best Thing', meta: [ { name: 'description', content: 'Default description' }, ], link: [ { rel: 'canonical', href: 'https://myapp.com' }, ], }, }, });
4. Not Using Nuxt Content for Blogs
If you have a blog, use Nuxt Content:
bashnpm install @nuxt/content
md--- title: My Blog Post description: A great blog post --- # My Blog Post Content here...
Automatic SEO, sitemap integration, and markdown rendering.
Nuxt SEO Checklist
- Use
useSeoMetaon every page - Install @nuxt/image for image optimization
- Set up @nuxtjs/sitemap
- Add robots.txt with proper rules
- Use
useFetchwith SSR (not client-only) - Add JSON-LD structured data
- Test with
nuxi previewbefore deploying
Quick Audit
bash# Build and preview npm run build npm run preview # Audit in another terminal npx rankcli audit --url http://localhost:3000
RankCLI has built-in checks for Vue and Nuxt apps. Run a free audit.
Try RankCLI
Catch SEO issues before they hurt your rankings. Run your first audit in seconds.