Preact Size Optimization — Minimizing 3kB Bundle Further
Learn how to optimize Preact bundle size: tree shaking, code splitting, Lazy Loading, and measuring bundle impact to keep your application under 10kB.
In this lesson, you'll understand how to analyze bundle size, eliminate unused code, and apply techniques that keep Preact applications minimal.
What You'll Learn
How to analyze bundle size with Vite, apply tree shaking, use code splitting with lazy loading, avoid common bloat patterns, and measure optimization impact.
Why It Matters
Preact's main advantage is its tiny size. A poorly optimized bundle can balloon to 100kB+ with unused libraries, defeating the purpose of choosing Preact over React.
Real-World Use
Doda Browser's extension loads under 2 seconds on 3G networks because the team optimized every kilobyte: tree-shook lodash, lazy-loaded the settings panel, and avoided React-compat libraries for simple components.
flowchart LR
A[Raw Bundle] --> B[Tree Shaking]
B --> C[Code Splitting]
C --> D[Lazy Loading]
D --> E[Optimized Bundle]
E --> F[<10kB Total]
style A fill:#673ab8,color:#fff
style F fill:#4a148c,color:#fff
Analyzing Bundle Size
Use Vite's bundle analyzer to see what's in your output:
npm install rollup-plugin-visualizer --save-dev
// vite.config.js
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
preact(),
visualizer({ open: true }) // Opens analysis in browser
],
build: {
rollupOptions: {
output: {
manualChunks: undefined
}
}
}
});
Output: After running npm run build, a treemap opens in your browser showing each file's size. Large blocks indicate optimization opportunities.
Tree Shaking
Eliminate unused exports from your code and dependencies:
// BAD — imports entire lodash library
import _ from 'lodash';
_.debounce(fn, 300);
// GOOD — imports only the debounce function
import debounce from 'lodash/debounce';
debounce(fn, 300);
// BEST — no library needed for simple debounce
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
Output: The lodash approach adds 70kB. The per-function import adds 2kB. The custom implementation adds 0.1kB.
Code Splitting with Lazy Loading
Split your application into chunks loaded on demand:
import { lazy, Suspense } from 'preact';
// These are loaded only when needed
const Dashboard = lazy(() => import('./routes/Dashboard'));
const Settings = lazy(() => import('./routes/Settings'));
const AdminPanel = lazy(() => import('./routes/AdminPanel'));
function App() {
return (
<div>
<nav>
<Link href="/">Home</Link>
<Link href="/dashboard">Dashboard</Link>
<Link href="/settings">Settings</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Router>
<Home path="/" />
<Dashboard path="/dashboard" />
<Settings path="/settings" />
</Router>
</Suspense>
</div>
);
}
Output: The initial bundle contains only Home and the shell. Dashboard and Settings are separate chunks loaded when the user navigates to those routes.
Avoiding Common Bloat
// BAD — importing preact/compat for everything
import React from 'react'; // 5kB compat layer
import { useState } from 'react'; // Could use preact/hooks directly
// GOOD — using Preact directly for core functionality
import { render } from 'preact';
import { useState } from 'preact/hooks';
import { signal } from '@preact/signals'; // 1.2kB
// BAD — bundling an entire icon library
import { Camera, Heart, Star } from 'react-feather'; // 30kB+
// GOOD — inline SVGs or a minimal icon component
function IconCamera() {
return (
<svg viewBox="0 0 24 24" width={16} height={16}>
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
<circle cx="12" cy="13" r="4"/>
</svg>
);
}
Output: Avoiding preact/compat for core functionality saves 2kB. Inline SVG icons add ~0.5kB each vs 30kB for an icon library with three icons.
Size Budgets in CI
Set a size budget to prevent bundle bloat:
// vite.config.js — enforce size budget
export default defineConfig({
build: {
rollupOptions: {
output: {
// Warn if any chunk exceeds 20kB
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]'
}
},
// Report gzipped sizes
reportCompressedSize: true
}
});
In your CI pipeline, add a script that fails if total bundle exceeds a threshold:
npm run build
du -sh dist/assets/*.js
# Fail if any chunk > 20kB or total > 50kB
Common Mistakes
- Not running production builds for measurement: Development bundles include source maps and debug code. Always measure
npm run buildoutput. - Importing entire libraries for one function: Instead of
import { debounce } from 'lodash', use per-function packages or write a 10-line custom version. - Using preact/compat when native Preact works: preact/compat adds 2kB. Only use it when you need specific React libraries.
- Bundling large JSON data files: Large JSON files add to the bundle. Fetch them at runtime with
fetch()instead. - Not code-splitting routes: A single bundle with all routes loads code the user may never visit. Split by route for faster initial load.
Practice Questions
What tool visualizes bundle composition? Answer:
rollup-plugin-visualizer. It generates a treemap showing the size contribution of each module.How do you lazy-load a component in Preact? Answer: Use
lazy(() => import('./Component'))frompreactand wrap it in<Suspense>with a fallback.What is tree shaking? Answer: A build step that removes unused exports from JavaScript bundles. It relies on ES module static structure.
How much does preact/compat add to bundle size? Answer: Approximately 2kB (gzipped). Preact core is 3kB, so compat brings the total to ~5kB.
Challenge
Build a Preact app with three routes, each importing different dependencies. Analyze the bundle, then optimize by: splitting routes with lazy loading, removing unused dependencies, and replacing large libraries with custom code. Measure before and after.
Mini Project
Take an existing Preact project, run the bundle analyzer, identify the top 3 largest dependencies, and replace each with a lighter alternative or custom implementation. Document the size savings for each change.
FAQ
What's Next
Learn about Preact Testing to write unit and integration tests for Preact components.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro