name: seo description: "SEO reference for the Vue SSR Starter Kit (e-xode/vue-ssr): how the kit's server-side SEO is wired and the per-page checklist. Covers meta injection in src/entry-server.js (title/description/keywords/robots/theme-color/author built from route meta i18n keys via t()), JSON-LD structured data (Organization site-wide, WebSite + SearchAction on the home route, BreadcrumbList on deep pages, suppressed on noindex routes), Open Graph + Twitter Cards, bilingual hreflang alternates (en/fr + x-default to /en), and the dynamic robots.txt + sitemap.xml served by server.js (public paths only, 1h cache). Trigger on any SEO work: meta tags, canonical, structured data, sitemap, robots, hreflang, Core Web Vitals, or per-page SEO checklists. Don't use for: routing internals (→ vue-ssr-architecture), translation copy of meta keys (→ translate), off-site/LinkedIn marketing content (→ marketing-content/content-strategy), visual/perf styling (→ design-scss)."
SEO — Vue SSR Starter Kit
Every public page is fully server-rendered with meta, structured data, and hreflang so it ranks and earns rich results. SEO copy lives in i18n; the wiring lives in
entry-server.jsandserver.js. There is noseo.config.js— meta is driven by routemetakeys, not a central config file.
How SEO is wired
| Layer | Where | Responsibility |
|---|---|---|
| Route metadata | src/router.js |
meta.title / meta.description / meta.keywords (i18n keys), meta.robots, meta.statusCode |
| SSR injection | src/entry-server.js |
generateHead() builds the full <head> per route (meta, canonical, hreflang, OG/Twitter, JSON-LD) |
| Meta copy | src/translate/{en,fr}.json |
the meta.* strings resolved via t() — edit copy here (via the translate agent) |
| robots / sitemap | server.js |
dynamic robots.txt + sitemap.xml (NOT static files) |
Meta resolution: resolvePageMeta(route, t) reads route.meta.title/description/keywords as i18n
keys and resolves them with t(). The page title renders as ${pageTitle} — ${APP_NAME}.
Hard rules
robots.txtandsitemap.xmlare generated byserver.js— never add static copies underpublic/.- Every public page gets: a unique title + meta description, a canonical, OG + Twitter tags, and hreflang alternates for
enandfrplusx-defaultpointing at theenURL. - 404 pages carry no canonical and no hreflang — driven by
route.meta.statusCode === 404(theis404branch ingenerateHead). - Non-indexable routes (signup, signin, verify-code, forgot/reset-password, dashboard, account, admin) set
meta.robots: 'noindex, ...'and areDisallowed inrobots.txt. JSON-LD is suppressed whenmeta.robotsincludesnoindex. - Meta title/description/keywords text lives in i18n (
meta.*keys) — edit copy via thetranslateagent, never hardcode it inentry-server.js.
JSON-LD structured data (already emitted)
getSchemaMarkup() in entry-server.js emits, for indexable routes only:
- Organization — site-wide (name =
APP_NAME,url,logo,sameAsfromSOCIAL_FACEBOOK/SOCIAL_INSTAGRAM/SOCIAL_TELEGRAMenv vars). - WebSite +
SearchAction— on theIndex/Homeroute. - BreadcrumbList — on deep pages (more than one path segment).
Optional enhancements (not currently emitted — add deliberately if a page warrants it):
SoftwareApplication (to describe the kit as a product), FAQPage, Person. Validate any
structured-data change with Google's Rich Results Test.
Open Graph, Twitter, and head extras
generateHead also emits: OG (og:title/description/url/type=website/site_name/image 1200×630
/og-image.png + og:image:alt + og:locale + og:locale:alternate), fb:app_id (when
FACEBOOK_APP_ID is set), Twitter summary_large_image, theme-color (theme-dependent), author
(= APP_NAME), apple-touch-icon, favicons, and the web manifest.
Environment inputs: NODE_HOST (canonical/site URL, defaults to http://localhost:3002), APP_NAME,
FACEBOOK_APP_ID, SOCIAL_*. Locales come from LOCALE_CODES (en, fr) in src/shared/const.js.
Sitemap coverage (server.js generateSitemap)
Public, indexable URLs only — one <url> per locale with hreflang alternates, <lastmod>,
changefreq, and priority:
publicPaths = ['', '/contact']→ home (priority 1.0) and contact (0.8), each inenandfr, withx-defaultpointing at theenURL. Cached in memory (1h TTL).- All auth/account/admin/dashboard paths and
/api/areDisallowed inrobots.txt. - When you add a new public, indexable route, add its path to
publicPaths(delegate theserver.jsedit to theserveragent) and give itmeta.title/meta.descriptioni18n keys.
Per-page SEO checklist
- Route has
meta.title+meta.descriptionkeys (andmeta.keywordsif relevant), present in both locales. - Canonical resolves to the absolute
NODE_HOSTURL for the current locale (auto, except 404). - hreflang
en/fr/x-defaultpresent (auto, except 404). - OG image + type + Twitter card present (auto via
generateHead). - Indexable? then it is in
publicPaths. Not indexable? thenmeta.robots: 'noindex, ...'andDisallowed. - Appropriate JSON-LD emitted (Organization always; WebSite on home; BreadcrumbList on deep pages) and validated.
Performance is SEO
Core Web Vitals affect ranking. The kit already favors SSR with hydration, self-hosted fonts
(@fontsource/inter, @fontsource/ibm-plex-mono — no render-blocking Google Fonts), lazy-loaded
routes, and compression. Flag regressions as ➜ delegate to design (visual/CSS) or note for the
orchestrator (code). ➜ See skill: vue3-performance for client/runtime perf, vue-ssr-deployment for build.