Skip to content

Vite Plugin ​

Process images at build time — same as the CLI but integrated into the Vite lifecycle. Runs on buildStart and re-runs in dev mode when source images change.

Integration Recipes show ready-made configurations for every case — with a note on what each is for and when to use it.

ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { vueImageKit } from '@macrulez/vue-image-kit/vite'

export default defineConfig({
  plugins: [
    vue(),
    vueImageKit({
      input: './src/images',
      output: './public/images',
      widths: [400, 800, 1200],
      manifest: './src/assets/images.ts',
    }),
  ],
})

All CLI options are supported. sharp must be installed as a dev dependency.

buildStart/handleHotUpdate call the same generate() the CLI does, so incremental generation applies here too — and is auto-enabled during vite dev specifically (not vite build) unless you set incremental explicitly. A single source-file change in dev reprocesses just that file, not the whole batch.

A hot update regenerates only for an image inside input and never for a file inside output, so the files the plugin writes itself cannot trigger another run. Runs never overlap: changes that arrive while one is running are collapsed into a single follow-up run. output may even sit inside input; it is skipped when the folder is scanned.

SVG images ​

A vector image has no hazehash, blurhash or thumbhash — only a dominant color and a size:

ts
import icon from './logo.svg?placeholder'
// → { placeholderColor: '#ee3333', width: 64, height: 64 }

The size is read from the file's own width/height (or viewBox) instead of rasterizing it, and a large SVG is rendered small for the color, so a 30000×30000 vector doesn't hit sharp's pixel limit. ?hazehash, ?blurhash and ?thumbhash on an SVG return an empty string and print a warning that points to ?placeholder. A corrupt SVG doesn't fail the build: the plugin warns and returns the declared size (or an empty object). A corrupt raster image still fails, loudly.

Build-time imports ​

The plugin also resolves query-suffixed imports, so you never wire props by hand — the metadata comes straight into your JS at build time:

ts
import meta from './photo.jpg?vik'
// → { src, srcset, webp, avif, width, height, placeholder, blurhash, thumbhash, name, src400, ... }

import hash from './photo.jpg?thumbhash'
// → 'base64string'

Pass the metadata straight to <VImage>'s image prop — no manual field wiring:

vue
<script setup lang="ts">
import meta from './hero.jpg?vik'
</script>

<template>
  <VImage :image="meta" alt="Hero" />
</template>
  • ?vik resizes/encodes the image into output and returns the full manifest entry (URLs use publicPath, exactly like the generated manifest). The ThumbHash is always included.
  • ?placeholder (with a field list), ?hazehash, ?blurhash, ?thumbhash, ?color, ?size, ?aspect and ?preview compute only the placeholder data and write no files — see Placeholders only and Choosing what to compute.

All of them re-run when the source image changes in dev. sharp is required; thumbhash is required for ThumbHash output.

TypeScript — enable typed ?vik / ?placeholder / ?hazehash / ?blurhash / ?thumbhash imports by referencing the bundled declarations once (e.g. in env.d.ts):

ts
/// <reference types="@macrulez/vue-image-kit/vite/client" />

Placeholders only, no resizing ​

When the project doesn't need responsive variants — only a blur while the image loads — turn the batch run off with generate: false. The plugin then touches nothing on its own; it only answers placeholder imports and the placeholders manifest, so input doesn't have to exist:

ts
// vite.config.ts
vueImageKit({ generate: false })
vue
<script setup lang="ts">
import placeholder from './photo.jpg?placeholder'
</script>

<template>
  <VImage src="/photo.jpg" v-bind="placeholder" alt="Photo" />
</template>

?placeholder returns the props of <VImage> ready to spread — { hazehash, placeholderColor, width, height } (blurhash instead of hazehash when the package is not installed) — computed at build time from the real file. Which hash it carries is set by the value:

  • ?placeholder — a HazeHash (a BlurHash when the hazehash package is not installed or mode is blurhash), the dominant color and the original size.
  • ?placeholder=hazehash — a HazeHash explicitly.
  • ?placeholder=blurhash — a BlurHash explicitly.
  • ?placeholder=thumbhash — a ThumbHash instead of the BlurHash.
  • ?placeholder=color — only the dominant color and the size.
  • ?hazehash / ?blurhash / ?thumbhash — just the hash string, export default 'LJ9+E%}}…'.

The values are identical to what the placeholders CLI command computes for the same file. With generate: false the ?vik import still works and still writes its resized variants into output when you ask for it.

Choosing what to compute ​

?placeholder takes a list of fields, so one import returns exactly what you need:

ts
import ph from './photo.jpg?placeholder=color,blurhash'
// → { blurhash: 'L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ', placeholderColor: '#fe0000' }

import all from './photo.jpg?placeholder=all&shape=raw'
// → { hazehash, blurhash, thumbhash, color, preview, size: { width, height }, aspect }

Fields are separated by commas (spaces work too), in any order, and a field named twice counts once:

  • hazehash — the HazeHash string (needs the hazehash package).
  • blurhash — the BlurHash string.
  • thumbhash — the ThumbHash as base64.
  • color — the dominant color as #rrggbb (an image with no opaque pixel has none, and the field is left out).
  • size — the real width and height.
  • preview — a tiny PNG as a data:image/png;base64,… URI, decoded from the ThumbHash. It is ready to use in a src or a CSS background without decoding anything on the page.
  • aspect — width / height as a number, for example 2 for a 200×100 image.
  • all — every field above.

Only the listed fields come back. Ask for size as well when <VImage> needs width and height. The older single values keep their shape: ?placeholder, ?placeholder=hazehash, ?placeholder=blurhash, ?placeholder=thumbhash and ?placeholder=color still return the hash plus color and size.

Result shape ​

&shape=props (the default) uses <VImage> prop names, so <VImage v-bind="ph" /> works: color becomes placeholderColor, preview becomes placeholder, size becomes width and height. aspect is not a <VImage> prop, so it is skipped with a warning. &shape=raw keeps the names as listed and returns size as an object, with aspect included:

ts
import raw from './photo.jpg?placeholder=color,size,aspect&shape=raw'
// → { color: '#fe0000', size: { width: 200, height: 100 }, aspect: 2 }

One value at a time ​

Each field also has its own import that returns the bare value:

ts
import color from './photo.jpg?color' // '#fe0000'
import size from './photo.jpg?size' // { width: 200, height: 100 }
import aspect from './photo.jpg?aspect' // 2
import preview from './photo.jpg?preview' // 'data:image/png;base64,…'
import blur from './photo.jpg?blurhash' // 'L6T9R…'
import thumb from './photo.jpg?thumbhash' // '1fsDBI…'

A value that could not be computed comes back as '' (or 0 for ?aspect and 0×0 for ?size). A bare ?size, ?color, ?preview or ?aspect is required — ?size=large is left alone, so another plugin's parameter with the same name is not taken over.

Settings for one import ​

Any of these imports accepts the settings of the hashes tuning for that one file only:

  • components — BlurHash components, 4x3 by default, each side 1–9.
  • sample — the size the image is downscaled to before hashing, 8–256.
  • strategy — dominant (default) or average for the color.
ts
import hash from './photo.jpg?blurhash&components=6x4'
import ph from './photo.jpg?placeholder=blurhash,color&sample=64&strategy=average'

An invalid value prints a warning and the default is used. The settings of a single import never touch the shared cache or the placeholders manifest.

Typing a list ​

/// <reference types="@macrulez/vue-image-kit/vite/client" /> types the fixed imports (?color, ?size, ?placeholder=color, …). A list or an import with settings cannot be typed that way: a TypeScript module pattern allows only one *, and it is already needed for the file path. Let the plugin write the declarations instead:

ts
vueImageKit({ types: true })

The plugin records every such import it sees and writes src/vik-imports.d.ts (types: 'path/to/file.d.ts' picks another place). A list is typed loosely, with every field optional (hazehash?: string, blurhash?: string, placeholderColor?: string, …), so the order and the set of fields don't matter. Entries are only ever added, never removed, so a dev server that has only loaded some pages doesn't shrink the file; delete the file to regenerate it. Until an import has been seen once, the editor reports it as an unknown module.

Examples ​

Blur and size for one image. size gives <VImage> its width and height, so the page doesn't jump when the image loads:

vue
<script setup lang="ts">
import photo from './assets/photo.jpg'
import ph from './assets/photo.jpg?placeholder=blurhash,size'
</script>

<template>
  <VImage :src="photo" v-bind="ph" alt="Photo" />
</template>

A ready-made preview as a CSS background. ?preview is a data URI of a tiny PNG, so nothing has to be decoded on the page:

vue
<script setup lang="ts">
import hero from './assets/hero.jpg'
import preview from './assets/hero.jpg?preview'
</script>

<template>
  <section class="hero" :style="{ backgroundImage: `url(${preview})` }">
    <img :src="hero" alt="" loading="lazy" />
  </section>
</template>

Reserve the space with the aspect ratio.

vue
<script setup lang="ts">
import cover from './assets/card.jpg'
import aspect from './assets/card.jpg?aspect'
</script>

<template>
  <div :style="{ aspectRatio: aspect }">
    <img :src="cover" alt="" />
  </div>
</template>

Several values at once, under their own names.

ts
import raw from './assets/photo.jpg?placeholder=color,size,aspect&shape=raw'

const { color, size, aspect } = raw
console.log(color, size.width, size.height, aspect)

A whole folder with import.meta.glob. The query is the same string:

ts
const placeholders = import.meta.glob('./assets/gallery/*.jpg', {
  query: '?placeholder=blurhash,size',
  import: 'default',
  eager: true,
})

Settings for one image only.

ts
import hash from './assets/banner.jpg?blurhash&components=8x3'
import avg from './assets/banner.jpg?color&strategy=average'

Typings for the lists. Reference the bundled declarations once, and let the plugin write the rest:

ts
// vite.config.ts
vueImageKit({ generate: false, types: true })
ts
// env.d.ts
/// <reference types="@macrulez/vue-image-kit/vite/client" />

SVG ​

A vector image only has color, size and aspect. Asking for other fields skips them with a warning. See SVG images.

Placeholder options ​

Every placeholder setting lives under one key, placeholders: in the options of the Vite plugin, or of the Nuxt module. This section shows all of them together and says what each one does; the sections after it explain the parts in detail.

In a Vite project ​

ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { vueImageKit } from '@macrulez/vue-image-kit/vite'

export default defineConfig({
  plugins: [
    vue(),
    vueImageKit({
      generate: false,
      types: true,
      placeholders: {
        mode: 'blurhash',
        tuning: { components: [4, 3], sample: 100, color: 'dominant' },
        imports: {
          extensions: ['.png', '.jpg', '.jpeg', '.webp', '.avif'],
          exclude: ['src/assets/sprites/'],
          preview: ['src/assets/hero/', '**/cover-*.webp'],
        },
        dirs: ['public/images', { dir: 'src/img', urlPrefix: '/assets/img' }],
        urls: ['https://cdn.example.com/hero.jpg'],
        root: '.',
        publicDir: 'public',
        concurrency: 4,
        timeout: 15000,
        maxBytes: 15 * 1024 * 1024,
        refreshRemote: false,
      },
    }),
  ],
})

In a Nuxt project ​

The module registers the Vite plugin itself, so the same placeholders object goes under vueImageKit in nuxt.config.ts. generate: false is set for you, and relative paths are resolved from the Nuxt root:

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@macrulez/vue-image-kit/nuxt'],
  vueImageKit: {
    placeholders: {
      mode: 'blurhash',
      tuning: { components: [4, 3], sample: 100, color: 'dominant' },
      imports: {
        extensions: ['.png', '.jpg', '.jpeg', '.webp', '.avif'],
        exclude: ['src/assets/sprites/'],
        preview: ['src/assets/hero/', '**/cover-*.webp'],
      },
      dirs: ['public/images', { dir: 'src/img', urlPrefix: '/assets/img' }],
      urls: ['https://cdn.example.com/hero.jpg'],
      manifest: './image-placeholders.ts',
    },
  },
})

You rarely need all of it. The smallest useful setting is placeholders: { imports: true }; add preview only for the images that need a blur in the server-rendered HTML.

Options of the Vite plugin that matter here ​

  • generate — boolean · default: true. Run the batch processing of input into output. false leaves only the placeholder features: the plugin touches no file and input doesn't have to exist. The Nuxt module always sets it to false for the placeholder plugin.
  • types — boolean | string · default: off. Write a .d.ts for the imports with a field list or settings; true writes src/vik-imports.d.ts, a string picks another path. See Typing a list.
  • placeholders — object. Everything below.

placeholders ​

  • imports — boolean | object · default: off. Compute a placeholder for every image a script or single-file component imports, with no query and no template edits. true takes the defaults of the three settings below; an object tunes them. See A placeholder for every imported image.
    • imports.extensions — string[] · default: ['.png', '.jpg', '.jpeg', '.webp', '.avif', '.gif', '.tif', '.tiff']. The file types to take. SVG is never taken.
    • imports.exclude — string[] · default: []. Images to skip. A list of path patterns.
    • imports.preview — boolean | string[] · default: false. Also render a ready data: preview and register it, so <VImage ssr-placeholder> can put it into the server-rendered HTML. true is every imported image; a list of path patterns is only the images it picks. Needs the thumbhash package. See A ready preview for server rendering.
  • mode — 'hazehash' | 'blurhash' | 'thumbhash' | 'color' · default: 'hazehash' when the hazehash package is installed, otherwise 'blurhash'. What a placeholder carries besides its dominant color and size: a HazeHash, a BlurHash, a ThumbHash, or nothing more. It is the mode of the automatic imports, of a bare ?placeholder import and of every manifest entry.
  • tuning — { components?, sample?, color?, budget? }. How hashes and colors are computed: BlurHash components (default [4, 3]), the size an image is downscaled to before hashing (default 100), and 'dominant' or 'average' for the color (default 'dominant'). See Tuning the hashes.
  • dirs — (string | { dir, urlPrefix? })[]. Folders whose images get an entry in the placeholders manifest (virtual:vue-image-kit/placeholders), for images whose src is built at runtime. urlPrefix is the URL the folder is served under when it lies outside the public directory.
  • urls — string[]. Remote or CDN images that are downloaded at build time and get a manifest entry.
  • manifest — string, Nuxt only. A manifest file (the one npx vue-image-kit placeholders writes) to merge with the folders; its entries win.
  • root — string · default: the project root. The directory that relative dirs are resolved from. Nuxt sets it to the Nuxt root. (exclude and preview patterns are always measured from the project root of Vite.)
  • publicDir — string · default: the Vite public directory (public). Files inside it are keyed in the manifest by their URL path.
  • concurrency — number · default: 4. Images processed in parallel.
  • timeout — number · default: 15000. Milliseconds to wait for a remote image.
  • maxBytes — number · default: 15728640 (15 MB). A remote image larger than this is skipped.
  • refreshRemote — boolean · default: false. Download remote images again even if a copy is cached.

Results are cached in node_modules/.cache/vue-image-kit/placeholders.json; a local file is recomputed only when its modification time or size changes, and changing tuning drops the cache.

A placeholder for every imported image ​

When a project imports its images (import hero from './hero.webp', or a plain src="@/assets/hero.webp" in a template), turn on imports and every one of them gets its placeholder at build time, with no query and no edit to any template:

ts
// vite.config.ts
vueImageKit({
  generate: false,
  placeholders: { imports: true },
})
ts
// nuxt.config.ts
vueImageKit: {
  placeholders: { imports: true },
}
vue
<script setup lang="ts">
import hero from '@/assets/hero.webp'
</script>

<template>
  <VImage :src="hero" alt="Hero" />
</template>

<VImage> finds the blur and the real size on its own. The plugin computes them from the file, then registers them under the value the import produced — the dev URL, or the hashed URL in a production build — so the lookup by src works in dev, in the build and during server rendering.

What is covered: default imports of .png, .jpg, .jpeg, .webp, .avif, .gif and .tif files in scripts and single-file components, the imports Vue generates from src="…" in a template, and import.meta.glob(…, { eager: true }) (Vite expands it into imports). Not covered: SVG, imports with a query (?url), lazy globs (import()), and URLs built at run time. For those, import with ?placeholder or use a manifest from folders.

Settings go next to imports:

ts
placeholders: {
  mode: 'thumbhash',
  imports: {
    extensions: ['.png', '.webp'],
    exclude: ['/sprites/', 'canvas-'],
  },
}
  • mode — hazehash (default when the hazehash package is installed), blurhash (default otherwise) or thumbhash, the same option as for the other placeholders; thumbhash needs the thumbhash package.
  • extensions — the file types to take.
  • exclude — images to skip, for ones that are never shown through <VImage> (a canvas sprite, say). Takes the same path patterns as preview.
  • preview — true (every imported image) or a list of path patterns: also render a ready data: preview. See A ready preview for server rendering. Off by default.

A corrupt image prints a warning and simply gets no placeholder; it doesn't fail the build. sharp must be installed as a dev dependency, and the first build computes every image once and keeps the result in the same cache as the other placeholders.

Choosing images by path ​

exclude and preview take a list of patterns. Each pattern is compared with the real path of the file, relative to the project root (src/assets/hero/a.webp), so it doesn't matter how the file is imported: @/assets/hero/a.webp, ~/assets/… and ../assets/… all point at the same path. In the dev server the path is the one the browser requests, with the base cut off.

  • A directory — a pattern ending in /: 'src/assets/hero/' takes everything inside that directory, at any depth.
  • An exact file — the full relative path: 'src/assets/hero/cover.webp'.
  • A part of a path — any other text is looked for inside the path: 'hero', '-large', '/sprites/'.
  • A glob — a pattern with * is matched against the whole path: * stands for any characters inside one directory name, ** for any number of directories. 'src/assets/*.png' takes the PNG files directly in src/assets, 'src/**/hero-*.webp' takes the matching files anywhere under src.

A leading ./ or / in a pattern is ignored. When a path can't be worked out (an import the bundler can't resolve), the pattern is compared with the import text as written.

A ready preview for server rendering ​

The blur that imports registers is a hash: it is decoded in the browser, so it shows up only after JavaScript has run. For the images that must have a blur in the server-rendered HTML, ask for a ready preview too:

ts
placeholders: {
  imports: {
    preview: ['src/assets/hero/', '**/cover-*.webp'],
  },
}
  • preview: true — a preview for every imported image.
  • preview: ['src/assets/hero/', '**/cover-*.webp'] — only for the images the patterns pick, by directory, by exact file or by glob (see Choosing images by path). Use this to keep the bundle small.
  • absent / false — no previews (default).

The plugin renders each preview (a small PNG as a data: URL) and registers it with the other fields, but nothing reaches the HTML by itself. The image says so explicitly with ssrPlaceholder:

vue
<VImage :src="hero" alt="Hero" ssr-placeholder />

The server then renders the <img> with the preview as its background; any <VImage> without the prop is unchanged. A preview adds a few kilobytes per image to the bundle (and to the HTML of the pages that use it), which is why it is a separate switch and why a list is better than true for a large project.

The preview is rendered from a ThumbHash, so the thumbhash package must be installed as a dev dependency, even when mode is blurhash. For one image without imports, import it directly — import preview from './hero.webp?preview' — and pass it as :placeholder="preview". See Preview in the server-rendered HTML.

Server-rendered preview, step by step ​

  1. Install the dependencies. The preview is rendered from a ThumbHash with sharp, so both packages are needed at build time, whatever mode is:

    bash
    npm install -D sharp thumbhash
  2. Turn on the preview in the config, for the images that need it. In Vite:

    ts
    // vite.config.ts
    vueImageKit({
      generate: false,
      placeholders: {
        imports: { preview: ['src/assets/hero/'] },
      },
    })

    In Nuxt:

    ts
    // nuxt.config.ts
    export default defineNuxtConfig({
      modules: ['@macrulez/vue-image-kit/nuxt'],
      vueImageKit: {
        placeholders: {
          imports: { preview: ['src/assets/hero/'] },
        },
      },
    })

    imports.preview is what makes the plugin render the preview and keep it in the registry. Without it there is nothing for ssr-placeholder to show.

  3. Mark the images that must show it, with ssr-placeholder on <VImage>. The image is imported as usual:

    vue
    <script setup lang="ts">
    import hero from '@/assets/hero/cover.webp'
    </script>
    
    <template>
      <VImage :src="hero" alt="Cover" :width="1200" :height="600" ssr-placeholder />
    </template>
  4. Restart the dev server — module and plugin options are read on start.

  5. Check the HTML the server sends. For a lazy image there is a transparent pixel instead of the real src, the preview as a background, and the real image in <noscript>:

    html
    <img
      src="data:image/gif;base64,R0lGOD…"
      alt="Cover"
      width="1200"
      height="600"
      loading="lazy"
      style="background-image:url(data:image/png;base64,iVBOR…);background-size:cover;…"
    />
    <noscript><img src="/_nuxt/cover.3f9a.webp" alt="Cover" width="1200" height="600" /></noscript>

    If src is the real file and there is no background-image, the image has no preview: check that the file matches a pattern in imports.preview, that thumbhash is installed, and that the server was restarted.

Which images get a preview (the config) and which <VImage> use it (the prop) are separate on purpose: the plugin can't know at build time which component receives which image, and a preview costs a few kilobytes, so it is only made where you ask for it.

Placeholders manifest from folders ​

Set placeholders.dirs and the plugin computes a placeholders manifest for every image in those folders — including images whose src is built at runtime. The manifest is the default export of the virtual module virtual:vue-image-kit/placeholders:

ts
vueImageKit({
  generate: false,
  placeholders: {
    dirs: ['public/images', { dir: 'src/img', urlPrefix: '/assets/img' }],
    urls: ['https://cdn.example.com/hero.jpg'],
    mode: 'blurhash',
  },
})
ts
import placeholders from 'virtual:vue-image-kit/placeholders'

app.use(VImageKitPlugin, { placeholders })
  • dirs — (string | { dir, urlPrefix })[]. Folders scanned recursively, relative to the project root. A file under the public directory is keyed by its path from there; a folder outside it needs a urlPrefix — without one its files are keyed by their project path (/src/img/a.jpg), which only matches the dev-server URL, and the build warns about it.
  • urls — string[]. Remote or CDN images, downloaded at build time; CDN URLs are fetched as a small rendition.
  • mode — 'hazehash' | 'blurhash' | 'thumbhash' | 'color' · default: hazehash when the package is installed, otherwise blurhash. What each entry carries besides its dominant color and size. It is also the default for a bare ?placeholder import.
  • tuning — see Tuning the hashes.
  • publicDir, concurrency, timeout, maxBytes, refreshRemote — the public directory (default: public) and the same download limits as the placeholders command.

The module is rebuilt when a file in dirs is added, removed or changed during vite dev, and its results are cached in node_modules/.cache/vue-image-kit/placeholders.json — the same cache the placeholders command uses — so a repeated build only reads the images that changed. ?placeholder, ?blurhash and ?thumbhash use that cache too.

The type of the module comes with the other declarations of @macrulez/vue-image-kit/vite/client.

Tuning the hashes ​

Every place that computes a placeholder — the plugin's ?placeholder/?blurhash/?thumbhash imports and its manifest, the placeholders command and the Nuxt option — takes the same tuning object:

ts
vueImageKit({
  generate: false,
  placeholders: {
    dirs: ['public/images'],
    tuning: { components: [6, 4], sample: 128, color: 'average' },
  },
})
  • components — [x, y] · default: [4, 3]. BlurHash components per axis, 1–9 each. More components keep more detail and make the string longer.
  • budget — number · default: 28. Size of a HazeHash string in bytes, 7–48. Also ?hazehash&budget=16 for one import.
  • sample — number · default: 100. The longest side, in pixels, that the image is downscaled to before hashing; 8–256. A ThumbHash is always computed from at most 100px.
  • color — 'dominant' | 'average' · default: 'dominant'. How the placeholder color is chosen: the most frequent color, or the mean of all opaque pixels.

Changing the tuning drops the cache, so no stale hash is reused.

On-demand dev serving ​

Both the CLI and the build-time imports above are batch/ahead-of-time — they process images before they're requested. If you'd rather not run a build step at all during development, set dev.onDemand: true and images resize on request instead, cached to disk after the first hit:

ts
vueImageKit({
  dev: { onDemand: true }, // mounts a handler at /_vik/image during `vite dev`
})
html
<img src="/_vik/image?src=/photos/cat.jpg&w=800&format=webp" />

This is dev-only — configureServer (the Vite hook it uses) never runs during vite build. For production without a CDN, mount the same handler in your own server — see Self-hosted on-demand server.

dev options ​

The handler behind dev.onDemand is the same createImageHandler the self-hosted server uses — root is fixed to Vite's own project root (server.config.root), everything else is the same tuning surface:

  • onDemand — boolean · default: false. Mounts the handler during vite dev.
  • route — string · default: /_vik/image. Route to mount it at.
  • cacheDir — string · default: <root>/.vik-cache. See Self-hosted on-demand server → Options.
  • maxAge — number · default: 31536000 (1 year).
  • allowedWidths — number[], optional. Restrict w to exactly these values.
  • maxWidth — number · default: 4000. Upper bound for w when allowedWidths isn't set.