SvelteKit Image Optimizer

💡Not to be confused with img:enhance that handles image optimizations at build time, this is for the external images, or images from CMSs and other sources. this package is more like cloudflare's image optimizer. a proxy right in your sveltekit app that auto optimizes images.

This is a simple utility that helps you create an endpoint of your svelte app that optimizes your images, it uses sharp to optimize images.

I have been using something similar for my small to medium projects, and decided maybe others can benefit from this as well.

This library is similar to what next/image image optimizer does, basically a proxy that optimizes/resizes images on the fly, with support for caching.

Table of Contents

Why would you use this?

Svelte Kit Image Optimize provides a seamless solution for image optimization in your Svelte Kit project. You should consider this library when

  • You need to serve optimized, properly sized images to reduce page load times and improve Core Web Vitals
  • You want automatic format conversion to modern formats like WebP and AVIF based on browser support
  • You require responsive images that automatically adapt to different screen sizes and device capabilities
  • You need flexible caching strategies (memory, filesystem, or S3) to improve performance and reduce server load
  • You want a simple, declarative API with Svelte components that handle the complexity of responsive images
  • You need to optimize images from external domains with security controls
  • You want to serve external images from your own domain, improving SEO.

💡 In many cased you do not have control over images, probably the content is created by writers or other non tech people, and you have no control over that, this can help you server optimized images without interfering with those content creators

Installation

To be able to use this package you need to install both sveltekit-image-optimize and sharp

💡 Note: If you already have sharp installed you can skip this.

npm i sveltekit-image-optimize sharp

Setup

To begin using the optimizer all you have to do is add the image optimizer handler to your hooks.server.ts

hooks.server.ts:

import { createImageOptimizer } from 'sveltekit-image-optimize';

const imageHandler = createImageOptimizer({
    //  optional params here
});

// add it to your hook handler (if you have multiple handlers use sequence)
export const handle: Handle = imageHandler;

💡 Note: while this simple example will get you going, I highly suggest you checkout the caching and the other options to optimize you setup

Using Components

Then Any where in your project you can use the provided Image, Picture components or toOptimizedURL to generate an optimized image URL (useful for styles/background images and such)

<script>
 import { Image,Picture ,toOptimizedURL} from 'sveltekit-image-optimize/components';

</script>
<!--
if width and heigth are [rovided the image will be resized to those dimentions
if preload is provided a <link as='image'> will be added to the <head>
-->
 <Image
    src="{URL to my image supports relative link like /images/image}"
    width="600"
    height="420"
    optimizeOptions={{
     preload: "prefetch"
    }}
 />

<!--
you can use picture too, you can spcify your sources here
the image optimizer endpoint will serve the image type based on the `accept` headers but you can provide a format field in `optimizeOptions` to enforce a specific format
-->
<Picture
 src="{URL to my image supports relative link like /images/image}"
    sources={[
        {
            media: '(min-width: 1024px)',
            optimizeOptions: { width: 500, fit: 'cover', background: 'transparent'}
        },
        {
            media: '(min-width: 768px)',
            optimizeOptions: { width: 400, fit: 'cover', background: 'transparent'}
        },
        {
            media: '(min-width: 480px)',
            optimizeOptions: { width: 300, fit: 'cover', background: 'transparent'}
        },
        {
            media: '(min-width: 320px)',
            optimizeOptions: { width: 320, fit: 'cover', background: 'transparent'}
        }
    ]}

/>

<!-- you can also just get the optimizedLink and ad it to your img element or use it how ever you see fit -->
<img src={toOptimizedURL("URL to my image supports relative link like /images/imag")} alt="">

Image Component Props

Prop Type Description
src string The source URL of the image (required)
alt string Alternative text for the image (required for accessibility)
optimizeOptions object Image optimization options (see below)
...rest any Any other HTML img attributes are passed through

OptimizeOptions Object

All these options are optional

Option Type Description
width number Target width of the image, if the image already have width/height you do not have to provide these here
height number Target height of the image, if the image already have width/height you do not have to provide these here
quality number Image quality (1-100)
format string Output format (avif, webp, jpeg, png, etc.), recommended is to omit this option because the optimization endpoint will select the best format based on the accept header
fit string Resize behavior (cover, contain, fill, inside, outside)
position string Position for cropping (center, top, left, etc.)
background string Background color when using fit modes that need it
preload 'preload' | 'prefetch' or undefined if provided a <link/> will be added to <head>

Picture Component Props

For more details you can check the component source

Prop Type Description
src string The source URL of the image (required)
alt string Alternative text for the image (required for accessibility)
defaultOptions object Default optimization options applied to all sources
sources array Custom responsive image sources (see below)
useDefaultSources boolean Whether to use built-in responsive breakpoints (default: true)
aspectRatio number Optional aspect ratio to maintain (e.g., 16/9)
...rest any Any other HTML img attributes are passed through

Each source in the sources array should have:

Property Type Description
media string Media query (e.g., '(min-width: 768px)')
optimizeOptions object Image optimization options for this breakpoint, similar to image optimize options omitting the preload because it does not make sense here (we can't know which picture source the client is going to use hence we can't ask to prefetch it)

When useDefaultSources is true, the Picture component includes sensible default responsive breakpoints from 540px to 5120px.

Advanced Hook Configuration

createImageOptimizer takes an object of Options

Option Type Default Description
route string /api/image-op The endpoint path where the image optimizer will handle requests
formatPriority Array<string> ['avif', 'webp', 'jpeg', 'jpg', 'png', 'gif'] Priority order for image formats
fallbackFormat string jpeg Default format to use when no format is specified
quality number 75 Default image quality (1-100)
cache ImageCacheHandler undefined Cache adapter for storing optimized images check Caching
cacheTTLStrategy number | 'source' | 'immutable' | 'no-cache' 'source' Cache TTL strategy, by default what ever cache policy is received from the source image is used.
allowedDomains Array<string|RegExp> | Function undefined Domains that are allowed to be optimized (security feature)
minSizeThreshold number | undefined 5kb The minimum size of the image to be optimized. Defaults to 5kb.
skipFormats Array avif, webp The formats that will not be optimized, this only applies to images without a resize query. Defaults to avif, webp

Client side Configuration

If you've customized the default route, you need to configure the client side as well in hooks.client.ts

hooks.client.ts:

import { initImageOptimizerClient } from 'sveltekit-image-optimize/client';
import type { ClientInit } from '@sveltejs/kit';

export const init: ClientInit = () => {
    // this is only needed if you change the default route /api/image-op
    // iF you do not change that , you don't have to initialize it on client side
    initImageOptimizerClient({
        route: '/api/image-op-2'
    });
};

Component Optimize options

Caching

This library provides several cache adapters for storing optimized images:

Cache Adapters

This library provides several cache adapters for storing optimized images:

Memory Cache

Memory cache is suitable for development environments. Not recommended for production as there is no cleanup other than ttl, so it could if you have many many images consume a lot of ram, and this is basically useless in serverless environments.

import { createMemoryCache } from 'sveltekit-image-optimize/cache-adapters';

const cache = createMemoryCache();

File System Cache

File system cache stores images on disk. Suitable for single-server deployments, For self hosting (single VPS) this is probably the best way.

import { createFileSystemCache } from 'sveltekit-image-optimize/cache-adapters';

const cache = createFileSystemCache('/path/to/cache/directory');

S3 Cache Adapter

The S3 cache adapter allows storing optimized images in Amazon S3 or S3-compatible storage. This is recommended for multi-server deployments or serverless environments.

To use the S3 cache adapter, you need to install the AWS SDK packages: These packages are omitted from dependencies on purpose as the usage of the adapter is optional

npm install @aws-sdk/client-s3

Usage

import { createS3Cache } from 'sveltekit-image-optimize/cache-adapters';

const cache = createS3Cache({
    region: 'us-east-1',
    bucket: 'my-image-cache',
    prefix: 'images', // optional, adds a prefix to all keys
    // Auth credentials (optional, can use AWS environment variables)
    accessKeyId: 'YOUR_ACCESS_KEY',
    secretAccessKey: 'YOUR_SECRET_KEY',
    // For S3-compatible services like MinIO, DigitalOcean Spaces, etc.
    endpoint: 'https://custom-endpoint.com', // optional
    forcePathStyle: true, // for minio or other providers, this only affect the genration of public links bucket.enpoint vs endpoint/bucket
    /* 
  if true the cached images won't be proxied and piped through sveltekit, instead a redirect response will be sent to the client, useful to save bandwidth o sveltekit backend or to use s3 cdn etc...
  if false a stream will be opened from s3 and piped straight through sveltekit response 
  when redirecting the server will redirect with a found status and give the same cache policy as if it proxied the request
  */
    useRedirects: false
});

Then use the cache with your image optimizer:

import { imageOptimizer } from 'sveltekit-image-optimize';

export const config = imageOptimizer({
    cache: cache
    // other options...
});

Make Your own Adapter

Cache adapters have the same interface, all you have to do is implement your own logic.

export interface ImageCacheHandler {
    // if true and getPublicUrl returns a url a redirect response will be sent instead of sending the image content
    useRedirects: boolean;

    // images are hashed based on url and the optimization parameter (with,height,format etc...)
    getKey(hash: string): Promise<{
        value: NodeJS.ReadableStream | Buffer | null;
        ttl?: number;
    }>;

    // save this image to cache
    setKey(
        hash: string,
        value: NodeJS.ReadableStream | Buffer | ReadableStream,
        ttl?: number,
        contentType?: string
    ): Promise<void>;

    // delete an image from cache
    delKey(hash: string): Promise<void>;

    // if your adapter supports public urls you can handle that here
    // useful with cdns and other caching platforms etc...
    getPublicUrl(hash: string): Promise<{
        url: string | null;
        ttl?: number;
    }>;
}

class MyAdapter implements ImageCacheHandler {
    // implement thigs here
}

Note that when implementing your own cache adapter, you are responsible for invalidating the cache based on the ttl provided to you on setKey, the image optimizer handler will not keep records of ttl and rely of your adapter response.

Animated Images

Animated images will not be transformed as sharp does not support them, they will be piped straight through.

Future plans

  • Add a fall back to use other libraries than sharp to add support for platforms what do not support sharp.
  • Add some tests in place
  • based on feedback adjust the interface if needed
  • Add cropping / filter and other basic image manipulations?

Contribution

Feel free to open an Issue or a pull request

License

MIT © 2025 humanshield85

Top categories

svelte logo

Need a Svelte website built?

Hire a professional Svelte developer today.
Loading Svelte Themes