---
title: "React's Best Parts in 5KB: Preact + HTM, No Build Tools Needed"
description: "Modern React development means webpack, babel, and megabytes of dependencies. Preact + HTM gives you the same DX in 5KB from a CDN. No build step needed."
date: 2025-11-25
slug: "react-best-parts-preact-htm-5kb"
tags: ["javascript", "preact", "react", "frontend", "performance", "no-build"]
---
I want to talk about React fatigue. Not the framework itself, but the ecosystem around it.
Modern React development means webpack or Vite, babel configuration, thousands of npm dependencies, and build steps that turn 50 lines of code into minutes of compilation. For a simple admin interface or internal tool, this is overkill.
There's a better way. Preact (3KB) + HTM (1KB) gives you React's component model and JSX-like syntax in 5KB total. Loaded from a CDN. No build step, no transpilation, no configuration.
Edit your code, refresh the browser. That's it.
## The React Build Problem
Don't get me wrong, React is great. The component model, hooks, and ecosystem are excellent. But somewhere along the way, using React became synonymous with complex build tooling.
Want to use JSX? You need babel. Want to split code? You need webpack or Vite. Want to use modern JavaScript? You need transpilation. By the time you're done, you have:
- 200MB+ in node_modules
- A few minutes for initial install
- 5-15 second dev server startup
- Hot reload that sometimes works
- Build configuration that breaks mysteriously
For a production app serving millions of users, this complexity might be justified. But for an internal admin panel or a side project? You're spending more time fighting tooling than building features.
## Preact + HTM: The Lightweight Alternative
Preact is a 3KB React alternative with the same API. HTM is a 1KB library that gives you JSX-like syntax using tagged template literals.
Together, they're 4KB. Compare that to React (45KB) + ReactDOM (130KB) = 175KB. That's a 40x difference.
The killer feature: both load directly from a CDN. No build step needed.
## Show Me
Here's a complete component from my newsletter digester project:
```javascript
import { h } from "https://esm.sh/preact@10.19.3";
import htm from "https://esm.sh/htm@3.1.1";
const html = htm.bind(h);
export default function Button({
children,
onClick,
variant = "primary",
disabled = false,
}) {
const baseClass = "px-4 py-2 rounded-md font-medium transition-colors";
const variantClasses = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300",
danger: "bg-red-600 text-white hover:bg-red-700",
};
const classes = `${baseClass} ${variantClasses[variant]}`;
return html`
`;
}
```
This is real production code. No transpilation, no build step. Just JavaScript.
## HTM Syntax
HTM uses tagged template literals to give you JSX-like syntax without compilation:
**Basic elements:**
```javascript
html`
`)}`;
```
It feels exactly like JSX. The only difference is using `html` tagged templates instead of JSX syntax.
## State and Hooks
Preact supports the same hooks API as React:
```javascript
import { h } from "https://esm.sh/preact@10.19.3";
import { useState, useEffect } from "https://esm.sh/preact@10.19.3/hooks";
import htm from "https://esm.sh/htm@3.1.1";
const html = htm.bind(h);
export default function Posts() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/posts")
.then(r => r.json())
.then(data => {
setPosts(data);
setLoading(false);
});
}, []);
if (loading) {
return html`
Loading...
`;
}
return html`
${posts.map(
post => html`
${post.title}
${post.summary}
`
)}
`;
}
```
`useState`, `useEffect`, `useCallback`, `useMemo`, `useRef` - they all work exactly like React.
## A Complete App
Here's the entry point for my newsletter digester frontend:
```javascript
// index.html
Newsletter Digester
```
That's it. No webpack config, no babel setup, no package.json scripts. Just an HTML file that imports a JavaScript module.
## Poor Man's Router
The `App.js` component imports other components and handles routing with simple state:
```javascript
import { h } from "https://esm.sh/preact@10.19.3";
import { useState } from "https://esm.sh/preact@10.19.3/hooks";
import htm from "https://esm.sh/htm@3.1.1";
import Posts from "./Posts.js";
import Sites from "./Sites.js";
import Config from "./Config.js";
const html = htm.bind(h);
export default function App() {
const [currentTab, setCurrentTab] = useState("posts");
return html`
`;
}
```
This is a full single-page app with client-side routing (via simple state conditionals), state management, and multiple views. Total bundle size: 5KB (Preact + HTM). Everything else loads from the same CDN.
The hardest part is unlearning the assumption that frontend development requires build tools. Modern browsers support ES modules natively. The build tools are optional.
## Real Project: Newsletter Digester
Everything I described above is from my actual newsletter digester project. The entire frontend is Preact + HTM. No build step.
It has:
- Multiple pages (Posts, Sites, Config, Logs)
- Reusable components (Button, Input, Select, Modal)
- State management with hooks
- API calls with fetch
- Markdown rendering
- Form handling
Total frontend code: ~1500 lines of JavaScript. Total dependencies: 0 (everything from CDN). Build time: 0 seconds.
Check out the code: [github.com/mfyz/newsletter-blog-digester](https://github.com/mfyz/newsletter-blog-digester)
Look at the `src/public/` folder. Every file is just JavaScript. No transpilation, no bundling, no magic. Open any component and you'll see exactly what runs in the browser.
For internal tools, admin panels, prototypes, and side projects, you don't need build complexity. You can ship a polished, maintainable frontend in 5KB with zero configuration. Less time fighting tooling, more time building features. Code that's easier to understand because there's no magic between your source and what runs in the browser.