---
title: Rules
description: Every check svelte-vitals runs, grouped by category.
---

Every rule svelte-vitals can report, grouped by category. Each rule links to a reference page describing what it checks, why it matters, and how to fix it.

The severities below are the defaults. See [Configuration](/guides/configuration) to change a rule's severity or turn it off.

{/* rules-index:start */}

<CardGroup cols={2}>
  <Card title="SEO" icon="search" href="/rules/seo">
    Resolved &lt;head&gt; metadata, structured data, and crawlability: what search engines actually see.
  </Card>
  <Card title="Performance" icon="zap" href="/rules/performance">
    Images, render-blocking assets, imports, and load waterfalls: what makes a route slow.
  </Card>
  <Card title="Correctness" icon="circle-check" href="/rules/correctness">
    Svelte 5 runes and lifecycle misuse: code that compiles but behaves wrong.
  </Card>
  <Card title="Security" icon="shield" href="/rules/security">
    Unescaped HTML, unsafe URLs, and server state that leaks across requests.
  </Card>
  <Card title="Architecture" icon="layers" href="/rules/architecture">
    Component size, prop count, and the import boundaries a project declares: signals that code is placed or shaped
    wrong.
  </Card>
  <Card title="Accessibility" icon="person-standing" href="/rules/a11y">
    ARIA validity, landmark structure, accessible names, and semantic markup: what makes a site usable for everyone.
  </Card>
</CardGroup>

## SEO

| Rule                                                                | Severity    | Summary                                                                                                  |
| ------------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------- |
| [`seo/title-presence`](/rules/seo/title-presence)                   | 🔴 critical | Every route should resolve a non-empty &lt;title&gt;.                                                    |
| [`seo/canonical-url`](/rules/seo/canonical-url)                     | 🟡 warning  | Every route should include a &lt;link rel="canonical"&gt; tag.                                           |
| [`seo/charset`](/rules/seo/charset)                                 | 🟡 warning  | The page should declare a character encoding with &lt;meta charset&gt;.                                  |
| [`seo/description-presence`](/rules/seo/description-presence)       | 🟡 warning  | Every route should include a &lt;meta name="description"&gt;.                                            |
| [`seo/duplicate-description`](/rules/seo/duplicate-description)     | 🟡 warning  | Each route should have a unique meta description.                                                        |
| [`seo/duplicate-title`](/rules/seo/duplicate-title)                 | 🟡 warning  | Each route should have a unique &lt;title&gt;.                                                           |
| [`seo/hreflang`](/rules/seo/hreflang)                               | 🟡 warning  | hreflang alternates should use valid codes; x-default is recommended for selector/redirect pages.        |
| [`seo/html-lang`](/rules/seo/html-lang)                             | 🟡 warning  | Your project's app.html should set the lang attribute on &lt;html&gt;.                                   |
| [`seo/image-alt`](/rules/seo/image-alt)                             | 🟡 warning  | Every &lt;img&gt; should have an alt attribute.                                                          |
| [`seo/json-ld-relative-url`](/rules/seo/json-ld-relative-url)       | 🟡 warning  | URLs in JSON-LD should be absolute.                                                                      |
| [`seo/json-ld-required-props`](/rules/seo/json-ld-required-props)   | 🟡 warning  | A recognized @type should include the properties its rich result requires.                               |
| [`seo/json-ld-validity`](/rules/seo/json-ld-validity)               | 🟡 warning  | A page's JSON-LD must be valid JSON with @context and @type.                                             |
| [`seo/og-image`](/rules/seo/og-image)                               | 🟡 warning  | Every route should include a &lt;meta property="og:image"&gt; tag.                                       |
| [`seo/og-title`](/rules/seo/og-title)                               | 🟡 warning  | Every route should include a &lt;meta property="og:title"&gt; tag.                                       |
| [`seo/og-url`](/rules/seo/og-url)                                   | 🟡 warning  | Every route should include an og:url with its canonical address.                                         |
| [`seo/robots-txt`](/rules/seo/robots-txt)                           | 🟡 warning  | Your project should provide a robots.txt file.                                                           |
| [`seo/single-h1`](/rules/seo/single-h1)                             | 🟡 warning  | Each page should have exactly one &lt;h1&gt;.                                                            |
| [`seo/sitemap-xml`](/rules/seo/sitemap-xml)                         | 🟡 warning  | Your project should provide a sitemap.xml file.                                                          |
| [`seo/ssr-disabled`](/rules/seo/ssr-disabled)                       | 🟡 warning  | export const ssr = false makes a route's content invisible to non-JS crawlers and slower to first paint. |
| [`seo/viewport`](/rules/seo/viewport)                               | 🟡 warning  | Pages should declare a responsive viewport meta tag.                                                     |
| [`seo/description-length`](/rules/seo/description-length)           | 🔵 info     | The meta description should be 70–160 characters.                                                        |
| [`seo/heading-level-skip`](/rules/seo/heading-level-skip)           | 🔵 info     | Heading levels should not be skipped.                                                                    |
| [`seo/indexability`](/rules/seo/indexability)                       | 🔵 info     | A route should not be accidentally set to noindex.                                                       |
| [`seo/json-ld`](/rules/seo/json-ld)                                 | 🔵 info     | Every route should include JSON-LD structured data.                                                      |
| [`seo/json-ld-date-format`](/rules/seo/json-ld-date-format)         | 🔵 info     | Date properties in JSON-LD should be ISO-8601.                                                           |
| [`seo/json-ld-deprecated-type`](/rules/seo/json-ld-deprecated-type) | 🔵 info     | Some schema types had their Google rich result dropped or restricted.                                    |
| [`seo/json-ld-placeholder`](/rules/seo/json-ld-placeholder)         | 🔵 info     | JSON-LD should not contain unreplaced placeholder text.                                                  |
| [`seo/og-description`](/rules/seo/og-description)                   | 🔵 info     | Every route should include an og:description.                                                            |
| [`seo/sitemap-in-robots`](/rules/seo/sitemap-in-robots)             | 🔵 info     | robots.txt should point crawlers at your sitemap.                                                        |
| [`seo/title-length`](/rules/seo/title-length)                       | 🔵 info     | The document title should be 30–60 characters.                                                           |
| [`seo/twitter-card`](/rules/seo/twitter-card)                       | 🔵 info     | Pages should declare a twitter:card for rich sharing on X/Twitter.                                       |

## Performance

| Rule                                                                                  | Severity   | Summary                                                                                                                                                               |
| ------------------------------------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`performance/font-preload-crossorigin`](/rules/performance/font-preload-crossorigin) | 🟡 warning | A font preload must set crossorigin so the preloaded file is actually used.                                                                                           |
| [`performance/image-dimensions`](/rules/performance/image-dimensions)                 | 🟡 warning | Every &lt;img&gt; should have explicit width and height attributes.                                                                                                   |
| [`performance/lcp-image`](/rules/performance/lcp-image)                               | 🟡 warning | The first (likely LCP) image should not be lazy-loaded.                                                                                                               |
| [`performance/load-waterfall`](/rules/performance/load-waterfall)                     | 🟡 warning | Dependent sequential awaits in a universal load cost a network round trip from the browser per hop.                                                                   |
| [`performance/minify-disabled`](/rules/performance/minify-disabled)                   | 🟡 warning | A build.minify:false left in vite.config ships unminified JS/CSS to production.                                                                                       |
| [`performance/preload-missing-as`](/rules/performance/preload-missing-as)             | 🟡 warning | Every &lt;link rel="preload"&gt; should declare an as attribute.                                                                                                      |
| [`performance/render-blocking-script`](/rules/performance/render-blocking-script)     | 🟡 warning | A head &lt;script src&gt; should not block parsing.                                                                                                                   |
| [`performance/heavy-import`](/rules/performance/heavy-import)                         | 🔵 info    | Avoid importing large, non-tree-shakeable packages.                                                                                                                   |
| [`performance/iframe-loading`](/rules/performance/iframe-loading)                     | 🔵 info    | An offscreen iframe eagerly loads an entire third-party document of scripts, fonts and media, usually costing more than an offscreen image; loading="lazy" defers it. |
| [`performance/image-loading-hint`](/rules/performance/image-loading-hint)             | 🔵 info    | Every &lt;img&gt; should have an explicit loading attribute.                                                                                                          |
| [`performance/namespace-import`](/rules/performance/namespace-import)                 | 🔵 info    | Prefer named imports over import \* as for tree-shaking.                                                                                                              |
| [`performance/preconnect`](/rules/performance/preconnect)                             | 🔵 info    | Preconnect to third-party origins used by the page.                                                                                                                   |
| [`performance/responsive-image`](/rules/performance/responsive-image)                 | 🔵 info    | Large images should provide a srcset.                                                                                                                                 |
| [`performance/sequential-awaits`](/rules/performance/sequential-awaits)               | 🔵 info    | Awaits that don't use each other's results still run one after another; start them together.                                                                          |
| [`performance/state-raw`](/rules/performance/state-raw)                               | 🔵 info    | Object/array $state that is only ever reassigned pays for deep reactivity it never uses; $state.raw skips the proxy.                                                  |

## Correctness

| Rule                                                                                    | Severity    | Summary                                                                                                                                                               |
| --------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`correctness/orphan-effect`](/rules/correctness/orphan-effect)                         | 🔴 critical | An $effect created outside component initialisation throws effect_orphan at runtime.                                                                                  |
| [`correctness/orphan-lifecycle`](/rules/correctness/orphan-lifecycle)                   | 🔴 critical | onMount, getContext and friends called outside component initialisation throw lifecycle_outside_component at runtime.                                                 |
| [`correctness/server-browser-global`](/rules/correctness/server-browser-global)         | 🔴 critical | window, document, localStorage accessed in module scope or a load/handler crash SSR with a ReferenceError.                                                            |
| [`correctness/autoplay-muted`](/rules/correctness/autoplay-muted)                       | 🟡 warning  | Browsers block autoplay with audio, and a blocked autoplay does not error, so a &lt;video autoplay&gt; without muted silently never starts playing for real visitors. |
| [`correctness/base-path-navigation`](/rules/correctness/base-path-navigation)           | 🟡 warning  | A hardcoded root-relative link resolves against the domain root, not kit.paths.base, so under a base path it lands outside the app and 404s in production.            |
| [`correctness/checkable-bind-value`](/rules/correctness/checkable-bind-value)           | 🟡 warning  | bind:value on a checkbox or radio input binds the DOM value property, which checkbox/radio interaction never changes, so the bound state silently never updates.      |
| [`correctness/each-index-key`](/rules/correctness/each-index-key)                       | 🟡 warning  | Keying an &#123;#each&#125; block by its index gives items position-based identity, the same bug as no key, only masked.                                              |
| [`correctness/each-key`](/rules/correctness/each-key)                                   | 🟡 warning  | An &#123;#each&#125; block over dynamic data should have a key.                                                                                                       |
| [`correctness/effect-as-derived`](/rules/correctness/effect-as-derived)                 | 🟡 warning  | Use $derived instead of an $effect that only assigns state.                                                                                                           |
| [`correctness/effect-as-onmount`](/rules/correctness/effect-as-onmount)                 | 🟡 warning  | An $effect that reads no reactive value belongs in an event handler, &#123;@attach&#125;, or onMount instead.                                                         |
| [`correctness/instance-browser-global`](/rules/correctness/instance-browser-global)     | 🟡 warning  | A component's instance script runs on the server during SSR, so window/document reads at its top level crash the render.                                              |
| [`correctness/nonreactive-builtin-state`](/rules/correctness/nonreactive-builtin-state) | 🟡 warning  | A plain Map, Set, Date, URL, or URLSearchParams in $state is not proxied, so its mutations are invisible to reactivity and the UI silently stops updating.            |
| [`correctness/prop-mutation`](/rules/correctness/prop-mutation)                         | 🟡 warning  | Don't mutate a prop from $props() unless it is declared $bindable.                                                                                                    |
| [`correctness/stale-prop-derivation`](/rules/correctness/stale-prop-derivation)         | 🟡 warning  | A value computed from a prop without $derived is evaluated once, so the UI silently stops tracking the parent.                                                        |
| [`correctness/unmutated-state`](/rules/correctness/unmutated-state)                     | 🔵 info     | Use const (or $state.raw) for a $state that is never mutated.                                                                                                         |

## Security

| Rule                                                                  | Severity    | Summary                                                                                                                       |
| --------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
| [`security/handler-state-write`](/rules/security/handler-state-write) | 🔴 critical | A load function or action writes to imported module state, shared across all requests on the server.                          |
| [`security/javascript-url`](/rules/security/javascript-url)           | 🟡 warning  | Avoid javascript: URLs in attributes.                                                                                         |
| [`security/raw-html`](/rules/security/raw-html)                       | 🟡 warning  | Sanitize the value; &#123;@html&#125; renders unescaped HTML.                                                                 |
| [`security/server-module-state`](/rules/security/server-module-state) | 🟡 warning  | A module-scope variable in a Kit route or hooks file is reassigned from a function, shared across all requests on the server. |
| [`security/shared-state-import`](/rules/security/shared-state-import) | 🟡 warning  | A Kit server/universal file imports a .svelte.ts module holding module-scope $state, one shared instance per server process.  |

## Architecture

| Rule                                                                                    | Severity | Summary                                                                                 |
| --------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------- |
| [`architecture/component-size`](/rules/architecture/component-size)                     | 🔵 info  | Very large components should be split up.                                               |
| [`architecture/directory-naming`](/rules/architecture/directory-naming)                 | 🔵 info  | A directory should be named in the casing its location declares.                        |
| [`architecture/doc-link-target`](/rules/architecture/doc-link-target)                   | 🔵 info  | A documentation link written in a comment must still point at something that exists.    |
| [`architecture/private-scope-import`](/rules/architecture/private-scope-import)         | 🔵 info  | A unit inside a private directory should not be imported from outside it.               |
| [`architecture/prop-count`](/rules/architecture/prop-count)                             | 🔵 info  | Components taking many props are doing too much.                                        |
| [`architecture/reserved-directory-names`](/rules/architecture/reserved-directory-names) | 🔵 info  | A directory's subdirectories should only take names declared for that position.         |
| [`architecture/reserved-name-placement`](/rules/architecture/reserved-name-placement)   | 🔵 info  | A reserved directory name may appear only in the places declared for it.                |
| [`architecture/route-component-import`](/rules/architecture/route-component-import)     | 🔵 info  | A SvelteKit route entry is rendered by the framework, not imported by other components. |
| [`architecture/unit-entry-file`](/rules/architecture/unit-entry-file)                   | 🔵 info  | A directory declared to be a unit should contain a file named after it.                 |

## Accessibility

| Rule                                                                    | Severity   | Summary                                                                                                                                                                           |
| ----------------------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`a11y/accessible-name`](/rules/a11y/accessible-name)                   | 🟡 warning | A button, link, image button, or iframe needs a way to compute its accessible name.                                                                                               |
| [`a11y/aria-hidden-focus`](/rules/a11y/aria-hidden-focus)               | 🟡 warning | A keyboard-focusable element must not be hidden from assistive technology with aria-hidden="true".                                                                                |
| [`a11y/disallowed-aria-props`](/rules/a11y/disallowed-aria-props)       | 🟡 warning | An aria-\* attribute the element's role does not support is ignored; one the role prohibits, such as a name on a bare div, is a name that is not exposed.                         |
| [`a11y/disallowed-element`](/rules/a11y/disallowed-element)             | 🟡 warning | Reports every occurrence of the elements a project declares it does not want, and nothing until something is declared.                                                            |
| [`a11y/duplicate-landmark`](/rules/a11y/duplicate-landmark)             | 🟡 warning | A route should have at most one main, banner, and contentinfo landmark.                                                                                                           |
| [`a11y/id-duplication`](/rules/a11y/id-duplication)                     | 🟡 warning | Every id in a route should be unique.                                                                                                                                             |
| [`a11y/interactive-nesting`](/rules/a11y/interactive-nesting)           | 🟡 warning | An interactive element should not sit inside another interactive element.                                                                                                         |
| [`a11y/invalid-aria-value`](/rules/a11y/invalid-aria-value)             | 🟡 warning | An aria-\* attribute's value should match the type the WAI-ARIA spec defines for it.                                                                                              |
| [`a11y/invalid-role`](/rules/a11y/invalid-role)                         | 🟡 warning | A role attribute should name a concrete WAI-ARIA role, not a typo or an abstract role.                                                                                            |
| [`a11y/label-has-control`](/rules/a11y/label-has-control)               | 🟡 warning | A label needs a for attribute or a wrapped control to be associated with the field it names.                                                                                      |
| [`a11y/no-accesskey`](/rules/a11y/no-accesskey)                         | 🟡 warning | The accesskey attribute assigns a page-level shortcut key whose actual combination varies by browser and OS, is undiscoverable, and conflicts with assistive-technology bindings. |
| [`a11y/no-autofocus`](/rules/a11y/no-autofocus)                         | 🟡 warning | autofocus moves focus on page load without the user asking, so screen reader users lose the page context they were building and keyboard users are dropped mid-page.              |
| [`a11y/no-missing-id-ref`](/rules/a11y/no-missing-id-ref)               | 🟡 warning | An id reference should point to an id that exists somewhere in the composed route.                                                                                                |
| [`a11y/permitted-contents`](/rules/a11y/permitted-contents)             | 🟡 warning | Every element must be permitted content of its parent, per the HTML content models.                                                                                               |
| [`a11y/placeholder-label-option`](/rules/a11y/placeholder-label-option) | 🟡 warning | A required, single-selection select needs an empty first option so users can't submit it unchanged.                                                                               |
| [`a11y/positive-tabindex`](/rules/a11y/positive-tabindex)               | 🟡 warning | A tabindex above 0 puts the element ahead of every naturally-ordered element on the page, so a single tabindex="1" reorders keyboard navigation globally.                         |
| [`a11y/required-aria-props`](/rules/a11y/required-aria-props)           | 🟡 warning | A role that requires state or property attributes needs them present, unless native host semantics already supply them.                                                           |
| [`a11y/required-element`](/rules/a11y/required-element)                 | 🟡 warning | Every route must contain the elements a project declares, judged on the composed route, so a layout's &lt;main&gt; counts.                                                        |
| [`a11y/top-level-landmark`](/rules/a11y/top-level-landmark)             | 🟡 warning | A banner, main, complementary, or contentinfo landmark should not be nested inside another landmark.                                                                              |
| [`a11y/unknown-aria-attribute`](/rules/a11y/unknown-aria-attribute)     | 🟡 warning | An aria-\* attribute should name a real WAI-ARIA attribute, not a typo.                                                                                                           |
| [`a11y/abbr-title`](/rules/a11y/abbr-title)                             | 🔵 info    | An &lt;abbr&gt; without a title gives readers no expansion. Visual users get no tooltip and assistive technology has nothing to expand.                                           |
| [`a11y/deprecated-aria`](/rules/a11y/deprecated-aria)                   | 🔵 info    | A role or aria-\* attribute ARIA 1.3 has deprecated, globally or on the role it sits on, still works today and is no longer defined there.                                        |
| [`a11y/deprecated-attr`](/rules/a11y/deprecated-attr)                   | 🔵 info    | An attribute the HTML spec data marks deprecated on this element has its behavior defined by legacy compatibility, not by the standard.                                           |
| [`a11y/deprecated-element`](/rules/a11y/deprecated-element)             | 🔵 info    | An element in the HTML standard's obsolete-features list is non-conforming and has a conforming replacement.                                                                      |
| [`a11y/doctype`](/rules/a11y/doctype)                                   | 🔵 info    | src/app.html should open with &lt;!doctype html&gt;.                                                                                                                              |
| [`a11y/no-duplicate-dt`](/rules/a11y/no-duplicate-dt)                   | 🔵 info    | Within a single dl element there should not be more than one dt element for each name; a duplicated term usually means two descriptions were meant to share one dt.               |
| [`a11y/pattern-title`](/rules/a11y/pattern-title)                       | 🔵 info    | When an input has a pattern, the spec says authors should include a title describing the expected format; without it a failed submit tells the user only that the value is wrong. |
| [`a11y/require-datetime`](/rules/a11y/require-datetime)                 | 🔵 info    | A time element's text should be machine-readable, or a datetime attribute should supply the machine-readable value.                                                               |
| [`a11y/unverified-id-ref`](/rules/a11y/unverified-id-ref)               | 🔵 info    | Opt-in: flags id references that cannot be verified on routes whose composition is not fully resolved.                                                                            |
| [`a11y/use-list`](/rules/a11y/use-list)                                 | 🔵 info    | A bullet character typed into plain text should be a real list element instead.                                                                                                   |

{/* rules-index:end */}
