ssrchecker
HomeCheckFrameworksSvelteKit

Is your SvelteKit page actually server-rendered?

Short answer
SvelteKit server-renders by default, so most routes return complete HTML. A page comes back client-rendered mainly when export const ssr = false is set, when the app is built as a static SPA with a fallback, or when the main content is fetched in onMount instead of a load function. The default is the good case.

SvelteKit sits alongside Next.js and Nuxt as an SSR-by-default meta-framework, so the diagnosis is the same shape: the page is rarely empty, it is a server-rendered page with the important part missing. What differs is the machinery — SvelteKit's load functions and page options are its own model and worth understanding directly.

Check your SvelteKit page
https://

Compiled does not mean server-rendered

Svelte's identity is that it is a compiler — it turns components into small, efficient JavaScript at build time, with no virtual DOM. This is a real performance advantage, and it is completely irrelevant to whether your content is in the HTML.

Compilation happens at build time and produces JavaScript; server rendering happens at request or build time and produces HTML. A brilliantly compiled Svelte component still renders to nothing in the raw HTML if server rendering is turned off. If someone assumes their SvelteKit site must be crawlable because Svelte "compiles away the framework", that assumption is the bug. Compilation and rendering are separate concerns.

The load function split

SvelteKit fetches data through load functions, and there are two kinds. The distinction is the single most useful thing to understand about SvelteKit rendering.

+page.server.js
Server only

Its load runs only on the server. Ideal for database queries and secrets. The data is resolved before the HTML is sent.

+page.js
Universal

Its load runs on the server for the first render, then in the browser on client-side navigation. The first render still puts data in the HTML.

Both are fine for SEO, because both run on the server for the initial request. The point of the split is where the code is allowed to run and what it can access — not whether the crawler sees the result. Data loaded through either one ends up in the server HTML. The problem is data loaded through neither, which is the onMount case below.

The ssr, csr and prerender page options

SvelteKit exposes rendering as three options you export from a +page.js or +layout.js. Setting them on a layout cascades to everything beneath it, which is how a single line can change an entire section — or the entire site, from the root layout.

export const ssr = true; // server-render this route (default) export const csr = true; // hydrate/allow client JS (default) export const prerender = false; // build to static HTML at build time?

The one that breaks crawlability is export const ssr = false. It disables server rendering for that route — or, in the root layout, for the whole app — leaving an empty shell in the HTML exactly like a client-only SPA. As with Nuxt's ssr: false, this is sometimes a deliberate choice for an app behind a login and sometimes inherited and forgotten. If a whole SvelteKit site comes back CSR, look for this in the root +layout.js first.

Note that csr = falseis the opposite situation and harmless for SEO: it ships the server-rendered HTML with no client JavaScript, much like Astro's default. It is ssr = false that removes your content from the HTML.

The onMount trap

The subtle cause, and the SvelteKit equivalent of fetching in a React useEffect. onMount runs only in the browser, after the component has mounted — which is after the server has already sent the HTML. Anything you fetch there is absent from the server response.

If your article body or product data is loaded in onMount rather than in a load function, the page shell renders server-side and looks healthy while the content that matters arrives only after hydration — a HYBRID verdict. Moving that fetch into a load function is usually the whole fix, and it is a small change because the data ends up on the same data prop either way.

Side-by-side comparison of a SvelteKit page's raw HTML with content present versus a HYBRID case where the body was fetched in onMount and is missing before hydration
A HYBRID SvelteKit result — shell present, the onMount-loaded content absent.

adapter-static and SPA mode

SvelteKit can be built as a fully static site or as a single-page app, depending on the adapter and its configuration. Full prerendering is great for crawlers — every route becomes static HTML. But adapter-static configured with a SPA fallback (a fallback: 'index.html' option) is a different thing: it serves the same shell for every URL and lets the client router take over, which reintroduces the exact problem plain React SPAs have.

How to spot it
Run this checker on your homepage and on a deep route. If both return the same word count, the same links and the same title, you are serving a SPA fallback rather than prerendered per-route HTML. Prerendering the routes that need to rank, instead of using the catch-all fallback, resolves it.

Your configuration and the verdict to expect

Find your setup. If the tool returns something other than the expected verdict, that difference is the bug.

SetupExpectWhy
Default SvelteKit route (no page options set)SSRSSR is on by default. Pages render to complete HTML on the server.
Data loaded in +page.server.js load()SSRRuns on the server before render. Data is in the HTML.
Data loaded in +page.js (universal) load()SSRRuns on the server for the first render, so the data is in the HTML.
export const prerender = trueSSRRoute built to static HTML at build time. Crawlers get everything.
export const ssr = false (in a +page or +layout)CSRDisables server rendering for that route or subtree. Empty shell.
export const ssr = false in the root +layout.jsCSRTurns the whole app into a client-only SPA.
adapter-static with a SPA fallback (fallback: 'index.html')CSREvery URL returns the same shell. React-CRA-style behaviour.
Main content fetched in onMount()HYBRID or CSRonMount runs only in the browser; that content isn't in the server HTML.

SvelteKit rendering FAQ

Yes. SvelteKit server-renders every route to complete HTML unless you disable it. This is different from plain Svelte, which is only a compiler and has no rendering model of its own. A SvelteKit route usually comes back client-rendered only when ssr is set to false, when it is built as a SPA, or when content is fetched in onMount.