SvelteKit SEO: what the framework does by default, and the page options that can empty your HTML
SvelteKit server-renders and normalises URLs for you; titles, the sitemap endpoint and per-route rendering choices are yours.
Published by AI Knows Us · Updated 21 October 2026
SvelteKit SEO starts from a good default: SvelteKit renders every page on the server, so search engines receive finished HTML, and it redirects trailing-slash variants so one page has one URL. What it leaves to you is the rest. You write a unique <title> and meta description inside <svelte:head>, build a sitemap as an endpoint, choose per route whether to prerender, and avoid the two page options that can strip your text from the HTML. This page covers each step from SvelteKit's own documentation, read on 22 October 2026, when the npm registry listed @sveltejs/kit 3.1.0 and Svelte 5.57.2 as the latest releases.
What SvelteKit's SEO page says it does for you
SvelteKit has a short best-practices page on SEO, and it opens with a fair line: the most important part of SEO is high-quality content that is widely linked from around the web. Then it lists what the framework handles out of the box.
- Server-side rendering is on by default. The docs say that although search engines have improved at indexing content rendered with client-side JavaScript, server-rendered content "is indexed more frequently and reliably". You can switch SSR off in the
handlehook, and the docs tell you to leave it on unless you have a good reason. - Performance. Svelte adds little overhead, which helps Core Web Vitals. The docs point you to PageSpeed Insights and Lighthouse to test, and name hybrid rendering and image optimisation as the two quick wins.
- Normalised URLs. SvelteKit redirects paths with a trailing slash to ones without, or the other way round depending on your setting, because duplicate URLs are bad for SEO.
The page also mentions dynamic rendering, which serves bots a different version, and says it is not generally recommended. Google's own JavaScript SEO guide calls dynamic rendering a workaround too, so treat it as a last resort.
Step 1: a unique title and description on every page
The SEO page says every page should have a well-written, unique <title> and <meta name="description"> inside <svelte:head>. It also suggests a common pattern: return SEO data from each page's load function, then read it as page.data in a <svelte:head> in your root layout. That way the head is written once and every route only supplies its values.
// src/routes/blog/[slug]/+page.server.js
export async function load({ params }) { const post = await getPost(params.slug); return { post, title: post.title, description: post.summary };
}
<!-- src/routes/+layout.svelte -->
<script> import { page } from '$app/state'; let { children } = $props();
</script> <svelte:head> <title>{page.data.title ?? 'Example'}</title> <meta name="description" content={page.data.description ?? ''} />
</svelte:head> {@render children()}
Because SSR is on, these tags are in the HTML the crawler first receives. Add a canonical link and Open Graph tags in the same block if you need them. Our guide to Open Graph tags lists the full set.
Step 2: build the sitemap as an endpoint
SvelteKit has no built-in sitemap generator. The SEO page shows the intended route instead: create src/routes/sitemap.xml/+server.js with a GET function that returns XML with the header Content-Type: application/xml. You fill the <url> entries from your routes and your content source.
// src/routes/sitemap.xml/+server.js
export const prerender = true; export async function GET() { const posts = await getAllPosts(); const urls = posts .map((p) => `<url><loc>https://www.example.com/blog/${p.slug}</loc></url>`) .join(''); return new Response( `<?xml version="1.0" encoding="UTF-8" ?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls}</urlset>`, { headers: { 'Content-Type': 'application/xml' } } );
}
The page options docs say prerender also applies to +server.js files, so a sitemap for a static content set can be written once at build time. Add a Sitemap: line to a robots.txt in your static folder so crawlers find it. Whether you need one at all depends on the site; our page on when a sitemap matters covers that.
Step 3: choose prerender, SSR or CSR per route
SvelteKit's page options are exported from +page.js, +page.server.js or a layout, and child routes override parents. The docs give the example of prerendering a marketing page, server-rendering dynamic pages "for SEO and accessibility", and turning an admin section into a client-only app. Three options matter for search:
| Option | What the docs say it does | SEO effect |
|---|---|---|
prerender = true | Builds the route to an HTML file at build time | Fastest response, full text in HTML |
prerender = 'auto' | Prerenders the route but keeps it in the server manifest | Prerender popular posts, server-render the long tail |
ssr = false | Renders an empty shell page instead | The first HTML has none of your content |
csr = false | Ships no JavaScript at all | Text stays in HTML; good for plain content pages |
The docs are blunt about ssr = false in the root layout: it turns the whole app into a single-page app, and you should not do it if your goal is a statically generated site. Keep it for routes that cannot run on the server, such as a dashboard behind a login.
The rule for prerendering is also from the docs: any two users hitting the page directly must get the same content from the server. Pages with form actions cannot be prerendered. The prerenderer finds pages by following links from the root, so a page nothing links to may be skipped. If you see "The following routes were marked as prerenderable, but were not prerendered", add the route through config.prerender.entries or an entries function, or switch it to 'auto'.
Step 4: settle your trailing slash rule
The trailingSlash option takes 'never' (the default), 'always' or 'ignore'. With 'never', a visit to /about/ is redirected to /about. The docs say ignoring trailing slashes is not recommended, because /x and /x/ are then treated as separate URLs, "which is harmful to SEO". Pick never or always, and keep your internal links and canonical tags in the same form. The option also changes prerender output: 'always' writes about/index.html, otherwise you get about.html.
Step 5: images that do not slow the page
SvelteKit's images page recommends the @sveltejs/enhanced-img plugin for images that live in your project. It serves smaller formats such as AVIF and WebP and sets the image's width and height automatically, so the browser reserves space and the layout does not shift. For large hero images, set sizes so phones get smaller files. For the largest contentful paint image, the docs say to set fetchpriority="high" and avoid loading="lazy". Images served from a CMS or database need a CDN approach, which the same page covers.
Check what a crawler actually gets
Google's guide describes three phases for JavaScript sites: crawling, rendering and indexing. A SvelteKit page with SSR on should look complete in the first phase. To confirm, fetch the URL with curl and search the response for your heading and body text, then run the URL Inspection tool in Search Console. If the text is missing, look for a route with ssr = false, or content loaded in onMount, which only runs in the browser. Our wider guide to JavaScript SEO explains the rendering queue in more detail.
Questions about SvelteKit SEO
Is SvelteKit good for SEO?
Yes. It server-renders by default, normalises trailing slashes, and lets you prerender any route to static HTML. You still add titles, descriptions, a sitemap and structured data yourself.
How do I add meta tags in SvelteKit?
Put them inside <svelte:head>. The pattern the docs suggest is to return the values from each page's load function and render them once in the root layout using page.data.
Does SvelteKit generate a sitemap?
No. You write a src/routes/sitemap.xml/+server.js endpoint that returns XML. You can prerender it so it is built once per deploy.
Is a SvelteKit single-page app bad for SEO?
Setting ssr = false sends an empty shell, so the content depends on the crawler running JavaScript. Keep SSR on for every page you want found, and use client-only rendering only for app sections that do not need indexing.
Should I prerender my whole SvelteKit site?
If every page shows the same content to every visitor, yes, and adapter-static outputs files for any static host. Mixed sites can prerender content pages and server-render the rest.
Server-rendered pages are easy to quote
The same habit that helps Google, putting the full text in the first response, helps an AI assistant that fetches your page while answering a question. Being readable does not decide whether the assistant names you when someone asks which product to choose, and no ranking report shows that. Measuring which assistants name a business, and why, is the new category AI Knows Us belongs to.