# E Xode Vue Ssr SEO

> SEO — Vue SSR Starter Kit

- Skill: `tomevault-io/e-xode-vue-ssr-seo` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/e-xode-vue-ssr-seo`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/e-xode-vue-ssr-seo/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/e-xode-vue-ssr-seo

---


# 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

1. **`robots.txt` and `sitemap.xml` are generated by `server.js`** — never add static copies under `public/`.
2. **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.
3. **404 pages carry no canonical and no hreflang** — driven by `route.meta.statusCode === 404` (the `is404` branch in `generateHead`).
4. **Non-indexable routes** (signup, signin, verify-code, forgot/reset-password, dashboard, account, admin) set `meta.robots: 'noindex, ...'` and are `Disallow`ed in `robots.txt`. JSON-LD is suppressed when `meta.robots` includes `noindex`.
5. **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 `Disallow`ed 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

1. Route has `meta.title` + `meta.description` keys (and `meta.keywords` if relevant), present in both locales.
2. Canonical resolves to the absolute `NODE_HOST` URL for the current locale (auto, except 404).
3. hreflang `en` / `fr` / `x-default` present (auto, except 404).
4. OG image + type + Twitter card present (auto via `generateHead`).
5. Indexable? then it is in `publicPaths`. Not indexable? then `meta.robots: 'noindex, ...'` and `Disallow`ed.
6. 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.

---
> Source: [e-xode/vue-ssr](https://github.com/e-xode/vue-ssr) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-16 -->

