Skip to main content

The Smallest Bundle: Top npm Packages Under 5KB

·PkgPulse Team

TL;DR

Bundle size is a first-class concern in 2026, and the best libraries have internalized it. Day.js (2KB) replacing Moment.js (300KB), Zustand (2KB) replacing Redux (7KB+middleware), nanoid (1KB) replacing uuid (14KB) — the pattern is clear: modern JavaScript libraries are getting smaller, not bigger. Here are the best sub-5KB packages by category, and how to check bundle size before installing anything.

Key Takeaways

  • Day.js: 2KB — full date library, Moment.js-compatible API
  • Zustand: 2KB — complete state management, no provider needed
  • nanoid: 1KB — secure unique ID generation, replaces uuid (14KB)
  • clsx: 0.5KB — className utility that shadcn/ui standardized
  • Use bundlephobia.com — check size BEFORE installing, includes gzipped + minified

How to Check Bundle Size

# Before installing any package, check:
# 1. bundlephobia.com/package/package-name
# 2. Or CLI:
npx bundlephobia-cli package-name

# Key metrics:
# - Minified size: raw bytes after minification
# - Gzipped size: what your users actually download
# - Tree-shakeable: yes = you might only pay for what you import
# - Side effects: false = better tree-shaking

# Example check:
npx bundlephobia-cli moment        # 300.3kB minified (72.1kB gzipped)
npx bundlephobia-cli dayjs         # 6.6kB minified (2.7kB gzipped) ← same API
npx bundlephobia-cli date-fns      # 78.4kB minified (but tree-shakeable, <5KB for typical usage)

Category: Dates & Time

PackageGzippedNotes
Day.js2.7KBMoment.js-compatible API
date-fns (used functions)~2-8KBTree-shakeable, functional
Luxon23KBFull Intl support, larger
Moment.js72KBDo not use for new projects
// Day.js — full date library in 2KB
import dayjs from 'dayjs';
// Most Moment.js code works with s/moment/dayjs/
dayjs('2026-03-08').add(7, 'day').format('YYYY-MM-DD')
// Plugins: dayjs/plugin/relativeTime, dayjs/plugin/timezone

Category: Unique IDs

PackageGzippedNotes
nanoid1.1KBURL-safe, cryptographically secure
uuid v4 (just v4)~3KBTree-shakeable in v9+
cuid22KBMonotonic, sortable
Built-in crypto.randomUUID()0KBNode 18+, Web Crypto API
// nanoid — 1KB
import { nanoid } from 'nanoid';
const id = nanoid();          // 21 chars: V1StGXR8_Z5jdHi6B-myT
const shortId = nanoid(10);   // Custom length: V1StGXR8_Z

// Built-in (Node 18+ / modern browsers) — 0KB!
const id = crypto.randomUUID();  // Standard UUID v4 format
// Use this unless you need shorter IDs (nanoid)

Category: Utilities

PackageGzippedNotes
clsx0.5KBclassName builder (shadcn/ui standard)
cn (tailwind-merge + clsx)~3KBTailwind className merging
klona1.1KBDeep clone (faster than structuredClone for small objects)
mitt0.3KBTiny event emitter
ms0.5KBTime string to ms: ms('2 days')
bytes0.8KBBytes to human: bytes(1024)'1KB'
// clsx — 0.5KB className builder
import { clsx } from 'clsx';
clsx('foo', { bar: true, baz: false }, ['qux'])  // 'foo bar qux'

// With tailwind-merge (3KB together):
import { cn } from '@/lib/utils';  // shadcn/ui convention
cn('px-4 py-2', isLarge && 'px-8 py-4', className)

// mitt — 0.3KB event emitter
import mitt from 'mitt';
const emitter = mitt<{ userLogin: string; logout: void }>();
emitter.on('userLogin', (userId) => console.log(userId));
emitter.emit('userLogin', '123');

Category: State Management

PackageGzippedNotes
Zustand1.8KBComplete store, no boilerplate
Valtio2.5KBProxy-based, auto-subscription
Jotai (core)3.1KBAtomic model, TypeScript-first
nanostores1.1KBFramework-agnostic atoms
Nano Stores1.1KBBest for multi-framework monorepos
// Zustand — 1.8KB for complete state management
import { create } from 'zustand';

const useStore = create<{ count: number; increment: () => void }>((set) => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
}));

// That's it. No Provider. No boilerplate. 1.8KB.

Category: Validation

PackageGzippedNotes
Valibot (tree-shaken)~1-3KBModular, pay per rule
Zod (tree-shaken)~4-6KBBetter DX, more ecosystem support
TypeBox~3KBJSON Schema + TypeScript
ArkType~5KBTypeScript syntax validation
// Valibot — modular, only pay for what you import
import { object, string, email, minLength, parse } from 'valibot';
const schema = object({ email: string([email()]), name: string([minLength(2)]) });
// This imports: ~1.5KB (just these rules)
// vs importing full library: ~2.8KB

Category: HTTP Clients

PackageGzippedNotes
Built-in fetch0KBNode 18+, modern browsers
ky2.5KBfetch-based, retries, timeout
wretch3.2KBfetch wrapper, middleware pattern
ofetch3.8KBUsed by Nuxt, H3
// ky — 2.5KB with retries and timeout
import ky from 'ky';
const data = await ky.get('https://api.example.com/users', {
  timeout: 5000,
  retry: 3,
}).json();

// For most use cases, built-in fetch + a tiny wrapper is enough:
async function get<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`${res.status} ${url}`);
  return res.json();
}
// 0KB

Category: React Utilities

PackageGzippedNotes
react-hot-toast3.5KBToast notifications
sonner3.2KBMore polished toasts
use-debounce0.9KBDebounce hook
react-usevariesHook library (import individually)
// sonner — 3.2KB toast library
import { Toaster, toast } from 'sonner';
// In layout: <Toaster />
toast.success('Saved!');
toast.error('Failed to save');
toast.promise(saveUser(), {
  loading: 'Saving...',
  success: 'Saved!',
  error: 'Failed',
});

The Zero-Dependency Champions

# Packages with zero runtime dependencies (lowest supply chain risk):
# These are the safest to install:

# nanoid: 0 deps
# clsx: 0 deps
# mitt: 0 deps
# ms: 0 deps
# dayjs: 0 deps
# zustand: 0 deps (React is peer dep only)
# valtio: 0 deps (React is peer dep only)
# klona: 0 deps

# Check dependency count:
npm view package-name dependencies
# If empty/undefined: zero runtime dependencies ✅

Replacing Bloated Dependencies

# Common swaps that cut bundle size significantly:

# Date handling:
# moment (72KB) → dayjs (2.7KB) or date-fns (tree-shaken)

# ID generation:
# uuid full (14KB) → nanoid (1.1KB) or crypto.randomUUID()

# State:
# redux + react-redux + redux-toolkit (10KB+) → zustand (2KB) for simple cases

# Validation:
# yup (30KB) → zod (14KB) → valibot (tree-shaken ~2KB)

# Classnames:
# classnames (1KB but older) → clsx (0.5KB, same API)

# Event emitter:
# eventemitter3 (2KB) → mitt (0.3KB)

# Deep clone:
# lodash.clonedeep (5KB) → structuredClone (built-in) or klona (1.1KB)

# HTTP:
# axios (11KB) → ky (2.5KB) or native fetch (0KB)

Compare bundle sizes and package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.