A Vite plugin that stops one broken image from taking down your whole build.
If a page in your project imports an image that doesn't actually exist, maybe a typo in the filename, a file someone forgot to add, or an asset that got renamed, Vite normally stops the entire build with a Rollup "could not resolve" error. Nobody's build finishes until that one image gets fixed.
This plugin catches that case instead. When an image import doesn't resolve to a real file, it swaps in a placeholder image and prints a clear console warning naming exactly which import is missing. Your build finishes, your site still deploys, and you can go fix the actual image without everyone else being blocked on it.
Every image that does exist is left completely alone and passed straight through to Vite as usual. Only a genuinely missing one gets swapped.
npm install --save-dev @binarynoir/vite-plugin-image-fallback// vite.config.ts
import { defineConfig } from "vite";
import { imageFallbackPlugin } from "@binarynoir/vite-plugin-image-fallback";
export default defineConfig({
plugins: [imageFallbackPlugin()],
});Only relative (./foo.png) and root-relative (/foo.png) image imports are
intercepted — bare package specifiers (some-icon-package/icon.png) are left
alone so they still go through normal node_modules resolution. An existing
file is untouched and passed straight through to Vite's own asset pipeline;
only an import that doesn't resolve to a real file gets swapped. A
root-relative import counts as existing if it is in Vite's root or in its
publicDir, wherever that is (VitePress's default <srcDir>/public, or an
absolute path outside the root).
In vite dev (and vitepress dev), not every image is an import. VitePress
compiles a Markdown image such as  to a plain URL,
which the browser requests directly. Without this plugin, a missing image's
request gets the page's HTML back, and the browser shows its broken-image
icon.
The plugin answers those requests too. A request for an image that isn't in
the root or publicDir gets the placeholder, with the missing path drawn
into it, and logs the same warning, naming the page that requested it. A
custom fallback data URI is served as is; a custom fallback URL is
redirected to.
Every placeholder response carries an X-Image-Fallback header holding the
missing path (URI-encoded), and a HEAD request for a missing image gets a
200 with that header (never a redirect). The #image-fallback= hint can't
be added to these URLs, so on a dev-server page the client script (see
Hints) asks the server about each image with a HEAD request
instead, and shows the same outline and popover.
| Option | Default | Description |
|---|---|---|
extensions |
["jpg", "jpeg", "png", "gif", "svg", "webp", "avif"] |
File extensions (without the dot) treated as image imports. |
fallback |
a plain gray placeholder SVG reading "Missing Image Asset" | The asset returned in place of a missing image — a data URI, or any string Vite can serve as a URL. |
silent |
false |
Suppress the console warning normally emitted for each missing asset. |
hints |
true |
Record which import was missing, and where, in each placeholder's URL, for the on-page hints below. false uses fallback exactly as given. |
imageFallbackPlugin({
extensions: ["png", "jpg", "jpeg"],
fallback: "/branding/placeholder.png",
silent: false,
});Each placeholder can explain itself on the page, the same way
Broken Links and
HTML Escape explain their
fixes. Turn the hints on in the browser, in VitePress from your theme's
enhanceApp:
// .vitepress/theme/index.ts
import DefaultTheme from "vitepress/theme";
import { enableImageFallbackHints } from "@binarynoir/vite-plugin-image-fallback/client";
export default {
extends: DefaultTheme,
enhanceApp() {
enableImageFallbackHints();
},
};Every placeholder then gets a dashed outline. Hover it, or click or tap it, for a popover that names the missing import and the file and line that imported it:
plugins/image-fallback.md, line 19
./missing-image-demo.png could not be found, so a placeholder is shown
instead. Check the path and the file name, or add the image.The build warning names the same file and line. The plugin records them in a
#image-fallback=… fragment on the placeholder's URL, which the browser
ignores when it loads the image, so the placeholder looks the same with or
without the client script.
enableImageFallbackHints(options) takes:
| Option | Default | Description |
|---|---|---|
hover |
true |
Show the popover on mouse hover. false for click and tap only. |
hoverDelay |
300 |
Milliseconds the mouse rests on a placeholder before the popover shows. |
injectStyles |
true |
Inject the default styles. false lets you style img[src*="#image-fallback="] and img[data-image-fallback] yourself. |
devPlaceholders |
on a page the Vite dev server served | Find the dev server's placeholders with one HEAD request per same-origin image URL, including images added or changed later. |
On the dev server, each placeholder found gets a data-image-fallback
attribute holding the missing path, and fires a bubbling image-fallback
event whose detail.src is that path. To treat placeholders differently in
your own code, for example to skip them in a click-to-zoom handler, use
isImageFallback(img). It is true for built placeholders, and for dev ones
once they have been found:
import { isImageFallback } from "@binarynoir/vite-plugin-image-fallback/client";
if (isImageFallback(img)) return; // nothing to zoomThe colors follow the VitePress theme and can be changed with the
--image-fallback-hint-error CSS variable.
For most application code, a missing asset failing the build is exactly the right behavior — you want to know immediately. This plugin is aimed at content-heavy sites (docs, blogs, CMS-driven pages) where hundreds of Markdown/MDX pages reference images added by many different contributors, and one bad reference in page 400 of 500 shouldn't block deploying the other 499. It trades a hard failure for a loud warning plus a visibly-broken (but visually obvious) placeholder.
Releases are tag-triggered. To ship a new version, from a clean main that's
in sync with origin/main:
npm run release:patch # or release:minor / release:majorThis runs typecheck/lint/test/build locally, then npm version <bump>
(bumps package.json, commits, and creates a matching vX.Y.Z tag) and
git push --follow-tags. Pushing that tag triggers
.github/workflows/release.yml, which
re-runs the checks, publishes to npm (with
provenance), and
creates a GitHub release with auto-generated notes.
For a prerelease or an explicit version, use npm run release -- <arg>
(e.g. npm run release -- 1.2.3 or npm run release -- prerelease) — see
npm version for the
full list of accepted values.
This requires an NPM_TOKEN repository secret (an npm
automation token
with publish access) — set it under Settings → Secrets and variables →
Actions. First time publishing this package? See PUBLISHING.md.
Bug reports, feature requests and questions are all welcome. Please open them on this project's GitHub issue tracker:
- Found a bug? Report it. Include your version and the steps to reproduce it.
- Have an idea? Request a feature. Tell us what you are trying to do.
- Something else? Browse open issues to see if it has already been raised.
Pull requests are welcome too.
Image Fallback is one of a family of open-source VitePress plugins, editor extensions and command-line tools from BinaryNoir. Browse them all, with documentation and live demos, at binarynoir.tech.
VitePress plugins
- Auto Sidebar: sidebar built from your folders
- Auto Navbar: navbar built from your folders
- Sidebar Accordion: one open sidebar group at a time
- Markdown Tags: inline status badges
- Reading Time Tag: reading-time estimates
- Glossary Tooltips: hover definitions from one glossary file
- Downloads: downloadable files next to your pages
- Exclude: keep pages out of the build with a
.excludefile - README Index:
README.mdindex pages for VuePress migrations - HTML Escape: stray tags and braces shown as text, not build errors
- Broken Links: dead links shown as text, not build errors
Vite plugins
- Optimize Images: smaller images in the build output
Editor extensions
- Tags for Markdown for VS Code: status badges in the VS Code Markdown preview
- Tags for Markdown for Obsidian: status badges in Obsidian
Command-line tools
- NoirWatch: get notified when a web page changes
- NoirCon: get notified when a site, server or port goes down
- callrx: amateur radio callsign lookup
Website, docs and demos: binarynoir.tech
John Smith III, BinaryNoir. Thanks to all contributors and users for their support and feedback.