SvelteKit Deployment SEO: Adapters, Prerendering, and Edge Rendering

SvelteKit's adapter and per-route prerender settings decide where and when your pages render — and that drives TTFB, LCP, and crawl budget. A deployment-focused deep dive: choosing adapter-static/node/vercel/cloudflare/netlify, prerender = true/false/'auto', edge-runtime constraints, and building sitemap.xml and robots.txt.

First published: Jul 3, 2026 · Last updated: Jul 13, 2026 · Advanced

SvelteKit's adapter and per-route prerender setting decide where and when a page renders — static HTML at build time, SSR on a server, or SSR at the edge — and that decision drives TTFB, which feeds LCP and crawl capacity. Pick adapter-static for pure content sites, a node/vercel/cloudflare adapter with per-route prerender for mixed content-plus-app sites, and an edge adapter when global TTFB matters (accepting cold starts and no Node fs). prerender = 'auto' is the mixed-site tool. Edge runtimes can't read the filesystem. And SvelteKit generates no sitemap.xml or robots.txt — you build those as +server.js endpoints, with the strategy depending on your adapter.

TL;DR — The adapter doesn’t change what SvelteKit renders — it changes where and when: build-time static (adapter-static), request-time on a server you run (adapter-node), or request-time on serverless/edge functions (adapter-vercel/-netlify/-cloudflare). Per-route prerender = true builds static HTML and drops the route from the dynamic manifest; prerender = 'auto' prerendersThe Speculation Rules API is a Chromium browser API (Chrome/Edge 109+) that lets a site tell the browser which same-site pages to prefetch (download the HTML document) or prerender (fully load and render in an invisible tab) before a visitor clicks — so the next navigation can be near-instant. It's a browser-side performance feature for real users, not a crawling, indexing, or ranking signal. and keeps it in the manifest — the tool for mixed /blog/[slug] sites. Edge runtimes run on V8 isolates: no Node fs, and cold starts hurt TTFBTime to First Byte — the time from the start of a request to when the first byte of the response arrives. It's a diagnostic metric (not a Core Web Vital) and a major input to FCP and LCP; ≤0.8 s is good., which feeds LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. and (per Google’s crawl-budget doc) crawl capacityThe number of URLs an engine will crawl in a timeframe.. SvelteKit generates no sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing..xml or robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere. — build them as +server.js endpoints, and note the strategy depends on the adapter. This is a narrower, deployment-focused companion to the SvelteKit SEOSvelte 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. fundamentals article in this section; I assume you already know SvelteKit is SSR-by-default and won’t re-litigate that here.

The one idea that makes all of this click

The adapter does not change what renders. It changes where and when. That’s the whole thing. The SvelteKit docs put it precisely: adapters “take the built app as input and generate output for deployment.” Evidence for this claim SvelteKit adapters take the built application as input and generate deployment-specific output. Scope: Deployment output; adapter choice can still constrain supported runtime features. Confidence: high · Verified: SvelteKit: Adapters Your components, your load functions, your <svelte:head> metadata — identical across every adapter. What differs is:

  • When the HTML is produced: at build time (static/prerendered) or at request time (SSR on a server, serverless function, or edge function).
  • Where it’s produced: on a single origin server, on a regional serverless function, or on an edge network close to the visitor.

Everything below is a consequence of those two axes.

Why deployment choices are SEO choices

The chain is short and well-documented: TTFBTime to First Byte — the time from the start of a request to when the first byte of the response arrives. It's a diagnostic metric (not a Core Web Vital) and a major input to FCP and LCP; ≤0.8 s is good.LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. → crawl capacity.

Time to first byte is how long the host takes to start sending the response. A prerendered file served from a CDN cache has a near-zero TTFB. A server that has to render the page has a higher one. A cold-starting serverless or edge function can have a much higher one on the first hit. TTFB is a direct input to Largest Contentful Paint — you can’t paint what you haven’t received — and LCP is a Core Web VitalsWeb Vitals is Google's initiative (launched May 2020) for unified page-experience quality signals. Core Web Vitals — LCP, INP, and CLS — are the subset used in ranking; the rest (TTFB, FCP, TBT, Speed Index) are diagnostic, not ranking factors. signal.

The crawl side is where Google is most explicit. From the crawl-budget documentation: “If the site responds quickly for a while, the limit goes up, meaning more connections can be used to crawl. If the site slows down or responds with server errors, the limit goes down and Google crawls less.” And the best-practice line: “Make your pages efficient to load. If Google can load and render your pages faster, we might be able to read more content from your site.” A cold-starting edge function that’s slow to respond is subject to the same dynamic as a slow origin server.

One honesty note up front: Google publishes no SvelteKit-specific guidance. There’s no doc or Search Off the Record episode naming SvelteKit adapters, prerender = 'auto', or edge cold starts. What I’m doing here is applying Google’s general renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. and crawl-budget guidance to SvelteKit’s specific mechanics — not quoting a rep who commented on SvelteKit, because none has. The Google framing that “server-side or pre-renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. is still a great idea because it makes your website faster for users and crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index., and not all bots can run JavaScript” is the closest official anchor, and it’s framework-agnostic.

Choosing an adapter for SEO outcomes

adapter-auto — the zero-config default, and its ceiling

New SvelteKit projects ship with adapter-auto. It detects the platform — Vercel, Netlify, Cloudflare Pages, Azure, AWS — and installs the matching adapter at build time. It’s a fine starting point, but there’s a hard ceiling worth knowing: adapter-auto does not take any options. The moment you need { edge: true }, Cloudflare bindings, Vercel ISR, or any platform-specific configuration, you install the underlying adapter (adapter-vercel, adapter-cloudflare, etc.) directly. Treat auto as a scaffold, not a production decision.

adapter-static — full SSG, for content-first sites

adapter-static prerenders your whole site to static files at build time. No server runs; a host serves flat HTML. Evidence for this claim adapter-static prerenders a SvelteKit site as static files. Scope: Routes must be prerenderable; performance outcomes depend on hosting and page design. Confidence: high · Verified: SvelteKit: Static site generation For a content-first site this is the strongest SEO profile you can have — lowest TTFB, no cold starts, nothing to fall over. The one requirement is the trap covered at length in the fundamentals article: SSR must stay on during the build, or you get empty shells instead of rendered HTML. I won’t re-explain that here beyond flagging it.

The catch is rigidity. Anything that genuinely needs server logic per request (true search, per-user content, form handling without a third-party endpoint) can’t live on a purely static build — which is exactly what the next adapters are for.

adapter-node — a server you control

adapter-node produces a standalone Node.js server. You run it, you scale it, you own the TTFB. This is the most flexible option and the one with the fewest runtime surprises — full Node APIs, including fs. It’s a good fit when you have infrastructure already, need Node libraries that edge runtimes can’t run, or want predictable (non-cold-starting) response times from a warm server. The tradeoff is operational: you’re running a server, and its speed and uptime are now your crawl capacity.

adapter-vercel — serverless, edge, and ISR

adapter-vercel deploys to Vercel’s serverless functions by default, with several SEO-relevant levers set per route via export const config:

  • runtime: 'edge' moves that route to Vercel’s edge runtime (more below).
  • regions controls where serverless functions run — closer to your users (or your database) means lower latency.
  • isr enables Incremental Static Regeneration: isr: { expiration: 60 } serves a cached static asset and regenerates it after the window, giving “the performance and cost advantages of prerendered content with the flexibility of dynamically rendered content.” ISR is a genuine fourth path between pure-static and pure-SSR — but note the docs’ own caveat: “Using ISR on a route with export const prerender = true will have no effect, since the route is prerendered at build time.” ISR and prerender are alternatives, not stackable.

adapter-cloudflare — Workers/Pages, global edge

adapter-cloudflare targets Cloudflare WorkersA serverless function that runs at Cloudflare's global edge, close to every user. and Pages — SSR on a global edge network, often the lowest TTFB for a geographically spread audience. The important constraint is the runtime: Workers run on V8 isolates, not Node. From the docs: “You can’t use fs in Cloudflare Workers.” Some Node APIs work only behind the nodejs_compat compatibility flag, and even then support isn’t one-to-one. If you were reading files at request time (a redirectA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't. map, a data file, custom OG-image inputs), that code needs a rethink — covered in the edge section below.

(The older adapter-cloudflare-workers is deprecated; new projects use adapter-cloudflare, which handles both Workers and Pages. If you’re on the old one, migrating is the recommended path.)

adapter-netlify — functions or Edge Functions (Deno)

adapter-netlify deploys to Netlify’s Node-based functions by default, or to Deno-based Edge Functions with edge: true. Same shape as Vercel: default serverless with an edge opt-in. One SvelteKit-specific footnote — Netlify Forms require the form’s page to be prerendered so Netlify can detect the form markup at deploy time, which is a small “prerender this route” requirement layered on top of the adapter choice.

The decision, in one line each

  • Pure content siteadapter-static, prerender everything.
  • Content site with dynamic pocketsadapter-node/-vercel/-cloudflare, prerender = true on content, false/'auto' on the dynamic routes.
  • App/dashboard with personalization → SSR-first (node or edge), prerender only the static shell (marketing, login).
  • Global, TTFB-critical audience → an edge adapter for the dynamic routes, accepting the Node-API constraints and cold-start reality.

(The Decision Tree tab walks this as a branching flow.)

Prerendering strategy for mixed sites

What true / false / 'auto' actually do

export const prerender is a per-route (or per-layout) page option, and the three values aren’t just on/off:

  • true — build this route to static HTML at build time. Critically, it’s “excluded from manifests used for dynamic SSR, making your server (or serverless/edge functions) smaller.” Once prerendered, the route can’t fall back to dynamic rendering — it’s static, full stop.
  • false — always render on request. No static file.
  • 'auto' — the mixed-site tool. It prerenders the route and keeps it in the dynamic server manifest, so the same route can be served statically for known paths and server-rendered for the rest. This is built for exactly the case the docs describe: a route like /blog/[slug] “where you want to prerender your most recent/popular content but server-render the long tail.”

Because prerendered routes shrink the server bundle, a mostly-prerendered site with a few 'auto'/false routes deploys a smaller, cheaper, faster function — an efficiency win independent of SEO.

Dynamic routes need an entries function

The prerender crawler discovers pages by following <a> links from your entry points. That works for static routes, but a dynamic route like /blog/[slug] has no fixed URL for the crawler to find. If nothing links to a given slug, SvelteKit won’t know it exists — and you’ll hit the classic build error that routes “were marked as prerenderable, but were not prerendered.”

The fix is an explicit entries function (or config.kit.prerender.entries) that enumerates the parameter values:

// src/routes/blog/[slug]/+page.server.js
export const prerender = true;

export function entries() {
  return [
    { slug: 'hello-world' },
    { slug: 'sveltekit-deployment-seo' },
  ];
}

In practice you generate that list from your CMSA content management system (CMS) is software that lets users create, manage, and publish digital content — like blog posts and pages — without writing raw code. WordPress, Drupal, and Joomla are the most common open-source CMS platforms. or content directory. Without it, prerendering only covers the slugs the link crawler happens to find.

The /blog/[slug] pattern in the wild

Put the two together and you have the canonical mixed-site setup: prerender = 'auto' plus an entries function that returns your recent and popular posts. Those get static HTML at build time; anything not in the list falls through to SSR on demand. New posts render dynamically until the next build prerenders them. It’s the pragmatic middle ground between “prerender all 40,000 posts every build” and “render every post on every request.”

Edge runtime constraints that affect SEO

config.runtime = 'edge' is per-route (on Vercel)

Edge isn’t an all-or-nothing switch. On Vercel it’s a per-route page option:

// +page.server.js or +server.js
export const config = { runtime: 'edge' };

That means you can push high-traffic, cacheable routes to the edge for low TTFB while keeping Node-dependent routes on the standard serverless (Node) runtime in the same deployment. Mix deliberately.

No fs, no arbitrary Node APIs

The edge runtimes — Cloudflare Workers, Vercel Edge Functions, Netlify’s Deno Edge Functions — don’t provide Node’s fs. Cloudflare’s docs: “You can’t use fs in Cloudflare Workers.” Vercel’s: “You can’t use fs in edge functions.” Both point to the same two escape hatches: use the read helper from $app/server to access bundled assets, or “prerender the routes in question” so the file access happens at build time instead of at request time.

The SEO-adjacent cases where this bites: dynamic OG-image generation that reads a font or template file, file-based redirect maps, or a sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. endpoint that reads content off disk. Any of those either moves to $app/server’s read() or moves to prerender/build time. It’s not a blocker — it’s a “know before you pick edge” constraint.

Cold starts and TTFB — when edge helps and when it doesn’t

Edge functions still cold-start. A cold edge function on its first request can be slower than a warm Node server, and dramatically slower than a prerendered file served from cache. Edge wins when the function stays warm or when it’s paired with aggressive caching so most requests never hit the function at all. It is not automatically the fastest option — “deploy to the edge” is not a synonym for “faster.” For a content site, prerendered static output beats edge SSR on TTFB every time, because there’s no function to start.

Generating sitemap.xml and robots.txt (SvelteKit won’t)

This is the gap most SvelteKit tutorials skip and most audits catch. SvelteKit generates no sitemap.xml and no robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere. automatically — regardless of adapter, regardless of how many pages you prerender. A fully static site with thousands of prerendered pages still ships with no sitemap unless you build one.

The +server.js endpoint pattern

The idiomatic sitemap is a route endpoint that returns XML with the right Content-Type:

// src/routes/sitemap.xml/+server.js
export const prerender = true; // needed on adapter-static

export async function GET() {
  const urls = await getAllUrls(); // from your CMS/content
  const body = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map((u) => `  <url><loc>${u}</loc></url>`).join('\n')}
</urlset>`;

  return new Response(body, {
    headers: { 'Content-Type': 'application/xml' },
  });
}

The strategy depends on your adapter

Here’s the part that ties this whole article together: your sitemap strategy is downstream of your adapter choice.

  • On adapter-static, the sitemap endpoint needs export const prerender = true so it’s included in the static output — there’s no server at runtime to generate it on request. It’s baked at build time, which means it’s only as fresh as your last build.
  • On a Node/serverless/edge adapter, the same endpoint can generate the sitemap dynamically per request from your CMS or database — always current, no rebuild needed. (On an edge adapter, remember the fs constraint: pull URLs from an API or binding, not a disk read.)

So the “should my sitemap be static or dynamic?” question isn’t a separate decision — it falls out of the adapter you already chose.

robots.txt: static file vs. endpoint

Two options. Drop a plain robots.txt in your static/ folder (served at /robots.txt automatically), which is the simplest choice and fine for most sites. Or generate it from a src/routes/robots.txt/+server.js endpoint when you need it to differ by environment (blocking crawlers on staging, allowing them in production, for instance). Either way, don’t block your /_app/ bundle or CSS — that breaks rendering for engines that do render.

If you’re coming at this from the broader framework or JavaScript-SEO angle, the “where and when does rendering happen” logic here is the same logic that governs JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. generally, and the SvelteKit fundamentals piece in this section covers the rendering modes and metadata patterns this article builds on top of.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.