Images in SvelteKit: a practical Svelte & SvelteKit guide
Image handling is where many web teams leave performance on the table. We load full-resolution photos for mobile viewers, skip responsive sizes for tablets, and serve PNG when WebP would cut payload in half. The difference between a slow and fast site often comes down to how we optimize and deliver images—and SvelteKit gives us the tools to get it right.
In this guide, we'll walk through the practical approach to images in SvelteKit, focusing on the modern image pipeline, lazy loading, and the details that matter in production. By the end, you'll know how to ship smaller, faster images without the manual work.
Understanding @sveltejs/enhanced-img
The @sveltejs/enhanced-img package is SvelteKit's answer to image optimization. It's not magic—it's a sharp-based transformer that runs at build time, generating multiple formats and sizes from a single source. When you import an image with the enhanced loader, SvelteKit produces WebP and AVIF versions alongside the original, plus responsive variants for different screen widths.
Start by installing it in your SvelteKit project:
npm install -D @sveltejs/enhanced-imgThen configure it in your svelte.config.js:
import { build } from '@sveltejs/package';
import adapter from '@sveltejs/adapter-auto';
export default {
kit: {
adapter: adapter(),
},
extensions: ['.svelte'],
};The real work happens when you import images:
import { Image } from '@sveltejs/enhanced-img';
import heroImage from '$lib/images/hero.jpg?enhanced';
// In your component
export let data;
By adding the ?enhanced query parameter, you tell SvelteKit to process that image. The loader returns an object with multiple size and format variants. When you render it with the <Image> component, SvelteKit handles the srcset, picture elements, and format negotiation automatically.
The benefit is clear: you write one line of code, and the framework generates optimized variants for every screen and format. Your bundle shrinks, your Core Web Vitals improve, and your users get faster page loads.
Modern formats and sizes
AVIF and WebP are not new, but many teams still ship only JPEG and PNG. AVIF offers 25–35% better compression than WebP; WebP beats PNG by 20–30%. Modern browsers support both. The key is serving the right format to each browser, and letting older browsers fall back gracefully.
When you use @sveltejs/enhanced-img, the generated srcset includes multiple widths. On a 1920px desktop, a 375px mobile, and a 768px tablet, you don't want to send the same image. The package typically generates widths at 320, 640, 960, 1280, and 1920 pixels by default, but you can customize this:
import { Image } from '@sveltejs/enhanced-img';
import productImage from '$lib/images/product.png?enhanced&w=400;800;1200';
// This generates three size variants instead of the default set
In your template, the <Image> component emits a <picture> element with multiple <source> tags:
<Image src={productImage} alt="Product showcase" />This renders something like:
<picture>
<source srcset="product-400.avif 400w, product-800.avif 800w" type="image/avif" />
<source srcset="product-400.webp 400w, product-800.webp 800w" type="image/webp" />
<img src="product-400.png" alt="Product showcase" />
</picture>The browser picks the first format it understands, then selects the best width based on the viewport. No JavaScript involved. This is the modern way to serve images.
Lazy load below the fold
Not every image needs to load on page entry. Hero images above the fold should load immediately; product thumbnails below the fold should lazy-load when the user scrolls near them. This is where the native loading="lazy" attribute shines.
The <Image> component accepts a loading prop:
<Image
src={heroImage}
alt="Hero"
loading="eager"
/>
<Image
src={productThumb}
alt="Related product"
loading="lazy"
/>Use loading="eager" for above-the-fold content. Use loading="lazy" for everything else. The browser's Intersection Observer API handles the rest—no extra dependencies, no event listeners.
In practice, we've seen this cut initial page load time by 15–25% on e-commerce sites where the product gallery lives below the fold. The images still load smoothly when users scroll, but the critical rendering path isn't blocked.
One note: lazy loading works best with explicit dimensions. If the browser doesn't know an image's height, it can't reserve space and may cause layout shift when the image loads. This brings us to our next point.
Set width and height
Always provide width and height on images. Without them, the browser can't reserve space, and when the image loads, the layout reflows. This contributes to Cumulative Layout Shift (CLS), one of Core Web Vitals. Higher CLS scores hurt SEO and user experience.
When you use @sveltejs/enhanced-img, the dimensions are baked into the returned image object. You just need to pass them through:
import { Image } from '@sveltejs/enhanced-img';
import cardImage from '$lib/images/card-cover.jpg?enhanced';
<Image
src={cardImage}
alt="Card cover"
width={400}
height={300}
/>The component will apply these as CSS constraints, ensuring the space is reserved before the image bytes arrive. On a responsive layout, you might want to set width and height relative to the container:
<div style="max-width: 800px;">
<Image
src={articleImage}
alt="Article hero"
width={800}
height={450}
/>
</div>The browser will scale the image down on smaller screens, maintaining the aspect ratio. CSS aspect-ratio also works well here as a fallback in older browsers.
Common mistakes
We see these issues regularly in production codebases:
- Forgetting the
?enhancedquery parameter. Without it, SvelteKit treats the import as a static asset. No optimization happens, no srcset is generated, and you're back to shipping one size for all devices. - Omitting width and height. This causes layout shift and hurts CLS. Always pass dimensions, even if you're using CSS to scale the image.
- Using
loading="lazy"on hero images. Hero images are above the fold and should load immediately. Reserve lazy loading for images that appear after scrolling. - Ignoring alt text.
@sveltejs/enhanced-imgdoesn't generate alt text for you. Provide meaningful descriptions for accessibility and SEO. - Not checking build output. Run
npm run buildand inspect the.svelte-kitdirectory to see what variants were generated. If formats are missing, check your Sharp installation and system dependencies.
Key takeaways
- Use
@sveltejs/enhanced-imgto automate image optimization. Add the?enhancedquery parameter to image imports and let the framework generate multiple formats and sizes. - Modern formats (AVIF, WebP) cut file size by 25–35% compared to JPEG and PNG. The
<picture>element handles format negotiation; browsers pick what they support. - Lazy-load images below the fold with
loading="lazy"to improve initial page load. Saveloading="eager"for hero and critical content. - Always provide explicit
widthandheightto prevent layout shift and maintain good Core Web Vitals scores. - Test your build output. Verify that variant images are generated and that the
srcsetin the rendered HTML matches your expectations.






Comments
Loading comments…