Svelte SEO
Svelte alone is client-side rendered — content isn't in the raw HTML. SvelteKit fixes that with SSR by default. Rendering modes, svelte:head, load functions, adapters, sitemaps, and the SPA-mode trap.
Svelte SEO hinges on one distinction: plain Svelte is client-side rendered (a blank HTML shell), while SvelteKit renders on the server by default and ships full HTML. Use SvelteKit for anything that needs to rank. Manage metadata with svelte:head, fetch SEO data in +page.server.js load functions, pick the right adapter, and never ship SPA fallback mode (ssr: false) — the SvelteKit docs themselves warn it has large negative SEO impacts.
Evidence for this claim The article's described svelte-seo capabilities must be evaluated against the platform's current documentation rather than assumed to be search-engine behavior. Scope: Platform-specific capability documentation. Confidence: high · Verified: SvelteKit page options Evidence for this claim Regardless of platform, Google needs crawlable URLs, accessible rendered content, descriptive metadata, and valid search directives. Scope: Google requirements independent of platform. Confidence: high · Verified: Google Search Central: SEO Starter GuideTL;DR — The most important thing to know about Svelte SEO is that Svelte and SvelteKit are not the same thing. Plain Svelte builds the page in the visitor’s browser, so search engines first see a blank shell. SvelteKit — Svelte’s official framework — builds the page on the server and sends finished HTML. Use SvelteKit for anything you want to rank.
Svelte vs. SvelteKit
Svelte is a tool for building user interfaces. It’s clever: instead of shipping a big JavaScript library to the browser, it compiles your components down to small, plain JavaScript at build time. That makes Svelte pages fast.
But “fast” and “search-friendly” aren’t the same thing. A plain Svelte app is client-side rendered — the server sends a nearly empty page, and JavaScript fills in the content once it loads in the browser. If a crawler looks at the raw HTML, there’s almost nothing there.
SvelteKit is the official framework built on top of Svelte. It does the thing that matters most for SEO: it renders your pages on the server first, so the HTML that arrives already has your content, your headings, and your links in it. This is the default behavior. If you’re building a site that needs to show up in Google, you want SvelteKit, not bare Svelte.
Why this matters for search
- Google can run JavaScript, so it will eventually see a client-rendered Svelte page — but there’s a delay, and delay hurts fresh content.
- Bing and other search engines are less reliable at running JavaScript.
- AI crawlers (GPTBot, ClaudeBot, PerplexityBot, and similar) generally fetch static HTML, and rendering behavior varies by provider and changes over time — current provider documentation doesn’t establish one shared render step you can count on filling in a blank shell.
SvelteKit’s server-side rendering sidesteps all of this. The content is in the HTML from the start, so every kind of crawler can read it without depending on a render step it may or may not perform.
The simple checklist
- Build with SvelteKit, not plain Svelte, for content that needs to rank.
- Give every page a unique
<title>and<meta name="description">using SvelteKit’s<svelte:head>element. - Don’t turn off server-side rendering (don’t use “SPA mode”) for pages you want found.
- Create a sitemap and a
robots.txt(SvelteKit doesn’t make these for you). - Don’t block your JavaScript or CSS files in
robots.txt.
Want the deeper version — the four rendering modes, the adapter trade-offs, the
ssr: false trap, and the load-function pattern for metadata? Switch to the
Advanced tab.
Evidence for this claim The article's described svelte-seo capabilities must be evaluated against the platform's current documentation rather than assumed to be search-engine behavior. Scope: Platform-specific capability documentation. Confidence: high · Verified: SvelteKit page options Evidence for this claim Regardless of platform, Google needs crawlable URLs, accessible rendered content, descriptive metadata, and valid search directives. Scope: Google requirements independent of platform. Confidence: high · Verified: Google Search Central: SEO Starter GuideTL;DR — Svelte SEO is really a SvelteKit conversation. Bare Svelte is CSR-only — content isn’t in the raw HTML. SvelteKit defaults to SSR, supports prerendering (SSG) per route, and lets you mix modes. Manage metadata with
<svelte:head>and feed it from+page.server.jsload functions so it lands in the initial HTML. The sharpest trap:adapter-staticwithssr: falsedoes not prerender — it produces empty shells; SSR must stay on during the build. SvelteKit’s own docs warn SPA fallback has “large negative performance and SEO impacts.” Use History API routing (the default), configuretrailingSlash, and pick an adapter to match — static for content, node/vercel/cloudflare for SSR.
Svelte is not SvelteKit — and for SEO that’s everything
Svelte is a compiler. It turns your .svelte components into lean vanilla
JavaScript with no virtual DOM and no runtime library shipped to the browser. That
gives Svelte a real performance edge — but it’s a bundle-size optimization, not a
rendering one. A plain Svelte app (Vite-bundled, no meta-framework) is still a
client-side-rendered SPA: the server returns a blank HTML shell and the browser
constructs the page. View source on one and your content isn’t there.
This is the #1 source of confusion when people search “Svelte SEO.” The compile step doesn’t put content in the HTML. SvelteKit does. SvelteKit is Svelte’s official meta-framework — the equivalent of Next.js for React or Nuxt for Vue — and it renders pages on the server by default, shipping fully populated HTML before any JavaScript runs. It adds SSR, static prerendering, file-based routing, load functions, and deployment adapters. The headline: bare Svelte = CSR-only; SvelteKit = SSR-first. Everything else in this guide assumes SvelteKit.
Two scoping notes worth stating precisely, because they’re where generic “Svelte
SEO” advice goes wrong: SvelteKit’s rendering options (ssr, csr, prerender,
trailingSlash) are set per route and inherit hierarchically — a +layout.js
or +layout.server.js can set a default for everything beneath it, and a child
route can override it. So “is this SvelteKit site SEO-friendly” isn’t a
project-level question; check the specific route. And of SvelteKit’s rendering
options, it’s specifically ssr: false — not SSR being merely absent, and not
“using SvelteKit” in general — that the docs tie to an empty output: “If you set
ssr to false, it renders an empty ‘shell’ page instead.” This guide is current
for Svelte 5.x and SvelteKit 2.x (the current majors as of this update); Svelte 5
made runes the default reactivity model, but runes are a component-authoring
concern, not a rendering mode — they don’t change any of the SEO guidance below.
Why the rendering mode matters comes straight from how search engines work. Google
processes JavaScript in three phases — crawling, rendering, and indexing — and
“all pages with a 200 HTTP status code are sent to the rendering queue, no matter
whether JavaScript is present on the page.” The render queue adds a delay, and as
Google’s own guidance puts it, “server-side or pre-rendering is still a great idea
because it makes your website faster for users and crawlers, and not all bots can
run JavaScript.” That last clause is doing a lot of work in 2026 — see the
AI-crawler section.
The four rendering modes in SvelteKit
SvelteKit’s strength is that rendering is a per-route decision via page options.
These options are hierarchical: set a default in a +layout.js/+layout.server.js
and every nested route inherits it, unless a child route exports its own value to
override it. Check the option that actually applies to the specific route you care
about — not just the value set at the top of the project.
- SSR (the default). Pages are rendered server-side and the full HTML is in the initial response. Best for dynamic, personalized, or frequently changing content. This is on unless you turn it off.
- Prerender (
export const prerender = true). Pages are generated as static HTML at build time. Maximum speed and maximum crawlability — ideal for blog posts, docs, and marketing pages. Crucially, prerendering is SSR run at build time, so SSR must remain enabled for it to work. - SPA fallback (
ssr: false). A single-page-app shell with no server rendering. The SvelteKit docs are blunt that this mode “has large negative performance and SEO impacts” and is really meant for things like wrapping in a mobile app — not for content you want indexed. - Hybrid. Mix modes per route: prerender your marketing pages, SSR your product pages, run an SPA-style admin behind a login. This is SvelteKit’s killer feature — you don’t pick one rendering mode for the whole site.
The ssr: false trap (the mistake to avoid)
This is the most expensive misunderstanding in SvelteKit SEO, so it gets its own
section. People reach for adapter-static to “make a static site,” and then set
ssr: false thinking they’re getting prerendered HTML. They aren’t.
Prerendering is server-side rendering executed at build time. If you disable SSR,
there is nothing to render — the docs are explicit that ssr: false “renders an
empty ‘shell’ page instead,” and that’s exactly what adapter-static then ships as
your prerendered output. The content only appears once the client-side JavaScript runs,
which puts you right back in CSR territory (with all its crawler problems) despite
having a “static” build. The rule: with adapter-static, leave ssr on (its
default) so prerendering outputs real HTML. Use ssr: false only when you
genuinely want an SPA shell and accept the SEO cost.
Managing metadata with <svelte:head>
SvelteKit has a special element, <svelte:head>, that injects content into the
document <head> — and you don’t need a third-party library to manage your title
and meta tags. Every page should have a unique <title> and
<meta name="description">, plus canonical, Open Graph, Twitter Card, hreflang,
robots directives, and JSON-LD as needed.
The pattern the official docs recommend for dynamic metadata:
- Return SEO metadata from a
+page.server.jsload()function. - Access it via
page.datain your layout. - Render it in
<svelte:head>in the root+layout.svelte.
<!-- +layout.svelte -->
<script>
import { page } from '$app/state';
</script>
<svelte:head>
<title>{page.data.title}</title>
<meta name="description" content={page.data.description} />
<link rel="canonical" href={page.data.canonical} />
</svelte:head>If you’d rather use a component wrapper, the third-party
svelte-seo package wraps <svelte:head>
with props for the common tags — but it’s a convenience, not a requirement.
<svelte:head> gets your tags into the head; it doesn’t verify you got them right.
That’s still your job: confirm every route renders a unique title and
description (not one inherited from the layout by accident), that the canonical
you emit is consistent between the direct-request HTML and what a client-side
navigation renders, and that robots directives and JSON-LD are present in the raw
response — not only visible after hydration.
Load functions: where you fetch SEO data matters
This is the metadata bug that bites people. +page.server.js load() runs on the
server, so its data is in the initial HTML response. +page.js load() runs on
the server for the first render and on the client during client-side navigation.
The mistake is fetching your title/description/canonical in a <script> block with
onMount() — that runs client-side only, so the initial HTML ships with no
metadata and the crawler sees nothing on the first fetch. Fetch SEO-critical data in
a load function (server load for anything that must be in the first response), not
in onMount.
Don’t assume the file name alone proves the boundary, though — +page.js load()
also runs on the server for the first request, then re-runs client-side on
navigation, and its return value has to survive serialization to be reused safely
on the client. If SEO-critical data ever depends on a value that isn’t safely
serializable, verify both the direct-request HTML and a client-navigated view of
the same route, not just one or the other.
Routing and URL structure
- File-based routing with
[param]for dynamic segments gives you clean, predictable URLs. - History API routing is the default — SvelteKit doesn’t use hash/fragment
routing, and that’s exactly what you want. Google can’t reliably resolve
hash-based (
#/page) URLs, so History API routing is the SEO-safe choice and SvelteKit makes it the default. trailingSlashinsvelte.config.jsaccepts'always','never', or'ignore'. SvelteKit handles the canonical redirect for you, but leaving it misconfigured (especially'ignore') can create duplicate-content variants. Pick one and be consistent.- Dynamic routes you want prerendered need an
entriesfunction to enumerate the paths at build time, or SvelteKit won’t know which URLs to generate.
Choosing an adapter for SEO
The adapter decides how and where your SvelteKit app is deployed, and that has SEO consequences (mostly via TTFB, which feeds LCP):
| Adapter | Rendering | SEO implication |
|---|---|---|
adapter-static | Full SSG | True static HTML for every page; no server needed; ideal for content-heavy sites (keep ssr on!) |
adapter-node | SSR on a Node server | Dynamic SSR, full flexibility; you run a server |
adapter-vercel | SSR + edge | SSR with optional edge functions; edge TTFB helps LCP |
adapter-cloudflare | SSR on Workers | Edge SSR globally — potentially the fastest TTFB/LCP |
adapter-netlify | SSR + CDN | Similar profile to Vercel |
For a blog or docs site, adapter-static with prerendering gives you maximum speed
and crawlability. For dynamic sites, adapter-cloudflare or adapter-vercel put SSR
at the edge, which can shrink TTFB and help Largest Contentful Paint — but the
adapter only picks the deployment shape. Actual TTFB depends on your data fetching,
caching, and the runtime’s cold-start behavior, so measure the deployed page rather
than assuming the adapter alone delivers the win.
An adapter is a boundary, not just a deploy target: streaming, the filesystem,
caching, edge/runtime APIs, redirects, and error handling can all behave differently
between adapters. A route that looks correct with the local dev server or
adapter-node isn’t guaranteed to behave identically once deployed through
adapter-cloudflare or adapter-vercel — test the production build on the actual
target, not just npm run preview.
Sitemaps and robots.txt
SvelteKit has no built-in sitemap or robots.txt — like a headless setup, you build them explicitly.
- Sitemap, manual: create
src/routes/sitemap.xml/+server.jsthat returns the XML. Addexport const prerender = trueif you’re onadapter-static. - Sitemap, dynamic: a server-rendered endpoint that queries your CMS/database and returns fresh XML on each request — best for large or fast-changing sites.
- Sitemap, packages:
svelte-sitemapscans routes post-build (for SSG), andsveltekit-static-sitemapgenerates from prerendered routes. - robots.txt: drop a static file in
static/(served at/robots.txtautomatically) or generate it from asrc/routes/robots.txt/+server.jsendpoint. Whichever you choose, don’t block your.js/.css— Google won’t render from blocked files.
Performance and Core Web Vitals
SvelteKit’s compiler and framework mechanics give you a structural head start on
performance, and Core Web Vitals are a ranking input — but the mechanics themselves
don’t guarantee a score. The compiler outputs vanilla JS with no virtual DOM and no
runtime library, so a typical SvelteKit page ships less JavaScript than an
equivalent React-based app; automatic per-route code splitting, built-in asset and
link preloading, file-hashing for long-lived caching, and edge deployment via
several adapters are all available. What you build with those mechanics — page
weight, image handling, third-party scripts, hydration cost, and the deployment
target you actually choose — still decides your measured CWV and your rankings.
Treat the framework as removing obstacles, not as delivering the result. The
SvelteKit docs point you at the right measurement tools:
“Google’s PageSpeed Insights and WebPageTest are excellent ways to understand the
performance characteristics of a site.” Image optimization is available via
@sveltejs/enhanced-img.
AI crawlers and the SSR imperative
Here’s the 2026 wrinkle the older SvelteKit SEO guides miss. Rendering behavior for
GPTBot (OpenAI), ClaudeBot (Anthropic), PerplexityBot, and the Bingbot fetch behind
Copilot is provider- and version-specific, and current provider documentation
doesn’t establish one shared render step you can rely on. Treat “generally fetches
static HTML without executing JavaScript” as the safe planning assumption, not a
guarantee about every provider forever. A CSR route (bare Svelte, or SvelteKit with
ssr: false) ships every one of these crawlers the same empty shell a first-fetch
Googlebot request sees; SvelteKit’s default SSR puts your content in the raw HTML
so no crawler has to depend on a render step at all. That’s the argument for keeping
SSR on for anything you want AI systems to see — not a promise that SSR guarantees
inclusion in an AI answer, which depends on factors well beyond rendering.
Common SvelteKit SEO mistakes
- Fetching SEO data in
onMount()instead of a server load function — metadata isn’t in the initial HTML. - Shipping SPA fallback mode (
ssr: false) for content you want ranked. adapter-staticwithssr: false— empty shells, not prerendered pages.- Blocking JS/CSS in
robots.txt— breaks rendering. - Skipping
trailingSlashconfiguration — duplicate-content variants. - Using hash/fragment routing — Googlebot can’t resolve those URLs (SvelteKit’s History API default already protects you here).
If you’re coming at this from the broader framework angle, Svelte/SvelteKit is one of the decoupled frontends a headless CMS pairs with, and the rendering-mode logic here is the same logic that governs JavaScript SEO generally — those topics live alongside this one in the cluster.
AI summary
A condensed take on the Advanced version:
- Svelte ≠ SvelteKit. Bare Svelte compiles to lean vanilla JS but is CSR-only — content isn’t in the raw HTML. SvelteKit renders SSR by default and ships full HTML. Use SvelteKit for anything that must rank.
- Why rendering mode matters: Google queues all
200pages for rendering (delay); Bing is less reliable at JS; AI-crawler rendering (GPTBot, ClaudeBot, PerplexityBot) is provider-specific and generally treated as fetch-only. SSR/prerender puts content in the first response so no crawler depends on a render step. - Four modes: SSR (default), prerender/SSG (
prerender = true, build-time HTML), SPA fallback (ssr: false, docs warn “large negative performance and SEO impacts”), and hybrid (mix per route — SvelteKit’s killer feature). - The trap:
adapter-static+ssr: falseproduces empty shells, not prerendered HTML. Prerendering is SSR at build time — keep SSR on. - Metadata: manage
<title>/meta with<svelte:head>(no library needed). Feed it from a+page.server.jsload function, notonMount()(client-only). - Routing: History API by default (good — hash routing is bad for SEO);
configure
trailingSlash; prerendered dynamic routes need anentriesfunction. - Adapters:
adapter-static(SSG, content sites) vs.adapter-node/vercel/cloudflare(SSR; edge adapters can cut TTFB, which helps LCP — but measure the deployed route, since adapters differ in streaming, caching, and runtime behavior). - No built-in sitemap/robots.txt — build them (manual
+server.jsroute, dynamic endpoint, or a package). Never block.js/.css. - CWV head start: compiler output ships less JS than React; code splitting, preloading, caching, edge deployment are all available — but page weight, images, third-party scripts, and your deployment choice still decide the measured score.
Official documentation
Primary-source documentation from SvelteKit and the search engines.
SvelteKit
- SEO • SvelteKit Docs — SSR-by-default,
<svelte:head>for metadata, trailing-slash normalization, performance, and the sitemap endpoint pattern. - Page options (prerender, ssr, csr) • SvelteKit Docs — how to set rendering mode per route, and the SPA-mode warning.
- Static site generation (adapter-static) • SvelteKit Docs — prerendering to static files and the
ssrrequirement. - Loading data • SvelteKit Docs — server vs. universal load functions (where your SEO data should be fetched).
- Performance • SvelteKit Docs — code splitting, preloading, caching, and measurement tools.
- Understand JavaScript SEO Basics — the three-phase process, the render queue, History API routing, and the “not all bots can run JavaScript” guidance.
- The new evergreen Googlebot — Googlebot’s move to evergreen Chromium and what it still doesn’t support.
- SEO Guide for Web Developers — SSR/prerendering recommendation for indexable content.
Quotes from the source
On-the-record statements from the SvelteKit docs and Google. Each link is a deep link that jumps to the quoted passage on the source page.
SvelteKit docs — rendering & SEO
- “This option has large negative performance and SEO impacts” — on running an app
as a client-side-rendered SPA (
ssr: falsefallback). Jump to quote - “Google’s PageSpeed Insights and WebPageTest are excellent ways to understand the performance characteristics of a site.” Jump to quote
Google — JavaScript & rendering
- “All pages with a 200 HTTP status code are sent to the rendering queue, no matter whether JavaScript is present on the page.” Jump to quote
- “Server-side or pre-rendering is still a great idea because it makes your website faster for users and crawlers, and not all bots can run JavaScript.” Jump to quote
- “Google Search won’t render JavaScript from blocked files or on blocked pages.” Jump to quote
Patrick Stox (my own work — JavaScript SEO: A Definitive Guide)
- “JavaScript is not bad for SEO, and it’s not evil. It’s just different from what many SEOs are used to, and there’s a bit of a learning curve.”
- On rendering modes: “Any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines.”
- On CSR: “The most problematic one is going to be full client-side rendering where all of the rendering happens in the browser. While Google will probably be OK client-side rendering, it’s best to choose a different rendering option to support other search engines.”
SvelteKit SEO checklist
A quick pass to confirm search engines and AI crawlers can see your SvelteKit content:
- You’re using SvelteKit, not bare Svelte, for anything that needs to rank.
- SSR is on (the default) for indexable routes — no accidental
ssr: false. - Content-heavy pages use prerender (
export const prerender = true) where they can. - With
adapter-static,ssris left on so prerendering outputs real HTML (not empty shells). - Every page has a unique
<title>and<meta name="description">via<svelte:head>. - SEO metadata is fetched in a
+page.server.jsload function, not inonMount(). - Canonical, Open Graph, and JSON-LD are rendered server-side, not injected client-side.
-
trailingSlashis set explicitly insvelte.config.js. - Prerendered dynamic routes have an
entriesfunction enumerating paths. - A sitemap exists (
+server.jsroute or package) and a robots.txt is instatic/or a route. -
robots.txtdoes not block.jsor.css. - Routing is History API (SvelteKit default) — no hash/fragment routes.
- You verified the rendered HTML in GSC URL Inspection (content + metadata present on first fetch).
The mental models
1. The Svelte-vs-SvelteKit fork. Before anything else, answer: bare Svelte or SvelteKit? Bare Svelte is CSR-only — content isn’t in the HTML. SvelteKit is SSR-first. Nearly every “Svelte SEO problem” traces back to someone using plain Svelte (or disabling SvelteKit’s SSR) for content that needs to rank.
2. Prerendering = SSR at build time.
“Static” doesn’t mean “no rendering.” Prerendering runs your server render at build
time and saves the HTML. That’s why ssr: false can’t prerender — there’s nothing to
render. Keep SSR on; choose when it runs (request time = SSR, build time =
prerender).
3. The rendering-mode-per-route decision. SvelteKit lets you choose per route, so match mode to content:
- Static-ish content (blog, docs, marketing) → prerender (SSG).
- Fresh/dynamic content (listings, personalized pages) → SSR.
- Behind-a-login, not-indexed UI → SPA fallback is acceptable there only.
4. HTML-first metadata.
Anything a crawler needs — title, description, canonical, JSON-LD — belongs in the
server-rendered HTML via <svelte:head> fed by a server load function. onMount()
metadata is seen late by Google, and providers that treat AI crawlers as fetch-only
won’t see it at all.
5. The adapter = the deployment shape. The adapter decides where SSR runs (or whether it’s all static). Static for content sites; node/vercel/cloudflare for SSR. Edge adapters can shrink TTFB, which can help LCP — but the adapter is one input; your data fetching, caching, and the runtime’s cold-start behavior decide what you actually measure. Adapters also differ in streaming, filesystem access, and error handling, so verify production behavior per adapter rather than assuming feature parity.
6. Plan for crawlers that don’t run your JavaScript. Rendering behavior for AI crawlers is provider- and version-specific, and current provider documentation doesn’t guarantee a shared render step. The safe planning assumption is fetch-only: if content isn’t in the initial HTML, treat it as invisible to that class of crawler. SSR/prerender removes the dependency on any crawler’s rendering capability at all — the strongest lever you have, not a guarantee about what any one AI system does with the result.
SvelteKit SEO — cheat sheet
Rendering modes (set per route via page options)
| Mode | How | SEO | Use for |
|---|---|---|---|
| SSR | default | ✅ Full HTML on first request | Dynamic / fresh content |
| Prerender (SSG) | export const prerender = true | ✅ Best — static HTML at build | Blog, docs, marketing |
| SPA fallback | export const ssr = false | ⚠️ “Large negative … SEO impacts” | App shells behind a login only |
| Hybrid | mix per route | ✅ Best of each | Most real sites |
The trap
adapter-static+ssr: false→ empty shells, not prerendered HTML. Keepssron withadapter-static.
Adapters
| Adapter | Rendering | Note |
|---|---|---|
adapter-static | SSG | True static; no server; keep SSR on |
adapter-node | SSR | Node server; full flexibility |
adapter-vercel | SSR + edge | Edge can cut TTFB → measure LCP impact |
adapter-cloudflare | SSR on Workers | Often low TTFB; test the deployed route |
adapter-netlify | SSR + CDN | Similar profile to Vercel |
Fast rules
- Metadata →
<svelte:head>(no library needed); feed from+page.server.jsload, neveronMount(). Confirm each route’s title/description is unique. - Routing → History API (default). Never hash/fragment routes.
- Set
trailingSlashexplicitly. - Prerendered dynamic routes → add an
entriesfunction. - No built-in sitemap/robots.txt — build them (
+server.jsroute or package). - Never block
.js/.cssin robots.txt. - AI crawlers → generally fetch-only, provider-specific → keep SSR on so content doesn’t depend on a render step.
- Options (
ssr/csr/prerender/trailingSlash) are hierarchical — check the value that applies to the specific route, not just the project default.
Plain Svelte or SvelteKit?
Choose the rendering path
Svelte SEO mistakes
- Using plain client-only Svelte for search landing pages. Move rankable routes to SvelteKit SSR or prerendering.
- Setting
ssr = falseglobally. That turns the site into an SPA and removes complete initial HTML. Scope client-only behavior to routes that do not need search visibility. - Updating
<svelte:head>only after browser fetches. Load metadata with the same server/build data that renders the page. - Using hash-based URLs for content routes. Hash fragments are not separate server routes. Use normal pathname routing.
- Testing only after hydration. Compare raw HTML with rendered DOM so client code cannot hide missing source content.
Raw HTML is an empty app shell
Likely cause: plain Svelte SPA output or ssr = false. Fix: use SvelteKit SSR/prerendering for the route and move essential loading to server-compatible load. Confirm: curl returns the heading, content, and internal links.
Titles and canonicals appear only after JavaScript
Likely cause: head values depend on client lifecycle or browser-only fetching. Fix: return the data during SSR/prerender and render it through <svelte:head>. Confirm: raw and rendered head values agree.
A dynamic route is missing from a static build
Likely cause: the prerenderer cannot discover the parameterized path. Fix: expose crawlable links or configure entries, or render that route on demand. Confirm: the deployed route returns a complete 200 page.
The page works locally but fails after deployment
Likely cause: adapter mismatch, missing server support, or environment-dependent data fetching. Fix: test the production adapter output and its runtime variables. Confirm: production source HTML matches the tested build.
Confirm SvelteKit is actually serving rendered HTML
The whole point of SvelteKit’s SSR is that content is in the raw HTML — before any
JavaScript runs. The fastest way to confirm you didn’t accidentally ship a CSR shell
is to fetch the raw response and look for your content. A plain curl doesn’t run
JavaScript, so it sees exactly what a non-rendering crawler sees.
macOS / Linux
# Raw HTML as the server sends it (no JS executed)
curl -sL "https://example.com/your-page/" -o raw.html
# Is your real content in the raw HTML? (empty result = you're shipping a CSR shell)
grep -o "Your unique headline text" raw.html
# Is your title/description in the initial response?
grep -iE "<title>|name=\"description\"" raw.htmlWindows (PowerShell)
Invoke-WebRequest -Uri "https://example.com/your-page/" -OutFile raw.html
Select-String -Path raw.html -Pattern "Your unique headline text"
Select-String -Path raw.html -Pattern "<title>","name=`"description`""If your headline and metadata are present in raw.html, SSR/prerendering is working.
If they’re missing but visible in the browser, your content depends on client-side
rendering — check that ssr isn’t set to false and that you’re not fetching content
only in onMount().
Check you’re not blocking JS/CSS
curl -sL "https://example.com/robots.txt" | grep -iE "disallow.*\.(js|css)|Disallow:\s*/(_app|assets)"A Disallow matching /_app/ (SvelteKit’s bundled output) or your CSS means Google
can’t render the page — almost always a mistake.
Patrick's relevant free tools
- Raw vs. Rendered HTML Checker — See what's in your page's initial HTML versus after JavaScript runs — headless-Chrome rendering only when the page actually needs it, a rendering-strategy verdict (SSR / prerendered / CSR / hybrid), ~15 calibrated JavaScript-SEO checks (noindex, canonicals, robots.txt blocking, links, soft 404s), a side-by-side raw-vs-rendered diff, and shareable reports.
- SEO Incident Simulator — Practice thirty deterministic technical SEO incident investigations — indexability, crawl controls, redirects, sitemaps, markup, caching, DNS, bot verification, rendering, hreflang, and faceted navigation — with clearly labeled fixture evidence and Find → Fix → Verify handoffs.
- Staging vs. Production SEO Diff — Compare matched release URLs across redirects, canonicals, robots directives, hreflang, selected headers, schema eligibility, and raw or optionally rendered content with honest not-evaluated states.
Tools for debugging SvelteKit SEO
- URL Inspection (Google Search Console) — the source of truth. Run a live test, then check the rendered HTML, screenshot, and page resources to confirm your content and metadata are present (and that nothing is blocked).
- PageSpeed Insights — the SvelteKit docs explicitly recommend it; checks Core Web Vitals (LCP/INP/CLS) that SvelteKit’s lean output is built to win.
- WebPageTest — the other tool the SvelteKit docs name; waterfall + filmstrip view for diagnosing TTFB and render timing.
- Rich Results Test — fast way to confirm JSON-LD made it into the rendered output.
- Screaming Frog SEO Spider — crawl with JS rendering on/off to diff raw vs. rendered HTML across the whole SvelteKit site.
- Ahrefs Site Audit — surfaces broken canonicals, missing metadata, redirect chains, and indexability issues at scale.
- View source /
curl— the quickest “is my content in the raw HTML?” check (see the Scripts tab).
Test yourself: Svelte SEO
Five quick questions on how Svelte and SvelteKit affect search visibility. Pick an answer for each, then check.
Resources worth your time
My related writing
- JavaScript SEO: A Definitive Guide — my full reference on rendering, DOM parity, and the rendering-mode trade-offs that underpin everything here. It mentions Svelte alongside React, Vue, and Angular as frameworks with head/meta modules for SEO.
- The Beginner’s Guide to Technical SEO — where rendering and crawling fit in the bigger picture.
My speaking
- How Search Works (SlideShare) — my walkthrough of crawling, rendering, indexing, and ranking. (My standing disclaimer applies: “This is my understanding of systems… not going to be 100% complete or accurate.”)
From around the industry
- SEO • SvelteKit Docs — the official, authoritative starting point: SSR by default,
<svelte:head>, trailing slashes, and the sitemap pattern. - Page options (prerender, ssr, csr) • SvelteKit Docs — the per-route rendering controls and the SPA-mode SEO warning, in the team’s own words.
- Understand JavaScript SEO Basics (Google Search Central) — the render queue, “not all bots can run JavaScript,” and the History API guidance.
- SvelteKit SEO: Your Secret Weapon (Okupter) — a practitioner guide covering meta tags, prerendering, rendering modes, structured data, and sitemaps.
- SvelteKit SEO: Search Engine Optimization Metadata (Rodney Lab) — the essential meta tags, an SEO component pattern, social cards, and language declaration.
- How to add a basic SEO component to SvelteKit (Thilo Maier) — a code-focused walkthrough of a reusable SEO component.
- Creating a Sitemap in SvelteKit (Bryan Anthonio) — a clear, current guide to the
+server.jssitemap-endpoint approach. - Rich Harris explains why SvelteKit pushes for SSR (DEV Community) — context on why SvelteKit’s creator made SSR the default and pushes against SPA/CSR.
Svelte SEO
Svelte SEO is the practice of making sure search engines and AI crawlers can see content built with Svelte. Plain Svelte is client-side rendered (a blank shell), so the key is to use SvelteKit, which renders pages on the server by default.
Related: JavaScript SEO, Headless CMS SEO
Svelte SEO
Svelte SEO is the practice of making content built with the Svelte UI compiler crawlable, renderable, and indexable. The single most important distinction is between Svelte and SvelteKit. Svelte by itself compiles components to lean vanilla JavaScript with no virtual DOM and no runtime library — but a bare Svelte app is still client-side rendered (CSR): the server returns a near-empty HTML shell and the browser builds the page. That content is not in the raw HTML, which is a problem for crawlers that don’t render JavaScript.
SvelteKit is Svelte’s official meta-framework (the analogue of Next.js for React or Nuxt for Vue). It renders pages on the server by default (SSR), supports static prerendering (SSG) per route, and lets you mix rendering modes route by route. Because SvelteKit ships fully populated HTML before any JavaScript runs, content is available to Googlebot, Bingbot, and AI crawlers on the first fetch.
The practical core of Svelte SEO: use SvelteKit (not bare Svelte) for anything that needs to rank; manage <title> and meta tags with the <svelte:head> element; fetch SEO data in +page.server.js load functions so it lands in the initial HTML; pick an adapter that fits your rendering needs; and avoid the SPA fallback trap (ssr: false), which the SvelteKit docs warn has “large negative performance and SEO impacts.”
Related: JavaScript SEO, Headless CMS SEO
Build-time retrieval analysis plus live signals for this exact article. The automatic chunk report includes a deterministic readiness score and is ready without a model download.
Search Console
sampleGA4 traffic (28d)
sampleCloudflare traffic (7d)
sampledCrUX field data (28d, phone)
sampleGoogle NLP entities
localChangelog
Updated Jul 18, 2026.
Editorial summary and recorded change details.Summary
Tightened the empty-shell claim to the documented ssr=false cause, added hierarchical/per-route page-option coverage and a Svelte 5.x/SvelteKit 2.x version gate (all live-verified against svelte.dev), and rescoped AI-crawler and performance/CWV claims from universal facts to provider-specific/mechanism-not-outcome framing, matching the sibling react-seo article's treatment.
Change details
-
Added a hierarchical-options note (per-route ssr/csr/prerender/trailingSlash inheritance via +layout.js/+layout.server.js) to the Svelte-vs-SvelteKit and rendering-modes sections, live-verified against svelte.dev/docs/kit/page-options.
-
Tied the SPA/ssr:false empty-shell claim to the exact live doc wording ('renders an empty shell page instead') rather than a general SvelteKit claim, and version-gated the guide to Svelte 5.x/SvelteKit 2.x (npm-verified current majors).
-
Rescoped AI-crawler JS-execution claims (beginner, advanced, ai-summary, frameworks, cheat-sheets lenses) as provider- and version-specific rather than a universal 'AI crawlers don't run JS' fact, and softened Performance/CWV and adapter-TTFB language from outcome guarantees to mechanism-provides-a-head-start framing.
-
Added production-parity guidance for adapters (streaming/cache/runtime/error handling can differ between adapters) and a load-function serialization/direct-vs-navigation verification note.
Full comparison unavailable — no prior snapshot was archived for this revision.