Vue.js SEO-Friendly SPAs: Tips, Tools & Prerender Example
Amine Lahmary·Aug 2, 2026·6 min read
Why SPAs struggle with SEO
A Single Page Application (SPA) renders its content with JavaScript after the page loads. The server only sends a tiny HTML shell. When Google visits:
- The crawler reads the raw HTML — an empty shell.
- Later, Google runs the JavaScript in a second, slower wave.
- Some pages are never fully rendered or indexed. That means titles, descriptions, and content are invisible at first. The fix is simple: give bots real HTML from the start.
The tools you need for Vue SEO
Here are the best tools for Vue.js SEO in 2026. You do not need all of them — start with the ones that solve your biggest problem.
ToolWhat it doesWhen to use it@unhead/vueManages title and meta tags per pageEvery SPAvue-routerClient-side routing with clean URLsEvery SPAvite-plugin-sitemapGenerates sitemap.xml at build timeStatic routesPuppeteer prerender scriptRenders full HTML at build timeContent pagesrobots.txtTells bots what to crawlEvery siteJSON-LDStructured data for rich resultsKey pages
Let's look at each one.
1. @unhead/vue for dynamic meta tags
Every page needs its own title and description. @unhead/vue is the modern standard for Vue 3. It replaces the older vue-meta.
<script setup>
import { useHead } from '@unhead/vue'
useHead({
title: 'About Me | Amine Lahmary',
meta: [{ name: 'description', content: 'Freelance Vue.js developer in Marrakech.' }]
})
</script>
Use computed values so the tags update when your data loads from the API.
2. Clean URLs with vue-router
Use clean, descriptive URLs. Good URLs help Google and humans understand your pages.
/blog/my-first-vue-app → good
/?page=blog&post=123 → bad
Keep a catch-all 404 route so missing pages behave properly.
3. vite-plugin-sitemap
A sitemap lists every page you want indexed. vite-plugin-sitemap creates it for you during vite build. Add your static routes in dynamicRoutes:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Sitemap from 'vite-plugin-sitemap'
export default defineConfig({
plugins: [
vue(),
Sitemap({
hostname: 'https://www.your-site.dev',
dynamicRoutes: ['/about', '/contact', '/my-work', '/blog']
})
]
})
4. robots.txt
Put robots.txt in the public/ folder. Vite copies it into the build output automatically.
User-agent: *
Allow: /
Sitemap: https://www.your-site.dev/sitemap.xml
5. JSON-LD structured data
JSON-LD tells Google exactly what your page is about. Add it with useHead:
useHead({
script: [
{
type: 'application/ld+json',
children: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: 'Vue.js SEO-Friendly SPAs',
author: { '@type': 'Person', name: 'Amine Lahmary' },
datePublished: '2026-08-02'
})
}
]
})
The Vue.js SEO checklist
Follow these tips to make any Vue SPA SEO-friendly:
- Fill the default
index.html. The root file is what bots read first. Give it a real title, description, and Open Graph tags. - Set a canonical URL on every page. It tells Google which version of a URL is the official one.
- Update meta tags per route. Use
useHead()with reactive values. - Add a sitemap. Submit it in Google Search Console.
- Block private pages. Use
noindexon dashboards and admin areas. - Do not block JavaScript. Never block
.jsor.cssfrom Googlebot inrobots.txt. - Handle 404s. Add a server fallback so deep links work and return a real 404.
- Use semantic HTML. Headings in order (
h1,h2,h3) help search engines understand your content.Prerender example: full HTML at build time
Prerendering is the easiest way to give bots real HTML without moving to a server framework. Here is how it works: at build time, a headless browser visits every route, waits for Vue to render, and saves the final HTML. When Google visits later, it reads complete, indexed pages.
This is exactly the approach this site uses. Here is a working example.
Step 1: Dispatch a ready event in your Vue app
In your blog or page component, tell the prerender script when content is ready:
<script setup>
import { onMounted } from 'vue'
import { useBlog } from '@/composables/useBlog'
const { post, fetchPost } = useBlog()
onMounted(async () => {
await fetchPost(route.params.slug)
document.dispatchEvent(new Event('prerender-ready'))
})
</script>
Step 2: Write the prerender script
Create scripts/prerender.js. It uses Puppeteer to capture each route:
import puppeteer from 'puppeteer'
import { promises as fs } from 'node:fs'
import path from 'node:path'
const DIST = path.resolve('dist')
const routes = ['/blog', '/about', '/contact', '/my-work']
const browser = await puppeteer.launch({ headless: true })
for (const route of routes) {
const page = await browser.newPage()
await page.goto(`http://localhost:5173${route}`, {
waitUntil: 'networkidle0'
})
await page.evaluate(() => {
return new Promise((resolve) => {
if (document.readyState === 'complete') resolve()
document.addEventListener('prerender-ready', resolve, { once: true })
})
})
const html = await page.content()
const outputDir = path.join(DIST, route === '/' ? '' : route)
await fs.mkdir(outputDir, { recursive: true })
await fs.writeFile(path.join(outputDir, 'index.html'), html)
console.log(`Prerendered ${route}`)
}
await browser.close()
Step 3: Run it after the build
Add the prerender step to your scripts:
{
"scripts": {
"build": "vite build",
"prerender": "node scripts/prerender.js",
"postbuild": "prerender"
}
}
The result is a dist/ folder with real HTML files:
dist/
├── index.html
├── about/index.html
├── contact/index.html
├── my-work/index.html
└── blog/index.html
Search engines and social media previews read these files instantly. Users still get the fast SPA experience.
Prerender vs SSR: which one for Vue?
PrerenderingSSR (Nuxt)SetupSmall build scriptBig framework changeServer neededNo, static filesYes, Node serverBest forContent that changes rarelyReal-time, user-specific pagesSpeedVery fastSlower per request
Choose prerendering when your pages are mostly static — blog posts, landing pages, portfolio items. Choose SSR when every request must show fresh, personal data.
Final tips for a Vue.js SEO-friendly SPA
- Start with the checklist. Most Vue apps get 80% of their SEO wins from good meta tags, a sitemap, and a proper
index.html. - Add prerendering when you have many content pages that must rank.
- Keep one source of truth for routes — use the same list for your sitemap and your prerender script so nothing is missed.
- Check your output: after the build, open a prerendered HTML file and confirm the title, description, and content are there. Vue.js SPAs can absolutely rank well in Google. With the right tools and one small prerender script, your app will be found — and your content will finally be seen.
This post was written in August 2026. Prerendering and SEO tools evolve quickly, so always verify the latest docs for @unhead/vue and vite-plugin-sitemap.