NNicheStackTutorials
← All tutorials
SvelteKit

Image Optimization in SvelteKit Static Sites (No Cloudinary)

Vite imagetools, responsive srcset at build time, and fixing the 'image not found in production' bug.

·12 min read·SvelteKit
Image Optimization in SvelteKit Static Sites (No Cloudinary) cover

Tested on: SvelteKit 2.16, vite-imagetools 7.0 Problem: 2.4 MB hero PNG killing mobile Lighthouse score.

Install

npm install -D vite-imagetools

vite.config.ts:

import { sveltekit } from '@sveltejs/kit/vite';
import { imagetools } from 'vite-imagetools';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [sveltekit(), imagetools()]
});

OptimizedImage component

src/lib/components/OptimizedImage.svelte:

<script lang="ts">
  import hero from '$lib/assets/hero.png?w=400;800;1200&format=webp&as=picture';

  let { alt = '', class: className = '' } = $props();
</script>

<picture>
  {#each hero.sources as source}
    <source srcset={source.srcset} type={source.type} />
  {/each}
  <img
    src={hero.img.src}
    width={hero.img.w}
    height={hero.img.h}
    {alt}
    class={className}
    loading="lazy"
    decoding="async"
  />
</picture>

Put source images in src/lib/assets/, not static/.

Production 404 — the classic mistake

GET /_app/immutable/assets/hero.abc123.webp 404

Cause: referenced static/images/hero.png with imagetools query. Imagetools only processes imports from src/.

Fix: move to src/lib/assets/hero.png and import with ?w=...&format=webp.

LCP image — don't lazy load

<img loading="eager" fetchpriority="high" ... />

Only the hero above the fold. Everything else: loading="lazy".

TypeScript module declaration

src/app.d.ts:

declare module '*?w=*&format=webp&as=picture' {
  const value: {
    sources: { srcset: string; type: string }[];
    img: { src: string; w: number; h: number };
  };
  export default value;
}

Without this, tsc screams on the import query string.

Build size comparison

Original hero.png: 2.4 MB
Generated WebP (400w): 28 KB
Generated WebP (800w): 62 KB
Generated WebP (1200w): 98 KB
Total shipped (browser picks one): ~62 KB avg

Verify in build output

npm run build
find build -name '*.webp' | head

Lighthouse

LCP: 4.1s → 1.5s
Performance score: 71 → 94
Properly size images audit: PASS

Tags: SvelteKitImagesWebPPerformance