Harness-engineering svelte-performance-patterns

Svelte Performance Patterns

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
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"
manifest: agents/skills/claude-code/svelte-performance-patterns/SKILL.md
source content

Svelte 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:

  1. 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/
  1. 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:

  1. Start loading page data before the user clicks by adding
    data-sveltekit-preload-data
    to links:
<!-- 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>
  1. 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>
  1. Set global preload behavior in
    svelte.config.js
    :
export default {
  kit: {
    preloadStrategy: 'modulepreload', // 'modulepreload' (default) | 'preload-js' | 'preload-mjs'
  },
};

Streaming slow data:

  1. 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:

  1. For lists with 500+ items, use a virtual scroller. Install
    svelte-virtual-scroll-list
    or implement a basic version:
<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:

  1. Use the
    loading="lazy"
    attribute for below-fold images to defer loading:
<img src="/photo.jpg" alt="Description" loading="lazy" decoding="async" />
  1. Use
    srcset
    and
    sizes
    for responsive images:
<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:

  1. 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} />
  1. Use
    $state.raw
    for large objects that only change by full replacement:
// 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:

  1. preload-code
    — fetches the JavaScript chunk for the destination route
  2. preload-data
    — runs the destination route's load function

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
    setTimeout
    or
    scheduler.yield()
  • CLS — reserve space for images with
    width
    /
    height
    attributes or CSS
    aspect-ratio
    ; avoid injecting content above existing content

Source

https://kit.svelte.dev/docs/link-options

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. 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.