| name | svelte-vitals |
| description | Use when writing or reviewing SvelteKit routes/components — svelte-vitals rule knowledge (SEO, performance, correctness, security, architecture) and how to run the scanner. |
svelte-vitals
When to use
Use this whenever you are writing or reviewing SvelteKit route files (+page.svelte, +layout.svelte) or components in this project — svelte-vitals statically checks SEO, performance, correctness, security, and architecture patterns.
Playbook
- After writing or editing code, run
npx svelte-vitals . --diff --reporter agent and fix any findings it reports.
- Before committing, run
npx svelte-vitals . --staged as a pre-commit gate.
- For a rule's full rationale, configurable options and fix examples, run
npx svelte-vitals explain <rule-id> (add --json for a structured object) or open its docs link below.
- For anything else — reporters, the config file, scoping to a change, CI, monorepos — run
npx svelte-vitals docs list and then npx svelte-vitals docs show <name>. Those guides ship inside the CLI, so they match the version installed here; prefer them over searching the web.
Rule digest
SEO
- seo/title-presence — Title presence (critical): A unique, non-empty is the single strongest on-page SEO signal and the text shown in search results and browser tabs. Fix: Add a <title> inside svelte:head (a dynamic title is fine). (docs)
- seo/description-presence — Description presence (critical): A meta description is the snippet search engines show under your title; without one they invent one from page text, often poorly. Fix: Add a inside svelte:head, or set description on your meta component. (docs)
- seo/canonical-url — Canonical URL (warning): A canonical URL tells search engines which URL is authoritative, preventing duplicate-content dilution across query strings and trailing-slash variants. Fix: Add inside svelte:head, or set the canonical prop on your meta component. (docs)
- seo/og-image — Open Graph image (warning): og:image is the preview thumbnail shown when the page is shared on social platforms; without it links render bare and get fewer clicks. Fix: Add , or set openGraph.images on your meta component. (docs)
- seo/og-title — Open Graph title (warning): og:title controls the headline shown when the page is shared on social platforms, independent of the document . Fix: Add <meta property="og:title">, or set openGraph.title on your meta component. (docs)
- seo/robots-txt — robots.txt (warning): robots.txt tells crawlers which paths they may fetch and points them to your sitemap; missing it leaves crawl behaviour to defaults. Fix: Create static/robots.txt (or a src/routes/robots.txt/+server endpoint). (docs)
- seo/sitemap-xml — sitemap.xml (warning): A sitemap.xml lists your URLs so search engines can discover and prioritise them, especially pages not well linked internally. Fix: Create static/sitemap.xml (or a src/routes/sitemap.xml/+server endpoint). (docs)
- seo/json-ld — JSON-LD structured data (info): JSON-LD structured data lets search engines render rich results (breadcrumbs, articles, products) for the page. Fix: Add a JSON-LD with literal JSON (Svelte emits the script body as-is). (docs)
- seo/html-lang — (warning): The attribute declares the page language for search engines, screen readers, and translation tools. Fix: Set the lang attribute on in src/app.html. (docs)
- seo/indexability — Indexability (info): A noindex directive removes the page from search results; an accidental noindex on a public route silently deindexes it. Fix: If this route should be indexed, drop noindex from its . (docs)
- seo/twitter-card — Twitter Card (info): twitter:card selects how the page renders when shared on X/Twitter; without it the platform falls back to a basic link (Open Graph tags are used as fallbacks for the rest). Fix: Add a twitter:card meta tag in svelte:head. (docs)
- seo/og-description — Open Graph description (warning): og:description is the summary shown under the title in social previews; without it platforms guess or show nothing, lowering click-through. Fix: Add an og:description meta tag in svelte:head. (docs)
- seo/og-url — Open Graph URL (info): og:url tells social platforms the canonical address to attribute shares and likes to, consolidating engagement on one URL. Fix: Add an og:url meta tag in svelte:head. (docs)
- seo/viewport — Viewport (warning): Without a viewport meta tag the page is not mobile-responsive, which Google penalizes under mobile-first indexing. Fix: Add the viewport meta tag (typically in src/app.html ). (docs)
- seo/sitemap-in-robots — Sitemap referenced in robots.txt (info): A Sitemap: line in robots.txt helps crawlers discover your sitemap; without it discovery relies on manual submission. Fix: Add a Sitemap: line to static/robots.txt. (docs)
- seo/json-ld-validity — JSON-LD validity (warning): Invalid JSON-LD — unparseable, or missing @context/@type — is silently ignored by search engines, so the structured data does nothing. Fix: Make the JSON-LD valid: parseable JSON with both @context (schema.org) and @type. (docs)
- seo/json-ld-deprecated-type — Deprecated structured-data type (info): Some schema types no longer produce rich results, so the markup adds weight without the SERP benefit. (docs)
- seo/json-ld-relative-url — JSON-LD relative URL (warning): Search engines need absolute URLs in structured data; a relative URL cannot be resolved reliably. Fix: Replace relative URLs in JSON-LD with absolute URLs. (docs)
- seo/json-ld-date-format — JSON-LD date format (info): Schema.org date properties expect ISO-8601; other formats may be ignored or misparsed. Fix: Format JSON-LD date properties as ISO-8601. (docs)
- seo/json-ld-placeholder — JSON-LD placeholder text (info): Leftover placeholder text (e.g. "Your Company Name", "lorem ipsum") ships misleading structured data. (docs)
- seo/json-ld-required-props — JSON-LD required properties (warning): A recognized @type missing its required properties is ineligible for the corresponding rich result. (docs)
- seo/title-length — Title length (info): A title that is too short wastes the strongest on-page signal; one that is too long is truncated in the SERP. (docs)
- seo/description-length — Description length (info): A description that is too short under-uses the SERP snippet; one that is too long is truncated by search engines. (docs)
- seo/charset — Character encoding (warning): Without a declared character encoding the browser must guess, which can render text as mojibake; is the standard declaration. Fix: Add the charset meta tag (typically the first line of in src/app.html). (docs)
- seo/image-alt — Image alt text (warning): An with no alt attribute is invisible to image search and assistive technology; a descriptive alt is an image-SEO signal. Fix: Add a descriptive alt attribute to the (or alt="" if purely decorative). (docs)
- seo/hreflang — hreflang validity (warning): A malformed hreflang code or a missing x-default breaks international targeting, so search engines may serve the wrong language version. (docs)
- seo/single-h1 — Heading hierarchy (warning): Each page should have exactly one
naming its main topic; none leaves the page without a primary heading, and several dilute the topic signal. (docs)
- seo/duplicate-title — Duplicate title (warning): Duplicate titles across pages make them compete in search results and weaken each page’s relevance signal. (docs)
- seo/duplicate-description — Duplicate description (warning): Duplicate meta descriptions give search engines no per-page summary, so they are often ignored or rewritten. (docs)
- seo/heading-level-skip — Heading order (info): Skipping a heading level breaks the document outline that search engines and assistive tech rely on to understand page structure. (docs)
- seo/ssr-disabled — SSR disabled (warning): SvelteKit's SEO guidance is to leave SSR on unless there is a good reason not to: server-rendered content is indexed more frequently and reliably, and SPA mode costs an extra network round trip before anything renders. (docs)
Performance
- performance/image-dimensions — Image dimensions (warning): An without explicit width and height triggers layout shift (CLS) as it loads, hurting Core Web Vitals and visual stability. Fix: Add explicit width and height attributes to the . (docs)
- performance/image-loading-hint — Image loading hint (info): A loading attribute lets the browser defer offscreen images; without it images load eagerly and can delay more important content. Static analysis cannot tell which image is the LCP, so this is advisory. Fix: Add loading="lazy" to offscreen elements (leave the LCP/hero image eager). (docs)
- performance/preload-missing-as — Preload missing as (warning): A
<link rel="preload"> without an as attribute is ignored by the browser (or fetched a second time), wasting the preload. Fix: Add an as attribute matching the resource type to the preload link. (docs)
- performance/font-preload-crossorigin — Font preload missing crossorigin (warning): A font preload without
crossorigin does not match the actual (CORS) font request, so the preloaded file is never used and the font downloads twice. Fix: Add the crossorigin attribute to the font preload link. (docs)
- performance/lcp-image — LCP image eager loading (warning): Lazy-loading the LCP (first/above-the-fold) image delays the largest paint and hurts Core Web Vitals. The first image is the best static proxy for the LCP candidate. Fix: Remove loading="lazy" from the first/LCP image; consider fetchpriority="high". (docs)
- performance/responsive-image — Responsive image (info): An without srcset ships one fixed-size asset to every device, wasting bytes on small screens. Static analysis cannot measure intended display size, so this is advisory. Fix: Add a srcset (and sizes) to the for responsive delivery. (docs)
- performance/render-blocking-script — Render-blocking script (warning): A synchronous )
- performance/preconnect — Preconnect third-party origin (info): Connecting to a third-party origin (DNS + TCP + TLS) is costly; a preconnect/dns-prefetch hint starts it early so the resource arrives sooner. Fix: Add a preconnect hint for the third-party origin. ()
Correctness
- correctness/each-key — Keyed each block (warning): An unkeyed {#each} adds/removes nodes at the end and rewrites the data of the DOM nodes in between when the list reorders, so element state/focus sticks to positions instead of items; a key lets Svelte insert, move, and delete the right nodes instead. (docs)
- correctness/each-index-key — Index used as each key (warning): Svelte's guidance is explicit: the key must uniquely identify the object — do not use the index. An index key gives items position-based identity, so element state (focus, inputs, transitions) sticks to positions when the list reorders or items are inserted or removed, exactly like an unkeyed block — but the visible key masks the problem. (docs)
- correctness/effect-as-derived — Effect used to derive state (warning): An $effect whose body only assigns to $state is the "useEffect → $effect" anti-pattern: it reruns after render and can cause extra passes or loops. $derived expresses the same dependency declaratively. (docs)
- correctness/effect-as-onmount — Effect used as onMount (warning): An $effect that reads no reactive value runs once after mount and never re-runs — it is an onMount in disguise, which obscures intent and misuses the reactivity system. (docs)
- correctness/unmutated-state — Unmutated $state (info): A $state that is never mutated pays for reactivity (deep proxying, tracking) it never uses; const (or $state.raw) is clearer and cheaper. (docs)
- correctness/prop-mutation — Mutated non-bindable prop (warning): Svelte's docs say plainly: don't mutate props unless they are $bindable. A plain-object prop mutation is a silent no-op (the object isn't a state proxy); a reactive-state-proxy prop mutation works but triggers the ownership_invalid_mutation dev warning only when that code path actually runs. In legacy mode, mutating methods like .push()/.splice() never trigger an update on their own — Svelte's reactivity there is based on assignments, not mutations. Neither case is caught by the compiler, so this rule catches both statically. (docs)
- correctness/stale-prop-derivation — Stale prop derivation (warning): Svelte's guidance is to treat props as though they will change: a plain
let color = type === 'danger' ? 'red' : 'green' freezes the first render's value, so the UI silently stops tracking the parent when the prop changes. In runes mode, $derived keeps the computation live at no cost; in legacy mode (export let props), a $: reactive statement does the same job. Fix: Wrap the prop-derived computation in $derived(...) (or $derived.by(() => ...) for a function body) in runes mode, or prefix the assignment with $: in legacy mode, keeping the same expression. ()
Security
- security/raw-html — Raw HTML render (warning): {@html} renders its value as unescaped HTML; if the value can contain user input and is not sanitized, it is a cross-site-scripting (XSS) vector. (docs)
- security/javascript-url — javascript: URL (warning): A javascript: URL in href/src/action executes arbitrary script on activation — an XSS / unsafe-navigation vector that also breaks under a strict Content-Security-Policy. (docs)
- security/handler-state-write — Handler writes imported state (critical): SvelteKit's docs mark this NEVER-DO-THIS: the server is one long-lived process shared by every user, so module state written during a request is visible to ALL later requests — one user's data can be served to another. (docs)
- security/server-module-state — Server module-scope state (warning): Module scope on the server is one shared, long-lived instance (SvelteKit docs: "Avoid shared state on the server"): a value reassigned during one user's request is served to every other user, and it silently resets on every deploy or restart. (docs)
- security/shared-state-import — Shared runes-state import on the server (warning): A .svelte.ts module with module-scope $state is one shared instance on the server: mutated, it leaks data between users; read-only, every request sees the same boot-time value instead of per-user data. (docs)
Architecture
- architecture/component-size — Component size (info): A very large component is hard to read, test, and reuse, and is a common sign that several responsibilities should be split out. (docs)
- architecture/prop-count — Prop count (info): A component taking many props is usually doing too much; grouping or splitting keeps its API understandable. (docs)
- architecture/private-scope-import — Private-scope import (info): A unit placed inside a private directory is written for one owner; importing it from elsewhere couples two parts of the tree that were meant to move independently, and the unit belongs higher up instead. Fix: Move this unit out of its private scope, to the directory shared by all of its importers, and update this import. (inert until configured) (docs)
- architecture/unit-entry-file — Unit entry file (info): A directory named after a unit but missing that unit's entry file is either an incomplete unit or a grouping wearing the wrong name; either way the tree no longer says what it means, and tooling that resolves by convention starts guessing. Fix: Make the directory and its entry file agree — add the entry file, or stop declaring this directory a unit. (inert until configured) (docs)
- architecture/directory-naming — Directory naming (info): A directory whose name breaks the convention its location declares stops carrying the meaning the convention gave it, and every reader — human or agent — has to open the directory to learn what it is. Fix: Rename the directory to the declared casing, or narrow the declaration that governs it. (inert until configured) (docs)
- architecture/reserved-directory-names — Reserved directory names (info): A closed set of directory names is only worth writing down if it stays closed: one directory outside it and the table stops describing the tree, so every reader has to open a directory to learn what it holds. Fix: Rename the directory to a declared name, move it under one of them, or add its name to the declaration. (inert until configured) (docs)
- architecture/reserved-name-placement — Reserved name placement (info): A name reserved for one kind of place stops carrying that meaning the moment it appears somewhere else: a reader who has met one exception has to open the directory to learn what it holds. Fix: Move the directory to one of the places declared for its name, rename it, or declare this place for the name. (inert until configured) ()