Guides
Vue SPA SEO Checklist: Rendering, Routing & Hydration
A Vue app can be searchable, but an app shell plus asynchronous content creates extra failure points. This checklist tests what arrives before and after hydration.
Run a fresh DomainLens audit and use the report as your priority list.
Classify routes before choosing SSR for everything
This route inventory prevents two expensive mistakes: building SSR for private application screens that do not need it, and leaving revenue/content routes dependent on a crawler executing several API calls.
| Route type | Search requirement | Preferred delivery |
|---|---|---|
| Marketing/content | Must be discoverable and indexable | SSR or static generation with hydration |
| Product/category | Indexable when inventory/content is useful | SSR/SSG with correct status and canonical |
| Account/dashboard | Private and not indexable | Client rendering is usually fine |
| Internal search/filter state | Usually not a landing page | Controlled URLs and noindex where appropriate |
| Not found | Must disappear from search | Server 404, not a 200 SPA component |
Require useful HTML in the first response
Vue’s SSR guide describes server-rendering components to HTML and hydrating them on the client. For an indexable route, view source should already contain the main heading, primary copy, canonical links, and crawlable navigation.
A click handler on a div, a button that calls router.push, or a link created only after an intersection observer fires is not equivalent to a stable anchor with an href. Use router links for navigation and reserve buttons for actions.
<RouterLink :to="{ name: 'product', params: { slug: product.slug } }">
{{ product.name }}
</RouterLink>
Make route metadata deterministic on server and client
- Resolve the route’s data before SSR so title and canonical do not describe a loading state.
- Emit one title, description, robots directive, canonical, and social preview; remove duplicates left by the base HTML shell.
- Use a production base origin and normalized route, not window.location inside server code.
- Reset head state on navigation so metadata from the previous route cannot leak.
- Return noindex only for genuinely excluded states; Google may skip rendering a page that starts with noindex.
Handle status codes before Vue mounts
The server or edge layer must know whether a requested entity exists, moved, is private, or failed. Rendering a NotFound.vue component inside an HTTP 200 response creates a soft 404. Rendering an error message after a failed API call can also leave the original title and canonical attached.
| State | HTTP response | Page handling |
|---|---|---|
| Entity exists | 200 | Indexable according to content policy |
| Slug moved | 301/308 | Redirect before app render |
| Entity removed | 404 or 410 | Useful error UI, no canonical to another unrelated page |
| Authentication required | 401/403 or login redirect | Noindex private shell |
| Temporary upstream failure | 5xx | Do not disguise as an empty 200 page |
Treat hydration mismatch as an SEO defect
Hydration replaces or patches server markup when the client computes different output. Common causes include timestamps, random IDs, browser-only conditions, locale/timezone differences, invalid nesting, and data fetched twice with different results. A page can look acceptable after recovery while wasting main-thread time or replacing the exact text a crawler received.
- 1Run the production SSR bundle and watch browser/server logs for hydration warnings.
- 2Compare view source, the DOM with JavaScript disabled, and the hydrated DOM.
- 3Serialize the server-fetched state and reuse it during hydration instead of immediately refetching.
- 4Move browser-only personalization behind onMounted without changing essential indexable content.
- 5Test direct entry, client navigation, refresh, slow API, and missing entity cases.
Control crawl space and performance
- Generate sitemap entries only for canonical public routes backed by real content.
- Do not create crawlable URLs for every client-side filter combination unless each is a planned landing page.
- Code-split by route, but preload only critical route assets; excessive hydration JavaScript can hurt INP.
- Reserve dimensions for async components and media to prevent CLS.
- Keep the LCP element in SSR output and ensure its image request is discoverable without waiting for the app bundle.
Verify a Vue route with DomainLens and Search Console
Run DomainLens on direct production URLs to check status, rendered metadata, headings, links, indexability, and performance. Then use Search Console’s live test to compare Google’s rendered HTML. The broader JavaScript SEO rendering guide provides a source-versus-DOM debugging workflow when the two disagree.