Build-time image tooling for modern web apps. Generate lightweight placeholders, responsive image variants, and matching client helpers without shipping extra runtime code.
nocojs can work both on the client side during bundling and on the server side in build scripts or SSR frameworks.
For client side, with appropriate bundler integration, nocojs scans your source during bundling, finds calls to placeholder(), and replaces them with inline data URLs.
On the server (Astro, NextJS, Tanstack Start) you can call getPlaceholder() or getOptimizedImage() to create the same assets programmatically.
Note: nocojs focuses on build-time generation, you will have to handle lazy-loading yourself. Pair it with your preferred lazy-loading or progressive enhancement strategy, such as:
- Zero runtime overhead – placeholders are inlined during the build.
- Multiple placeholder types – blurred, grayscale, dominant-color, average-color, transparent, or the default miniaturized version.
- Responsive outputs –
getOptimizedImage()creates multi-format, multi-width srcsets and optional placeholders in one call. - Bundler integrations – works with Webpack, Rspack, Rollup/Vite, and Parcel.
Install the main package along with the bundler integration (if you require client-side support) that matches your stack:
# Pick the integration that matches your bundler
npm install nocojs @nocojs/rollup-plugin # Rollup / Vite
npm install nocojs @nocojs/webpack-loader # Webpack / Next.js
npm install nocojs @nocojs/rspack-loader # Rspack
npm install nocojs @nocojs/parcel-transformer # Parcelimport { placeholder } from "nocojs/client";
export function HeroImage() {
return <img src={placeholder("/images/hero.jpg")} alt="Hero" />;
}With an integration configured, the bundler replaces the call above with a base64 data URI during the build.
import { getPlaceholder, getOptimizedImage } from "nocojs";
const heroPlaceholder = await getPlaceholder("./public/hero.jpg", {
placeholderType: "blurred",
width: 16,
});
const responsive = await getOptimizedImage("./public/hero.jpg", {
outputDir: "./public/generated",
baseUrl: "/generated",
widths: [640, 960, 1280],
formats: ["webp", "jpg"],
});
console.log(heroPlaceholder.placeholder);
console.log(responsive.srcset);import { defineConfig } from "vite";
import { rollupNocoPlugin } from "@nocojs/rollup-plugin";
export default defineConfig({
plugins: [
rollupNocoPlugin({
publicDir: "public",
cacheFileDir: ".nocojs",
placeholderType: "blurred",
width: 12,
}),
],
});// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.(js|ts|jsx|tsx)$/,
use: [
{
loader: "@nocojs/webpack-loader",
options: {
publicDir: "public",
cacheFileDir: ".nocojs",
placeholderType: "blurred",
width: 12,
},
},
],
},
],
},
};Bundler integrations forward these options to the core transformer:
interface TransformOptions {
placeholderType?: "normal" | "blurred" | "grayscale" | "dominant-color" | "average-color" | "transparent";
width?: number;
height?: number;
wrapWithSvg?: boolean;
cache?: boolean;
replaceFunctionCall?: boolean;
publicDir?: string;
cacheFileDir?: string;
logLevel?: "none" | "error" | "info" | "verbose";
}interface GetPlaceholderOptions {
placeholderType?: "normal" | "blurred" | "grayscale" | "dominant-color" | "average-color" | "transparent";
width?: number;
height?: number;
wrapWithSvg?: boolean;
cache?: boolean;
cacheFileDir?: string;
_enableLogging?: boolean;
}interface GetOptimizedImageOptions {
outputDir: string;
widths?: number[];
baseUrl?: string;
formats?: string[];
quality?: number;
namingPattern?: string;
placeholderOptions?: GetPlaceholderOptions | null;
cache?: boolean;
}-
Use static, analyzable paths: Always provide fixed string literals or easily resolvable paths
placeholder('/images/hero.jpg') // ✅ Good placeholder('https://cdn.example.com/image.jpg') // ✅ Good
-
Use with lazy loading libraries: Combine with libraries like
react-intersection-observer,lozad.js, orlazysizes -
Keep placeholders small: Default 12px width is optimized for performance
-
Use consistent placeholder types: Stick to one type across your application for visual consistency
-
Avoid dynamic arguments: The build-time parser cannot resolve dynamic values
const imagePath = '/images/photo.jpg'; placeholder(imagePath) // ❌ Bad - dynamic variable placeholder(`/images/${filename}`) // ❌ Bad - template literal with variables placeholder(getImagePath()) // ❌ Bad - function call result
-
Don't use with conditionals: Build-time analysis requires static calls
placeholder(condition ? 'img1.jpg' : 'img2.jpg') // ❌ Bad - conditional expression
-
Avoid runtime modifications: The
placeholder()function is replaced at build timeconst result = placeholder('/image.jpg'); const modified = result + '?v=1'; // ❌ Bad - modifying the result
Important: All placeholder() function calls must be statically analyzable at build time. The arguments must be string literals or easily resolvable static expressions that the build tool can evaluate without executing your code.
- Use static string literals for
placeholder()so the transformer can resolve paths. - Point relative paths to your
publicDir(for exampleplaceholder("/images/photo.jpg")). - Combine the generated placeholders with a lazy-loading strategy to avoid layout shifts.
- Keep the cache directory (default
.nocojs) between builds for faster CI/CD pipelines.
Integration and usage examples can be found in the examples repo
nocojs stores metadata and generated placeholders in .nocojs/cache.db (configurable). Restoring this directory between builds allows the transformer and server APIs to reuse prior results.
Netlify
export const onPreBuild = async ({ utils }) => {
await utils.cache.restore(".nocojs");
};
export const onPostBuild = async ({ utils }) => {
await utils.cache.save(".nocojs");
};This monorepo contains the following packages:
nocojs– top-level package that re-exports the clientplaceholder()helper and the server utilitiesgetPlaceholder()andgetOptimizedImage().@nocojs/core– TypeScript engine that implements the transformer, placeholder generation, optimized image pipeline, and cache management.@nocojs/rollup-plugin– Rollup/Vite integration.@nocojs/webpack-loader– Webpack (and Next.js) loader.@nocojs/rspack-loader– Rspack loader.@nocojs/parcel-transformer– Parcel integration.
This is a Lerna workspace with packages written in TypeScript.
packages/
├── core/ # TypeScript engine shared by all integrations
├── nocojs/ # Aggregated public API (client + server)
├── rollup-plugin/ # Rollup / Vite integration + example
├── webpack-loader/ # Webpack integration + example
├── rspack-loader/ # Rspack integration
└── parcel-transformer/ # Parcel integration + example
# Enable pnpm via Corepack (once per environment)
corepack enable
# Install dependencies
pnpm install
# Build all packages
pnpm build:packages
# Run tests
pnpm test- Fast builds –
oxc-parserprovides speedy AST traversal and transformation. - Efficient processing –
sharppowers resizing and color extraction. - Tiny payloads – placeholders are typically under 1 KB and served inline.
MIT
Contributions are welcome! To get started:
- Fork and clone the repo.
- Run
pnpm installto bootstrap dependencies. - Use
pnpm build:packagesandpnpm testto validate changes. - Add or update documentation when introducing new features.
- Create a changeset with
pnpm changesetbefore opening a pull request.
Create issues for bugs or ideas, or start a discussion if you’re planning a larger feature.