| 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.js and server.js. There is no seo.config.js — meta is driven by route meta keys, 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.txt and sitemap.xml are generated by server.js — never add static copies under public/.
- Every public page gets: a unique title + meta description, a canonical, OG + Twitter tags, and hreflang alternates for
en and fr plus x-default pointing at the en URL.
- 404 pages carry no canonical and no hreflang — driven by
route.meta.statusCode === 404 (the is404 branch in generateHead).
- Non-indexable routes (signup, signin, verify-code, forgot/reset-password, dashboard, account, admin) set
meta.robots: 'noindex, ...' and are Disallowed in robots.txt. JSON-LD is suppressed when meta.robots includes noindex.
- Meta title/description/keywords text lives in i18n (
meta.* keys) — edit copy via the translate agent, never hardcode it in entry-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, sameAs from SOCIAL_FACEBOOK / SOCIAL_INSTAGRAM / SOCIAL_TELEGRAM env vars).
- WebSite +
SearchAction — on the Index/Home route.
- 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 in en and fr,
with x-default pointing at the en URL. Cached in memory (1h TTL).
- All auth/account/admin/dashboard paths and
/api/ are Disallowed in robots.txt.
- When you add a new public, indexable route, add its path to
publicPaths (delegate the
server.js edit to the server agent) and give it meta.title/meta.description i18n keys.
Per-page SEO checklist
- Route has
meta.title + meta.description keys (and meta.keywords if relevant), present in both locales.
- Canonical resolves to the absolute
NODE_HOST URL for the current locale (auto, except 404).
- hreflang
en / fr / x-default present (auto, except 404).
- OG image + type + Twitter card present (auto via
generateHead).
- Indexable? then it is in
publicPaths. Not indexable? then meta.robots: 'noindex, ...' and Disallowed.
- 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.