Skip to content

Usage

Before using oh-image in your components, you need to register its Vite plugin. This is what enables oh-image to process, resize, and optimize your images at build time. Open your vite.config.ts and add ohImage() to the plugins array:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { ohImage } from "@lonik/oh-image/plugin";
export default defineConfig({
plugins: [ohImage(), react()],
});

That’s it — the plugin uses sensible defaults out of the box, but you can customize it if needed:

ohImage({
format: "webp", // output format (default: "webp")
placeholder: true, // generate placeholders globally (default: true)
bps: [640, 828, 1200, 1920], // breakpoints for responsive srcset generation
});

You can also override these settings on a per-image basis using query parameters — more on that below.

Import any image with the ?oh query parameter and pass it to the <Image> component — oh-image takes care of the rest:

import { Image } from "@lonik/oh-image/react";
import photo from "./assets/photo.jpg?oh";
function App() {
return <Image src={photo} alt="A photo" />;
}

Behind the scenes, oh-image transforms this into a fully optimized <img> element. Here’s what the rendered output looks like:

<img
src="/assets/photo.800x600.webp"
srcset="
/assets/photo.640w.webp 640w,
/assets/photo.828w.webp 828w,
/assets/photo.1200w.webp 1200w,
/assets/photo.1920w.webp 1920w
"
width="800"
height="600"
alt="A photo"
fetchpriority="auto"
style="
background-position: 50% 50%;
background-repeat: no-repeat;
background-image: url(data:image/webp;base64,…);
background-size: cover;
"
/>

A single <img> tag — no wrapper divs, no extra DOM nodes. The responsive srcset is generated automatically from your configured breakpoints, and the placeholder is applied as a CSS background image directly on the element, fading out once the full image loads.

Per-image optimization with query parameters

Section titled “Per-image optimization with query parameters”

The global plugin config is a great starting point, but sometimes you need finer control over how a specific image is processed. You can customize optimization on a per-image basis by appending query parameters to the import path:

// Convert to AVIF instead of the default format
import hero from "./assets/hero.jpg?oh&format=avif";
// Resize to a specific width and height
import thumbnail from "./assets/photo.jpg?oh&width=400&height=300";
// Disable placeholder with a custom blur amount
import banner from "./assets/banner.jpg?oh&placeholder=false";
// Use custom breakpoints for this image only
import portrait from "./assets/portrait.jpg?oh&bps=640,1080";

Vite only processes files that are imported as modules. Images placed in the public/ folder are served as-is and are not processed by any Vite plugin, including oh-image.

For oh-image to process your images, they must be placed in the src/assets/ folder (recommended) or any other directory inside src/:

my-app/
├── public/
│ └── logo.svg ← served as-is, NOT optimized
├── src/
│ ├── assets/
│ │ └── hero.jpg ← ✅ processed by oh-image
│ └── components/
│ └── images/
│ └── banner.png ← ✅ also works from any src/ subdirectory

Then import them with the ?oh query parameter as usual:

import hero from "./assets/hero.jpg?oh";