Learn Svelte - Performance Optimization
Series/Learn Svelte/Episode 15
Episode 15 of 24

Learn Svelte - Performance Optimization

This episode covers how to make a Svelte app faster: profiling with browser tools and benchmarks, minimizing the bundle with tree-shaking, reducing runtime overhead and reactivity costs, and lazy loading components and code splitting.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Optimization without measurement is just guessing. Before chasing micro-optimizations, it's important to know where time is really wasted — whether it's a heavy bundle, expensive renders, or slow requests.

This episode covers profiling with browser tools and benchmarks, minimizing the bundle with tree-shaking, reducing runtime overhead and reactivity costs, and lazy loading components and code splitting.

When you're done, you'll have a clear workflow: measure, find the biggest problem, fix it, and measure again. Optimization becomes a repeatable, measurable process instead of aimlessly shuffling problems around.

One principle underlies this entire episode: measure the impact of a change after it's done, not just when you start. Before and after numbers are the only proof an optimization actually worked.

Profiling and Benchmarking

Start with the Performance Panel

Chrome DevTools provides a Performance panel to record activity while a page runs. The recording shows where CPU is spent: slow functions, expensive layouts, and janky paints. Start with the flow your users use most often.

Improve recording quality by enabling CPU throttling in the Performance panel to simulate a mid-range device. Optimizations that look great on a developer laptop may not be felt on user devices.

Benchmark with Lighthouse

Lighthouse provides measurable scores for performance, accessibility, and SEO. Run it regularly in CI so regressions are caught early. Scores aren't the end goal, but a compass for where to improve.

Minimizing the Bundle with Tree-shaking

Visualize Bundle Size

It's hard to optimize something you can't see. vite-bundle-visualizer produces an interactive map of each module's size:

Analyze the bundle with a visualizer
npm install -D vite-bundle-visualizer
npx vite-bundle-visualizer

The result is a treemap chart showing the largest libraries. The target is usually one or two giant modules — not many small ones.

Effective Tree-shaking

Tree-shaking removes unused code at build time. For it to work, use named imports instead of importing an entire library. For example, import { writable } from "svelte/store" is far friendlier than an import that drags in the whole module. Also make sure the libraries you use declare the correct sideEffects in package.json.

Also check whether the libraries you use already take advantage of ES module exports. Modern builds need that format so the bundler can safely cut unused parts.

Reducing Runtime Overhead and Reactivity Costs

Reactive with Care

Svelte turns reactive declarations into precise update code. The more things you make reactive, the more work the compiler has to track. Limit $: statements to values that truly derive from their source:

JSEfficient reactive statement
let query = ""
let hasil = []
 
$: if (query.length >= 3) {
  hasil = cari(query)
}

This reactive statement only runs cari(query) when query changes and only if its length is sufficient. The Svelte compiler generates code that compares values before marking a change, so updates that don't alter the result never touch the DOM.

Beware of mass updates when data changes. If a statement triggers many consecutive updates, consider deferring the computation until the data has finished changing.

Reduce Object Allocation in Loops

Inside {#each}, avoid creating a new object or array each iteration. New objects force reactivity to compare new values every time, while also adding garbage collector pressure. Build static data outside the template or in a value computed once.

The same pattern applies to functions called in the template. A function that allocates a new object every render will be re-run on every potential update.

Lazy Loading and Code Splitting

Dynamic Imports for Heavy Components

Editor, chart, or video player components don't need to load on the first page. Dynamic imports split the code and load a module only when it's needed:

Code splitting with dynamic import
<script>
  let Editor = null
 
  import("$lib/components/Editor.svelte").then((mod) => {
    Editor = mod.default
  })
</script>
 
{#if Editor}
  <svelte:component this={Editor} />
{/if}

import("$lib/components/Editor.svelte") produces a separate chunk that downloads after the main page finishes loading. Everything else runs without waiting for the heavy component — perceived speed rises even if the total bytes downloaded are similar.

Use sensible thresholds. Don't split small components into dozens of chunks — too many small requests actually slow things down. Split only what's heavy and rarely used.

Use Await Blocks for Heavy Data

SvelteKit supports waiting for parallel data inside load functions. Components that depend on heavy data can also be rendered conditionally with the {#await} block so first content appears faster.

Conclusion

Key takeaways:

  • Measure first with the Performance panel and Lighthouse before optimizing.
  • Use vite-bundle-visualizer to find the largest modules.
  • Named imports and correct sideEffects declarations support tree-shaking.
  • Limit reactive statements to values that truly change.
  • Avoid allocating new objects inside {#each} loops.
  • Use dynamic imports to split heavy components into separate chunks.

Next, in episode 16 you'll learn testing & quality — unit testing with Vitest and Svelte Testing Library, integration testing for SvelteKit apps, E2E testing with Playwright, and static analysis, linting, and type checking. Your optimized code must be guaranteed to stay correct through automated testing.

Learn Svelte - Performance Optimization | Learn Svelte