Skip to content
svelte-vitals
Esc
↑↓navigate↵open⌘Jpreview
On this page

seo/title-presence · Title presence

Every route should resolve a non-empty <title>.

Severity: critical

What it checks

Every route must resolve a non-empty <title> (own or inherited through the layout chain). A dynamic title (<title>{data.title}</title>) is the correct SvelteKit pattern and passes; only a genuinely missing or empty title is flagged.

Why it matters

A unique, non-empty <title> is the single strongest on-page SEO signal and the text shown in search results and browser tabs.

How to fix

Add a <title> inside <svelte:head> (a dynamic title is fine), or set it via your meta component:

<svelte:head>
  <title>{data.title}</title>
</svelte:head>

Mode differences

Source analysis (the CLI, the dashboard’s static baseline) composes each route’s <head> from <svelte:head> in the page and its layout chain, followed into repo-local components (including workspace packages the app declares) and the components of installed Svelte packages it declares, plus the known meta components (svelte-meta-tags, svelte-seo, svead) and any you declare in metaComponents. A value it cannot read literally ({data.title}) is dynamic, judged by treatDynamicAs. A component a function mounts with import() (onMount(async () => { Meta = (await import('$lib/Meta.svelte')).default })) renders only in the browser: its <title> counts on a route with ssr = false, and not on a server-rendered route, whose HTML has none. A title the script sets with document.title = … (in $effect or onMount) counts the same way, and so does one set by an exported function of a repo-local module that a page or layout script calls ($effect(() => syncTitle())), when no other title reaches the route. Rendered analysis (the Vite plugin’s build pass, a route you visit in the dashboard) reads the shipped <head>, where every value is literal and treatDynamicAs does not apply; the build pass covers prerendered routes only. When the two disagree, trust the rendered result.

Disabling

Record existing findings in the suppressions file (npx svelte-vitals --update-suppressions), scope the rule per route or path with overrides, or turn it off:

export default {
  rules: {
    'seo/title-presence': 'off'
  }
};