Skip to content

Repository files navigation

Utilbench

Fast, private browser utilities for developers. No servers, no tracking — just tools that work.

Utilbench is a client-side utility tools SPA. Every tool runs entirely in the browser using Web Workers, WASM, and on-device AI models. Your files are never uploaded — nothing you drop into a tool leaves your machine.

Features

  • 33 tools across data, text, and media categories
  • 100% client-side — no backend, no analytics, no uploads. Assets (code chunks, WASM, AI models) are fetched from the same origin on demand; your data never is.
  • On-device AI — ONNX Runtime for background removal, TensorFlow.js + ESRGAN for image upscaling, all inference in-browser
  • Off-main-thread compute via a managed Web Worker pool (2–4 threads, task queue, cancellation, 30 s default timeout)
  • Cached WASM loader with streaming compilation and ArrayBuffer fallback
  • Cmd/Ctrl + K global fuzzy search across all tools (Fuse.js)
  • URL-state sync — many tools serialize settings to the query string so links are shareable
  • Per-tool preferences persisted to localStorage with debounced writes
  • Lazy-loaded routes with per-tool skeleton loaders
  • SEO-ready — prerendered per-route HTML shells, meta tags, OG images, JSON-LD, auto-generated sitemap

Tools

Data & Documents

Tool Description
JSON Formatter Format, validate, and beautify JSON with syntax highlighting
JSON Schema Generator Generate JSON Schema from sample JSON data
JWT Decoder Decode and inspect JWT tokens without sending data to a server
Base64 Encoder Encode and decode Base64 strings
CSV to JSON Convert CSV files to JSON with customizable options
YAML to JSON Convert YAML data to JSON format
Merge PDF Combine multiple PDF files into one, in any order
Split PDF Split a PDF by page range, every N pages, or one per page
Compress PDF Shrink a PDF — lossless restructure or strong image compression
Watermark PDF Add a text or image watermark — opacity, rotation, tiling, page targeting
PDF Metadata Remover Strip title, author, dates, XMP, and producer fingerprints from a PDF
Images to PDF Convert JPG, PNG, and WebP images into a single PDF with reorder and fit options

Text & Code

Tool Description
Cron Parser Parse and explain cron expressions in plain English
Diff Checker Compare two texts and highlight the differences
Case Converter Convert text between camelCase, snake_case, and more
Markdown Preview Render Markdown to clean HTML, live, in your browser
Markdown to PDF Convert Markdown into a print-ready PDF — choose page size, margins, and fonts
Lorem Ipsum Generate placeholder text for designs and mockups

Media & Assets

Tool Description
Background Remover Remove image backgrounds with a local AI model — transparent PNG cutouts
Image Upscaler Upscale images 2× or 4× with a local AI super-resolution model (ESRGAN)
Image Compressor Shrink images with real JPEG, WebP, AVIF, and PNG codecs without changing dimensions
Image Format Converter Convert PNG, JPG, WebP, GIF, BMP, and AVIF to PNG, JPG, or WebP
Image Resizer Resize and crop images directly in the browser
Image Cropper Crop and trim images with a visual drag box and aspect presets
Image Watermark Add a text or logo watermark — position grid, opacity, tiling, rotation
Add Text to Image Meme maker — add captions and text layers with a drag editor
Image Metadata Remover Remove EXIF, GPS, and camera metadata from images locally
HEIC Converter Convert iPhone HEIC/HEIF photos to JPG, PNG, or WebP (libheif)
PDF to Image Convert each PDF page to a PNG or JPEG — choose DPI, format, and pages
QR Generator Generate QR codes from text, URLs, or structured data
SVG Optimizer Optimize and minify SVG files while preserving visual quality
Favicon Generator Generate favicons in all required sizes from a single image
Lottie Previewer Preview and inspect Lottie animation files

Tech Stack

  • React 19 + React Router v7 (lazy-loaded routes)
  • Vite 6 (build & dev server) · Cloudflare Vite plugin + Wrangler (deploy)
  • Tailwind CSS 4 + shadcn/ui (utility-first styling + Radix primitives)
  • TypeScript (strict mode, noUncheckedIndexedAccess, verbatimModuleSyntax)
  • Biome (linting & formatting — replaces ESLint + Prettier)
  • Vitest + Testing Library + vitest-axe (jsdom environment)
  • Bun (package manager & script runner)

Tool-side heavy lifting:

  • onnxruntime-web — background removal (local ONNX segmentation model)
  • @tensorflow/tfjs + upscaler / @upscalerjs/esrgan-slim — AI super-resolution
  • libheif-js — HEIC/HEIF decoding (WASM)
  • @jsquash/* (jpeg, png, webp, avif, oxipng) — real image codecs in WASM
  • pdf-lib + pdfjs-dist — PDF manipulation and rendering
  • svgo, marked + dompurify, fflate (ZIP), lottie-web, qrcode, croner / cronstrue, js-yaml, diff
  • Fuse.js (fuzzy search) · react-helmet-async (SEO) · @dnd-kit (reordering) · sonner (toasts)

Getting Started

# Install dependencies (postinstall copies WASM/model assets into public/)
bun install

# Start dev server
bun run dev

# Production build (copy assets → Vite → OG image → icons → SEO shells)
bun run build

# Vite build only (still copies assets first)
bun run build:only

# Build + serve via Wrangler locally
bun run preview

# Lint & format check
bun run check

# Auto-fix lint/format issues
bunx --bun @biomejs/biome check --write ./src

# Deploy to Cloudflare
bun run deploy

Testing

bun run test          # watch mode
bun run test:run      # single run
bun run test:ui       # Vitest UI
bun run test:coverage # v8 coverage

Vitest runs in jsdom with @testing-library/react and @testing-library/jest-dom matchers. Test files live alongside source as **/__tests__/*.test.{ts,tsx}. The shared setup (src/test/setup.ts) installs jsdom polyfills required by Radix UI (ResizeObserver, PointerEvent, pointer-capture, scrollIntoView).

Notes:

  • Auto-cleanup is not enabled — component tests must call cleanup() in afterEach.
  • Tools using useUrlState (or any React Router hook) must wrap renders in <MemoryRouter>.

Adding a New Tool

Tools are auto-discovered at build time via import.meta.glob — no manual registration. Each tool lives in src/tools/<tool-slug>/ and exports two files.

1. tool.ts — named tool export of type ToolDefinition:

import type { ToolDefinition } from "../types";

export const tool: ToolDefinition = {
  name: "My Tool",
  slug: "my-tool",
  description: "What it does",
  seoDescription: "Longer description used for meta tags (optional)",
  category: "data",              // "data" | "text" | "media"
  tags: ["tag1", "tag2"],
  featured: false,
  icon: "Wrench",                // Lucide icon name — must also be mapped in src/lib/icons.ts
  route: () => import("./Route"),
  features: [                    // rendered as the three feature cards on the tool page
    { icon: "Zap", title: "Instant", description: "Runs locally in your browser." },
    { icon: "Code", title: "Customizable", description: "Configure to taste." },
    { icon: "Download", title: "Exportable", description: "Copy or download results." },
  ],
};

2. Route.tsx — default-exports the React component (lazy-loaded by the router):

import { ToolShell, TwoPane, PaneHeader, ErrorAlert } from "../../components/tool-layout";

export default function Route() {
  return (
    <ToolShell>
      <TwoPane>
        {/* tool UI */}
      </TwoPane>
    </ToolShell>
  );
}

Also remember to:

  • Add the Lucide icon import + mapping entry to src/lib/icons.ts (falls back to Braces otherwise).
  • Add a skeleton entry to src/tools/skeletonRegistry.tsx for the lazy-loading state.

The Route.tsx body must start directly with the tool's interactive UI inside a ToolShell. Page chrome — breadcrumbs, hero, feature cards, sibling tools — is centralized in src/pages/ToolPage.tsx and must not be duplicated per tool.

Architecture

src/
├── components/
│   ├── ui/                    # shadcn/ui primitives
│   ├── tool-layout/           # ToolShell, TwoPane, PaneHeader, ErrorAlert
│   ├── skeleton/              # Shared skeleton primitives + FeatureCardsSkeleton
│   ├── Layout.tsx             # Global <main>, header, footer
│   ├── SearchModal.tsx        # Cmd+K fuzzy search (cmdk + Fuse.js)
│   ├── FeatureCards.tsx       # Three feature cards on every tool page
│   ├── Breadcrumbs.tsx
│   ├── PageTransition.tsx
│   ├── ReportIssueButton.tsx
│   ├── Logo.tsx / IconSwap.tsx / KbdHint.tsx
│   ├── RootErrorBoundary.tsx
│   └── ToolErrorBoundary.tsx
├── hooks/
│   ├── useClipboard.ts        # async copy/read with auto-resetting `copied` state
│   ├── useToolPreferences.ts  # debounced localStorage settings per tool
│   ├── useUrlState.ts         # schema-based URL search-param sync
│   ├── useKeyboardShortcut.ts # declarative shortcut registration
│   └── useScrollReveal.ts
├── lib/
│   ├── icons.ts               # Static Lucide icon registry — add entries here
│   ├── image.ts / encode.ts   # Shared image + encoding helpers
│   ├── pdf.ts / pdfjs-render.ts / pdf-tools-client.ts
│   ├── markdown.ts            # marked + DOMPurify pipeline
│   ├── prefetch.ts            # Route prefetch on hover/intent
│   ├── errorReport.ts
│   └── utils.ts               # cn() helper
├── pages/
│   ├── Home.tsx / Tools.tsx / Privacy.tsx / NotFound.tsx
│   └── ToolPage.tsx           # Shared chrome for every tool route
├── seo/
│   ├── SEOHead.tsx            # title, description, canonical, OG, Twitter tags
│   ├── JsonLd.tsx             # JSON-LD structured data
│   ├── schemas.ts             # Organization, WebSite, SoftwareApplication, Breadcrumb, WebPage
│   └── constants.ts
├── tools/                     # Tool plugins — one directory per tool
│   ├── registry.ts            # Auto-discovers tools via import.meta.glob
│   ├── skeletonRegistry.tsx   # Per-tool skeleton loaders
│   ├── constants.ts
│   └── types.ts               # ToolDefinition interface
├── workers/
│   ├── pool.ts                # 2–4 thread pool, task queue, 30 s timeouts, cancellation
│   ├── worker.ts              # Generic pool worker
│   └── pdf-tools.worker.ts    # Dedicated PDF worker
├── wasm/
│   └── loader.ts              # Cached module loader with streaming compilation
├── test/setup.ts              # jsdom polyfills for Radix
├── App.tsx
├── router.tsx                 # Lazy-loaded routes with title metadata
├── main.tsx                   # Entry point — HelmetProvider, RouterProvider
├── config.ts                  # APP_NAME, APP_DESCRIPTION
└── index.css                  # Tailwind 4 + hex theme tokens (:root)

Key patterns

  • Plugin system — tools self-register by filesystem convention; the registry exposes getAllTools(), getToolBySlug(), getFeaturedTools(), getToolsByCategory().
  • Tool layout primitives — every Route.tsx composes ToolShell (+ optional TwoPane, PaneHeader, ErrorAlert). The wide variant (1900 px) is for tools needing a bigger work surface: diff-checker, jwt-decoder, background-remover, image-upscaler, image-compress, image-crop, image-caption, image-watermark.
  • Worker poolworkerPool singleton in src/workers runs CPU-heavy tasks off the main thread with queue, cancellation, and a 30 s default timeout. A wedged worker is terminated and replaced in place to avoid pool poisoning.
  • WASM loadercompileWasm / instantiateWasm from src/wasm stream + cache modules in memory with an ArrayBuffer fallback.
  • Theming — semantic CSS custom properties (hex) under :root in src/index.css. A dark variant is declared (@custom-variant dark) but there is no .dark palette or in-app toggle yet — the app ships light-only.
  • Vendor splittingvendor-react, vendor-router, vendor-fuse, vendor-lottie, vendor-radix-core (eager first-paint graph), vendor-radix-tools (lazy tool routes only), vendor-cmdk, vendor-marked, vendor-dompurify, vendor-pdf, vendor-pdfjs, vendor-dnd, vendor-jsquash. ONNX Runtime is deliberately not seeded — only the background-remover worker imports it.
  • Path alias@/*./src/* (used by shadcn components; tool Route.tsx files use relative imports).

Build pipeline

Asset copy steps run on postinstall and before every build, staging WASM binaries and AI models into public/:

  • copy-pdfjs-assets.ts — pdf.js worker + cmaps
  • copy-ort-assets.ts — ONNX Runtime WASM
  • copy-upscaler-models.ts — ESRGAN model weights
  • copy-libheif-assets.ts — libheif WASM
  • copy-tfjs-wasm-assets.ts — TensorFlow.js WASM backend

Post-Vite steps:

  • generate-og-image.ts — Satori + Resvg, produces OG images
  • generate-icons.ts — converts public/favicon.svg into 192 px / 512 px PNGs
  • generate-seo-shells.ts — writes a static prerendered HTML shell per route (/, /tools, /privacy, /tools/<slug>) with route-specific head tags, JSON-LD, and above-the-fold content; React clobber-renders over it at runtime
  • prerender.ts — optional Puppeteer route prerendering (not in the default build chain)

A custom Vite plugin in vite.config.ts also generates sitemap.xml from the discovered tool slugs.

Deployment

Deployed on Cloudflare Workers Assets via Wrangler (bun run deploy).

  • wrangler.jsoncnot_found_handling: "single-page-application" provides the SPA fallback (no _redirects file needed; Cloudflare flags it as an infinite loop).
  • public/_headersX-Frame-Options: DENY, X-Content-Type-Options: nosniff, strict referrer policy, permissions policy, and long-lived cache headers for /assets/* and /fonts/*.

Project Docs

  • CLAUDE.md — conventions and architecture notes for contributors and Claude Code
  • PRODUCT.md — strategic context: users, purpose, brand personality, anti-references, design principles
  • DESIGN.md — visual design system: tokens, typography, elevation, components, do's and don'ts

License

MIT — see LICENSE for details.

About

Utilbench — browser-based utility tools SPA. Every tool runs fully client-side. Zero backend, zero uploads, zero tracking — files never leave device. Fast, offline-capable, privacy-first.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages