Panduan Vue SEO

Vue 3 defaults untuk rendering sisi klien, so konten isn't di raw HTML. ini adalah cara membuat Vue apps dapat di-crawl dan dapat diindeks — router mode, @unhead/vue, prerendering dengan vite-ssg, SSR/SSG dengan Nuxt, hydration, dan Core Web Vitals.

Pertama kali diterbitkan: 26 Jun 2026 · Terakhir diperbarui: 3 Agu 2026 · Advanced
Bahasa

Vue 3 defaults untuk rendering sisi klien, so sebuah plain Vite + Vue app ships sebuah empty HTML shell dan membangun halaman di browser. Google dapat render ini — tetapi rendering adalah delayed dan async data dapat menjadi missed, dan Bing dan social scrapers sering dapat't run JS di semua. fixes: gunakan Vue Router history mode (not hash mode), manage <head> dengan @unhead/vue, dan put konten di HTML dengan prerendering (vite-ssg), SSR, atau SSG via Nuxt. Dynamic rendering adalah deprecated — don't bangun pada ini.

TL;DR — Vue 3 adalah CSR oleh default, so konten isn’t di raw HTML — Google renders ini later via -nya evergreen Chromium WRS, tetapi render adalah queued dan async data fetched di onMounted dapat menjadi missed, dan Bing/social scrapers sering don’t run JS di semua. Non-negotiables: Vue Router createWebHistory() (hash mode memiliki “a bad impact in SEO” (terjemahan) “sebuah buruk impact di SEO”), <head> via @unhead/vue / useSeoMeta(), dan sebuah rendering strategy itu gets konten ke HTML — vite-ssg prerendering untuk konten-stable situs, Nuxt untuk full SSR/SSG. Dynamic rendering adalah deprecated; prerender-spa-plugin adalah legacy. Watch hydration mismatches (sebuah server/client konten delta adalah sebuah SEO masalah, not hanya sebuah perf one) dan CSR bundle’s hit pada LCP. untuk framework-agnostic versi dari semua dari ini, see JavaScript SEO.

default Vite + Vue scaffold adalah client-side rendered

sebuah plain npm create vue@latest app (Vite + Vue) ships sebuah near-empty HTML shell dan sebuah JavaScript bundle. browser executes itu bundle untuk bangun DOM. ini adalah fine untuk pengguna dan sebuah masalah untuk crawler: raw HTML berisi almost no konten, dan everything depends pada rendering.

itu’s sebuah property dari default scaffold, not sebuah ceiling pada Vue itself — Vue core juga mendukung server rendering (createSSRApp) dan static generation sebagai pertama-class paths, dan Vue’s own SSR guide steers sebagian besar production SSR/SSG berfungsi toward Nuxt alih-alih sebuah hand-rolled setup. So sebelum reaching untuk sebuah fix, periksa what Anda app adalah actually shipping: sebuah bare Vite SPA dengan no rendering config adalah CSR; yang sama app behind Nuxt atau vite-ssg isn’t.

Vue’s own SSR guide adalah direct tentang upside dari not doing ini: dengan rendering sisi server, “the search engine crawlers will directly see the fully rendered page.” (terjemahan) “ mesin pencari crawler akan directly see fully rendered halaman.” flip side adalah what Anda ship oleh default — konten itu hanya exists setelah 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 dari life di December 2023; everything here adalah Vue 3.)

How Googlebot handles sebuah Vue app

Google processes JavaScript di phases — crawl raw HTML, lalu render ini di sebuah evergreen, headless Chromium instance ( Web rendering Service), lalu indeks rendered hasil. Two things ikuti dari itu:

  • rendering adalah queued dan delayed. sebuah halaman sits di render queue — biasanya seconds untuk minutes, tetapi ini dapat spike. konten itu exists hanya setelah JS runs adalah not immediately dapat diindeks; konten di raw HTML adalah.
  • Async data adalah nyata risk. Synchronous JavaScript renders reliably. data fetched dari sebuah API setelah mount adalah not guaranteed untuk menjadi caught. Vue’s SSR guide says ini 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.” (terjemahan) “jika Anda app starts dengan sebuah memuat spinner, lalu fetches konten via Ajax, crawler akan not wait untuk Anda untuk finish. ini berarti jika Anda memiliki konten fetched asynchronously pada halaman where SEO adalah penting, SSR mungkin menjadi necessary.” Practically: data fetched di onMounted adalah client-hanya dan invisible untuk crawler itu don’t render — dan di risk bahkan dengan ones itu melakukan.

John Mueller memiliki described failure mode untuk SPA-style setups where static HTML adalah mostly identical dan semua unique konten depends pada JavaScript: jika itu JS dapat’t menjadi executed properly, halaman end up looking yang sama untuk Google, dan focus stays pada boilerplate HTML alih-alih JS-dimuat konten. lesson adalah yang sama sebagai broader JavaScript SEO halaman: get konten ke DOM, fast dan reliably.

Bing dan social bot adalah worse di ini daripada Google. Bing memiliki no rendering pipeline comparable untuk Google’s, dan social preview scrapers (X, Slack, iMessage) umumnya don’t run JavaScript di semua — so sebuah bare CSR Vue app produces empty share previews. jika Bing traffic atau social sharing penting, prerendering atau SSR isn’t optional.

Vue Router: gunakan createWebHistory(), not hash mode

Vue Router memiliki two history modes. Hash mode (createWebHashHistory()) produces URLs like example.com/#/about; Vue Router’s docs say ini “does however have a bad impact in SEO” (terjemahan) “melakukan however memiliki sebuah buruk impact di SEO” because everything setelah # adalah sebuah fragment 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 gunakan HTML5 history mode:

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [/* ... */],
})

History mode gives clean URLs (example.com/about) tetapi memerlukan sebuah server fallback — apa pun direct permintaan untuk sebuah route memiliki untuk sajikan index.html so Vue dapat take di atas, otherwise direct hits 404. Configure itu catch-semua pada Anda host (Nginx try_files, sebuah SPA rewrite pada Netlify/Vercel/Cloudflare, etc.).

Scope itu rewrite carefully — sebuah catch-semua itu’s too broad juga menyajikan index.html untuk missing static assets, nyata API routes, atau halaman Anda actually ingin untuk 404. itu silently undoes soft-404 guidance below dan dapat hide sebuah broken tautan behind sebuah halaman itu looks like ini “worked.” (terjemahan) “worked.”

untuk genuine not-ditemukan states, hindari soft-404s — sebuah client-side “not found” (terjemahan) “tidak ditemukan” view itu masih mengembalikan 200 dapat get terindeks sebagai sebuah empty shell. Google’s guidance adalah untuk either redirect untuk sebuah URL itu mengembalikan sebuah nyata 404 status atau tambahkan sebuah <meta name="robots" content="noindex"> untuk halaman error, dan untuk gunakan History API untuk routing antara views.

Head dan meta management: @unhead/vue

oleh default setiap Vue route shares one <title> dan one deskripsi meta. tag history here penting because old jawaban adalah dead ends:

  • vue-meta — Nuxt 2-era library. Legacy.
  • @vueuse/head — -nya successor, now sunset.
  • @unhead/vue — saat ini community standard, dan what Anda harus gunakan untuk sebuah non-Nuxt Vue 3 app.

ergonomic entry poin adalah useSeoMeta() composable — jenis-safe, XSS-safe, dan aware dari 100+ meta tags including Open Graph dan 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 dapat menjadi reactive — pass sebuah getter atau computed nilai dan tags update when Anda data melakukan. untuk tag canonical, safest pattern adalah masih HTML, not JS: Google’s guidance adalah itu “the best way to set the canonical URL is to use HTML,” (terjemahan) “ best cara untuk atur canonical URL adalah untuk gunakan HTML,” dan jika Anda harus inject ini dengan JavaScript, selalu set ini untuk yang sama nilai HTML akan. (I tested JS canonicals di Ahrefs dan ditemukan Google melakukan respect them — ini bahkan led Google untuk tambahkan sebuah exception untuk -nya docs — tetapi HTML adalah masih lower-risk path.)

Pick sebuah rendering strategy

ini adalah decision itu actually moves needle. sama menu sebagai framework- agnostic JavaScript SEO halaman, applied untuk Vue.

rendering sisi klien (CSR) — default, dan risky one. Acceptable untuk app-like halaman behind sebuah login, dashboards, dan anything Anda tidak perlu terindeks. Not acceptable untuk konten itu harus peringkat atau menjadi shared.

Prerendering dengan vite-ssg. untuk konten-stable Vue 3 SPAs, vite-ssg generates static HTML di bangun time — swap Anda bangun script dari vite build untuk vite-ssg build. ini ships @unhead/vue dibangun di. Great untuk marketing situs, docs, dan blogs; not sebuah substitute untuk SSR pada highly dynamic, per-permintaan halaman. Note itu old webpack-era prerender-spa-plugin adalah legacy dan effectively unmaintainedvite-ssg (atau Nuxt) adalah modern replacement.

rendering sisi server. Vue’s official guide covers sebuah manual SSR setup dengan @vue/server-renderer, tetapi ini explicitly steers sebagian besar projects untuk sebuah meta-framework alih-alih rolling Anda own — dan untuk sederhana cases ini bahkan 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.” (terjemahan) “jika Anda’re hanya investigating SSR untuk meningkatkan SEO dari sebuah handful dari marketing halaman … lalu Anda probably ingin SSG alih-alih SSR.” jika Anda melakukan roll manual SSR, buat app, router, dan store fresh per permintaan: pada sebuah panjang-running Node server, module-tingkat singletons get reused di seluruh permintaan, dan mutating shared state dengan one pengguna’s data dapat leak ini ke lainnya pengguna’s respons. itu’s one more alasan Vue’s docs poin sebagian besar projects di Nuxt, which handles per-permintaan isolation untuk Anda.

Evidence for this claim Module-scope singleton state in Vue SSR can leak user-specific data across requests; create application, router and store instances per request. Scope: SSR and hydration Confidence: high · Verified: Server-Side Rendering

Nuxt — recommended full solusi. Nuxt gives Anda SSR dan SSG out dari box, dibangun-di useSeoMeta() dan head management, dan @nuxtjs/seo module untuk sitemaps, robots.txt, dan data terstruktur. untuk sebagian besar Vue projects where SEO penting, ini adalah path Vue’s own docs poin untuk. (Nuxt memiliki -nya own depth — covered separately; treat ini sebagai cross-tautan, not deep dive.)

Dynamic rendering — don’t. Serving prerendered HTML untuk bot dan SPA untuk pengguna adalah selalu sebuah workaround, dan Google deprecated ini di 2024, menghapus implementation docs. bangun pada SSR atau SSG instead.

untuk documentation specifically, VitePress adalah Vue-native static situs generator. setiap halaman ships sebagai plain HTML — no JS rendering barrier — dan SEO adalah configured via frontmatter (title, description, head) atau transformHead bangun hook untuk canonical/dynamic tags.

Hydration mismatches adalah sebuah SEO masalah, not hanya sebuah perf one

When Anda melakukan SSR/SSG, client mounts dengan createSSRApp() — not plain createApp() — dan hydrates server-rendered HTML alih-alih rebuilding ini dari scratch. jika server HTML dan client render diverge, Vue discards dan re-renders mismatched nodes. di SEO istilah, itu berarti konten Google terindeks dari server render dapat differ dari what pengguna sees setelah hydration — sebuah quality-signal inconsistency, not hanya sebuah flicker. umum causes: invalid HTML nesting, random nilai di templates, dan date/time discrepancies antara server dan client. Vue 3.5+ menambahkan data-allow-mismatch untuk suppress mismatch warning selectively where sebuah difference adalah intentional — ini quiets console, ini doesn’t membuat server dan client output equivalent, so don’t reach untuk ini hanya untuk silence sebuah mismatch Anda haven’t actually diagnosed.

Core Web Vitals adalah sebuah Vue-spesifik concern

Default CSR Vue ships sebuah besar JS bundle itu memiliki untuk download, parse, dan execute sebelum largest konten paints — so LCP suffers jika Anda melakukan nothing. Levers:

  • Code splitting (Vite’s default) dan lazy-dimuat routes so Anda tidak ship whole app up front.
  • fetchpriority="high" pada LCP hero image.
  • Vapor Mode — sebuah opt-di compiler mode itu bypasses virtual DOM untuk eligible components, which dapat cut hydration cost pada SSR halaman. ini adalah Vue 3,6, not 3,5 — masih experimental (beta/release-candidate sebagai dari mid-2026, not yet di stable 3,5.x line) — worth watching, not something untuk depend pada di production today.

data terstruktur di Vue

Google executes JavaScript sebelum reading data terstruktur, so injecting JSON-LD berfungsi — @unhead/vue’s useHead() dengan sebuah script dari jenis application/ld+json adalah reliable pattern. Because ini depends pada rendering, confirm ini dengan pemeriksaan URL alih-alih assuming Google saw ini.

Where ini fits

Vue SEO adalah one instance dari umum JavaScript SEO masalah — parity, interaction, state, timing — dan ini shares almost everything dengan CMS headless situation, where rendering mode dari frontend decides outcome.

One caveat itu applies untuk setiap option above: SSR dan SSG get Anda konten ke HTML, tetapi neither guarantees pengindeksan, rankings, Core Web Vitals, atau itu hydration akan match server output exactly. mereka hapus JavaScript-rendering barrier — rest dari SEO (dan correctness) adalah masih pada Anda.

jika Anda remember one thing: get Anda konten ke HTML. Everything else adalah detail.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.