git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/svelte-performance-patterns" ~/.claude/skills/intense-visions-harness-engineering-svelte-performance-patterns && rm -rf "$T"
agents/skills/claude-code/svelte-performance-patterns/SKILL.mdSvelte Performance Patterns
Minimize bundle size, reduce perceived latency, and handle large datasets efficiently in SvelteKit applications
When to Use
- You are seeing slow initial page loads due to large JavaScript bundles
- Users experience navigation delay between pages in your SvelteKit app
- You are rendering large lists (100+ items) that cause janky scrolling
- You want to improve Core Web Vitals (LCP, FID/INP, CLS) scores
Instructions
Code splitting — automatic with SvelteKit:
- SvelteKit automatically code-splits at the route level. Each page's JavaScript is only loaded when the route is navigated to. Verify this is working by checking that bundle chunks are created per route:
npm run build # Check .svelte-kit/output/client/_app/immutable/chunks/
- Manually split heavy components using dynamic imports:
<script lang="ts"> let HeavyChart: typeof import('./HeavyChart.svelte').default | null = $state(null) async function loadChart() { const module = await import('./HeavyChart.svelte') HeavyChart = module.default } </script> <button onclick={loadChart}>Show Chart</button> {#if HeavyChart} <svelte:component this={HeavyChart} {data} /> {/if}
Link preloading — data-sveltekit-preload-data:
- Start loading page data before the user clicks by adding
to links:data-sveltekit-preload-data
<!-- Preload on hover (default behavior for most links) --> <a href="/dashboard" data-sveltekit-preload-data="hover">Dashboard</a> <!-- Preload immediately on page load (for critical next steps) --> <a href="/getting-started" data-sveltekit-preload-data="eager">Get Started</a> <!-- Disable preloading (for links with side effects) --> <a href="/logout" data-sveltekit-preload-data="off">Logout</a>
- Preload code (JS chunks) separately from data using
:data-sveltekit-preload-code
<!-- Load the JS bundle for this route on hover, without fetching data yet --> <a href="/editor" data-sveltekit-preload-code="hover">Open Editor</a>
- Set global preload behavior in
:svelte.config.js
export default { kit: { preloadStrategy: 'modulepreload', // 'modulepreload' (default) | 'preload-js' | 'preload-mjs' }, };
Streaming slow data:
- Stream slow database queries or API calls so the initial HTML renders immediately with a loading state:
// +page.server.ts export const load: PageServerLoad = async ({ params }) => { const product = await getProduct(params.id); // fast — awaited const reviews = getReviews(params.id); // slow — NOT awaited, returns promise return { product, reviews }; };
<!-- +page.svelte --> {#await data.reviews} <ReviewsSkeleton /> {:then reviews} <ReviewsList {reviews} /> {:catch error} <p>Failed to load reviews.</p> {/await}
Virtualizing large lists:
- For lists with 500+ items, use a virtual scroller. Install
or implement a basic version:svelte-virtual-scroll-list
<script lang="ts"> import { VirtualList } from 'svelte-virtual-scroll-list' let { items }: { items: Item[] } = $props() </script> <VirtualList {items} let:item height={400} itemHeight={50}> <div class="row">{item.name}</div> </VirtualList>
Image optimization:
- Use the
attribute for below-fold images to defer loading:loading="lazy"
<img src="/photo.jpg" alt="Description" loading="lazy" decoding="async" />
- Use
andsrcset
for responsive images:sizes
<img src="/image-800.jpg" srcset="/image-400.jpg 400w, /image-800.jpg 800w, /image-1200.jpg 1200w" sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px" alt="Hero image" />
Reducing reactive overhead:
- Avoid creating new objects in templates — they cause unnecessary re-renders:
<!-- Bad: creates new object every render --> <Component config={{ theme: 'dark', size: 'lg' }} /> <!-- Good: stable reference --> <script> const config = { theme: 'dark', size: 'lg' } </script> <Component {config} />
- Use
for large objects that only change by full replacement:$state.raw
// Avoids deep reactivity proxy overhead let dataset = $state.raw<DataPoint[]>(initialData); // Update only via full reassignment: dataset = await fetchNewData();
Details
SvelteKit preloading strategy:
SvelteKit's link preloading works in two steps:
— fetches the JavaScript chunk for the destination routepreload-code
— runs the destination route's load functionpreload-data
Default behavior: on tap/click, SvelteKit preloads code. With
data-sveltekit-preload-data="hover", data preloading starts on hover — making navigation feel instant.
Bundle analysis:
Identify large dependencies causing bundle bloat:
npx vite-bundle-visualizer # or npm run build -- --report
Common culprits:
moment.js (use date-fns instead), lodash (use individual imports), large chart libraries (load dynamically).
SvelteKit performance vs. React:
Svelte compiles to vanilla JavaScript with no virtual DOM — component updates are surgical DOM mutations. This eliminates reconciliation overhead for interactions but doesn't remove the need for virtualization on very large lists.
Prerender for maximum performance:
For content that doesn't change per-user, prerender pages at build time:
// +page.ts export const prerender = true;
Prerendered pages are served as static HTML with no server processing time — ideal for marketing pages, docs, and blog posts.
Web Vitals checklist:
- LCP — prerender or stream; optimize above-fold images; preload critical routes
- INP — avoid heavy synchronous work in event handlers; defer with
orsetTimeoutscheduler.yield() - CLS — reserve space for images with
/width
attributes or CSSheight
; avoid injecting content above existing contentaspect-ratio
Source
https://kit.svelte.dev/docs/link-options
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.