SvelteKit provides server rendering, route-level code splitting, data loading, prerendering, and client navigation, but those capabilities do not make every application fast automatically. Slow server work, serialized data, load waterfalls, oversized images, eager third-party scripts, and unnecessary client code can still dominate PageSpeed results.
The right workflow is to trace the deployed route, identify the constrained phase, and change the smallest responsible layer.
Measure the deployed route
Start with a production build behind the same adapter, CDN, proxy, and data services used in production. Development mode has different compilation, caching, and diagnostics, so its timing is not a release benchmark.
For each important route, record:
- edge and application Time to First Byte;
- server
loadand database or API duration; - HTML and serialized data size;
- client JavaScript by route and shared chunk;
- image and font transfer size;
- LCP, CLS, FCP, and main-thread long tasks;
- warm and cold runtime behavior where the platform can cold-start.
PageSpeed Insights combines field data, when available, with a Lighthouse lab run. Use Chrome DevTools and server telemetry to connect a browser symptom to the code that produced it. What Is a Good PageSpeed Score? explains why the overall Lighthouse number is secondary to the underlying metrics and real-user trend.
Choose rendering per route
SvelteKit pages are server-rendered by default and then hydrated in the browser. Routes can also be prerendered or rendered only on the client, but each choice changes the performance and product contract.
Prerender content that is known at build time and can be safely reused by every visitor:
// +page.ts
export const prerender = true;
Use request-time server rendering for authorization, personalization, or data that must be current for the request. Measure its database, upstream API, and cold-start costs. Avoid disabling SSR for an entire route unless the product truly requires a client-only experience; a client-only shell can add round trips before useful content appears.
Do not choose static rendering only to improve a benchmark. Define how content is invalidated and rebuilt, and confirm that stale content is acceptable. SvelteKit's page options documentation describes how prerender, ssr, and csr compose across layouts and pages.
Remove load waterfalls
SvelteKit runs layout and page load functions concurrently where their dependencies allow it. Application code can reintroduce waterfalls by awaiting one independent request before starting another.
// +page.server.ts
export async function load({ fetch, locals }) {
const productPromise = fetch('/internal/product');
const accountPromise = locals.getAccountSummary();
const [productResponse, account] = await Promise.all([
productPromise,
accountPromise
]);
return {
product: await productResponse.json(),
account
};
}
Only parallelize independent work. If one result supplies an ID required by the next query, preserve that dependency and optimize the underlying query instead.
The official SvelteKit performance guidance recommends preventing waterfalls, reducing code size, and diagnosing the deployed application. Also inspect how much data each load returns. Data used only to produce server HTML may belong in a server-rendered component or a smaller projection rather than a large object serialized to the browser.
Stream nonessential promises only when the user experience and error handling benefit. Important above-the-fold content should not appear late simply because streaming is available.
Ship less client JavaScript
Svelte compiles component logic efficiently, but application dependencies and product choices still determine bundle size. Inspect route chunks with a bundle analyzer and browser Coverage.
Reduce client work by:
- moving secrets and server-only operations into
.servermodules; - importing large editors, charts, maps, or media players only when needed;
- avoiding a full utility library for one small function;
- keeping static content out of interactive component state;
- removing unused analytics and duplicated SDKs;
- ensuring package imports can be tree-shaken;
- testing how shared layouts affect every route's baseline.
Dynamic import is useful for code that is not required for the initial task:
async function openEditor() {
const { createEditor } = await import('$lib/editor/client');
return createEditor();
}
Do not delay accessibility-critical controls or the primary content merely to reduce an initial bundle. The goal is an earlier useful page with responsive interactions, not the smallest possible JavaScript number in isolation.
Build a deliberate image pipeline
SvelteKit does not impose one universal image service. The Svelte project documents @sveltejs/enhanced-img for build-time optimization of local assets in its image guidance. It can produce modern formats, responsive sources, and intrinsic dimensions for supported build-time images.
<enhanced:img
src="./product-dashboard.png"
alt="Product dashboard showing weekly performance metrics"
sizes="(max-width: 720px) 100vw, 960px"
/>
For remote or user-uploaded media, use an image CDN or server pipeline that creates bounded variants. Persist dimensions and alternative-text metadata. Render correct srcset, sizes, width, and height attributes.
The likely LCP image should be discoverable from initial HTML, loaded eagerly, and prioritized when appropriate. Images below the fold can be lazy-loaded. Do not mark every image high priority; competing priorities can delay the resource that matters.
Control navigation and preloading
SvelteKit can preload route code or data based on link options. Preloading can improve likely navigations, but excessive data preloading spends bandwidth and server work on routes a visitor may never open.
Choose the behavior by link importance and data cost, as described in the link options documentation. Audit inherited attributes on layout containers because one broad setting can affect many links.
Programmatic preloading should follow a real interaction or strong intent signal. Avoid preloading personalized or expensive endpoints indiscriminately. Test navigation with cache state, authentication, and slow networks rather than measuring only a warm local transition.
Set cache boundaries explicitly
Use response headers that reflect the data's sharing and freshness rules. In a server load, setHeaders can set cache behavior for the page response where the adapter and platform honor it.
export async function load({ setHeaders }) {
setHeaders({
'cache-control': 'public, max-age=0, s-maxage=300, stale-while-revalidate=600'
});
return { /* public data */ };
}
That example is suitable only for public data that may be shared for those periods. Never cache account-specific HTML publicly. Include locale, currency, authorization, experiments, and cookies in the design when they change a response. Verify actual edge headers and cache status after deployment; adapter capability and platform configuration determine what happens beyond the application.
Version immutable assets for long browser caching and define an invalidation path for rendered content. A cache without an ownership and freshness policy becomes a correctness risk.
Protect Core Web Vitals
For LCP, separate server response time, resource discovery, download, and render delay. Keep the LCP content in initial server HTML, optimize its image or font, and avoid reveal animations that postpone paint.
For CLS, reserve dimensions for images, embeds, advertisements, and async components. Stabilize font swaps with suitable fallbacks and font metrics where needed. The CLS guide shows how to locate individual shift sources.
For INP, profile event handlers and the rendering work they trigger. Break up long tasks, reduce large reactive updates, virtualize only when list size justifies its complexity, and provide immediate visual feedback before long asynchronous work.
Third-party scripts affect all three metrics. Load them only where needed, after consent where required, and with a documented owner. Measure their production configuration because a placeholder script rarely behaves like the live service.
Use a release checklist
Before releasing a SvelteKit performance change, verify:
- the route uses the intended prerender, SSR, and CSR settings;
- independent data work runs concurrently without changing semantics;
- returned
loaddata is bounded and necessary; - client bundles exclude server-only code and defer optional heavy features;
- image variants, intrinsic dimensions, loading, and priority are correct;
- preload behavior is intentional and does not issue wasteful requests;
- cache headers match personalization and freshness rules;
- repeated lab tests use equivalent deployed conditions;
- field Core Web Vitals are monitored separately from lab results.
Published performance histories can be reviewed on IndieTools Speed. Treat them as a signal to investigate a route, then use traces and server telemetry to locate the actual constraint.


