Oh Image is an image component library for React and Vite apps. It ships with a lightweight yet powerful Image component plus a Vite optimizer plugin that automatically optimizes static assets. Depending on whether your image is local or fetched from a CDN, you can choose the integration that fits best. The quick examples below give you a taste of the project.
Vite plugin
The Vite plugin's main purpose is to generate an Image component automatically for each static import. Suppose you want to load a hero image on the homepage from the assets folder:
import HeroImage from "./assets/hero.jpg"; // this will return image url
function App() {
return <HeroImage />;
}
In this case the src value is just the URL for the image, so you have to
manually handle every detail that makes it responsive and optimized. Now add the
$oh query parameter suffix to the import and let the oh-image Vite plugin take
care of the rest:
import HeroImage from "./assets/hero.jpg?$oh"; // this will return Image component
function App() {
return <HeroImage alt="Hero image" />;
}
This returns the Image component, whose inner <img> element renders markup
similar to the following:
<img class="flex" width="1920" height="1280" loading="lazy" fetchpriority="auto"
alt="Hero image"
srcset="/@oh-images/breakpoint-16-deab89be27aab77b-d9c45d4b5795047a-hero.webp 16w,
/@oh-images/breakpoint-48-deab89be27aab77b-d9c45d4b5795047a-hero.webp 48w,
/@oh-images/breakpoint-96-deab89be27aab77b-d9c45d4b5795047a-hero.webp 96w,
/@oh-images/breakpoint-128-deab89be27aab77b-d9c45d4b5795047a-hero.webp 128w,
/@oh-images/breakpoint-384-deab89be27aab77b-d9c45d4b5795047a-hero.webp 384w,
/@oh-images/breakpoint-640-deab89be27aab77b-d9c45d4b5795047a-hero.webp 640w,
\/@oh-images/breakpoint-750-deab89be27aab77b-d9c45d4b5795047a-hero.webp 750w,
/@oh-images/breakpoint-828-deab89be27aab77b-d9c45d4b5795047a-hero.webp 828w,
/@oh-images/breakpoint-1080-deab89be27aab77b-d9c45d4b5795047a-hero.webp 1080w,
/@oh-images/breakpoint-1200-deab89be27aab77b-d9c45d4b5795047a-hero.webp 1200w,
/@oh-images/breakpoint-1920-deab89be27aab77b-d9c45d4b5795047a-hero.webp 1920w"
src="/@oh-images/main-deab89be27aab77b-d9c45d4b5795047a-hero.webp"
style="background-position: 50% 50%; background-repeat: no-repeat;
background-image: url("/@oh-images/placeholder-deab89be27aab77b-d9c45d4b5795047a-hero.webp");
background-size: cover;"
sizes="auto, 100vw"
>
It's a lot of HTML, but it gives you a feel for the benefits you get automatically:
- width pulled from the image metadata
- height pulled from the image metadata
srcsetentries that cover a wide range of devices- a placeholder background while the image loads
- WebP output even if the source asset is JPEG
- sensible default attributes (e.g.,
sizes) to maximize performance
That's a solid payoff for adding a single suffix.
Let's adjust our HeroImage. Hero images often need to load immediately, and with oh-image you only need to set one prop:
function App() {
return <HeroImage priority />;
}
The generated <img> is now configured to load as soon as possible.
This setup is perfect for statically loaded images. If you need to fetch assets
from an image CDN—or simply want to render an <img> from the public
directory—reach for the Image component itself.
Image component
The Image component is a powerful wrapper around native img element that you can use it directly. It exposes a collection of props
that keep images responsive and fast. For example:
import { Image } from "@lonik/oh-image/react";
function App() {
return (
<Image
src="some-image.jpg"
width={800}
height={600}
breakpoints={[16, 32, 64, 128]}
/>
);
}
We'll cover these props in later guides.
Using the component this way won't
optimize a local file, but it still gives you niceties such as automatically
generated srcset values and sensible performance defaults.
The component really shines when combined with loaders.
Loaders
Loaders bridge the Image component and your image CDN provider. They offer
out-of-the-box integrations with popular services—just pass the appropriate
loader to the loader prop.
Using Cloudinary as an example:
import { Image } from "@lonik/oh-image/react";
import { useCloudinaryLoader } from "@lonik/oh-image/cloudinary";
function App() {
const loader = useCloudinaryLoader({
path:"https://res.cloudinary.com/example"
transforms: {
rotate: 180,
},
});
return (
<Image
width={600}
height={400}
placeholder
breakpoints={[16, 32, 64, 128, 256, 512]}
loader={loader}
src="car.jpg"
/>
);
}
The Image component creates all required attributes and uses the loader to build
the correct URLs for the CDN. In addition to the main src, it generates srcset
entries and placeholder URLs. In the example above, the src might look like
https://res.cloudinary.com/example/image/upload/w_600,h_400,f_webp,r_180/car.jpg,
which returns a 600 × 400 WebP image rotated by 180 degrees.
We currently support three providers, with more on the roadmap. Creating a custom loader is as simple as writing a function that accepts options and returns the desired URL.
Hopefully this gives you a sense of what the library offers. The next guides will explore these ideas in more depth.
Why?
If you're used to creating <img> tags with only src and alt, you might
wonder why you need a library at all. Even with minimal requirements, you'll
benefit from automatic optimization and effortless CDN fetching. The real power
shows up once you understand how images work on the web. I highly recommend
reading Web.dev Images—it's an excellent guide
that will give you the knowledge and confidence to rely on this library instead
of a plain <img> tag when you need consistent performance across devices.