Preact Complete Guide | Fast 3KB React Alternative
이 글의 핵심
Preact is a fast 3KB alternative to React with the same modern API. It's perfect for performance-critical applications and works with most React libraries.
Introduction
Preact is a fast 3KB alternative to React with the same modern API. It’s not a reimplementation of React, but a fresh take on the Virtual DOM with a focus on performance and size.
Created by Jason Miller (Google Chrome team member, creator of wmr and many performance tools), Preact represents what React would look like if it prioritized size and speed above all else.
Why Preact Matters
The performance impact of bundle size:
- Preact: 3KB gzipped (13x smaller than React)
- React: 40KB gzipped (not including React DOM)
- On 3G networks: 200ms faster initial load with Preact
Real-world adoption:
- Uber.com ??migrated from React to Preact, improved TTI by 50%
- Etsy ??uses Preact for embedded widgets on seller pages
- Housing.com ??switched to Preact, reduced JS bundle from 200KB to 100KB
- ~7 million weekly npm downloads (widely used for performance-critical sites)
Production use cases where Preact excels:
- Embedded widgets ??third-party widgets that load on other sites (ads, chat, analytics)
- E-commerce product pages ??every 100ms load time = 1% conversion increase
- Progressive Web Apps ??offline-first apps where bundle size matters
- Mobile-first sites ??emerging markets with slow networks
When to choose Preact:
- Performance is critical (e-commerce, mobile-first)
- Building embeddable widgets for third-party sites
- Bundle size matters (need <5KB base framework)
- Already using React patterns, want instant performance win
When to use React instead:
- Need React Native for mobile apps
- Using many React-specific libraries (check compatibility)
- Team is React-expert and performance is “good enough”
- Corporate requirement (React has Facebook backing)
React vs Preact
React (40KB):
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Preact (3KB):
import { h } from 'preact';
import { useState } from 'preact/hooks';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Almost identical!
1. Installation
New Project with Vite
npm create vite@latest my-app -- --template preact-ts
cd my-app
npm install
npm run dev
Add to Existing Project
npm install preact
2. Core Concepts
JSX and h()
import { h } from 'preact';
// JSX (recommended)
const element = <div>Hello</div>;
// h() function (JSX compiles to this)
const element = h('div', null, 'Hello');
Components
import { h } from 'preact';
// Function component
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// With TypeScript
interface GreetingProps {
name: string;
}
function Greeting({ name }: GreetingProps) {
return <h1>Hello, {name}!</h1>;
}
Props and Children
function Card({ title, children }) {
return (
<div class="card">
<h2>{title}</h2>
<div>{children}</div>
</div>
);
}
// Usage
<Card title="My Card">
<p>Card content</p>
</Card>
3. Hooks
useState
import { useState } from 'preact/hooks';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(c => c + 1)}>Increment (updater)</button>
</div>
);
}
useEffect
import { useEffect } from 'preact/hooks';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]); // Re-run when userId changes
return <div>{user?.name}</div>;
}
useMemo
import { useMemo } from 'preact/hooks';
function ExpensiveList({ items, filter }) {
const filtered = useMemo(() => {
console.log('Filtering...');
return items.filter(item => item.includes(filter));
}, [items, filter]);
return (
<ul>
{filtered.map(item => <li key={item}>{item}</li>)}
</ul>
);
}
useCallback
import { useCallback } from 'preact/hooks';
function TodoList({ todos }) {
const [filter, setFilter] = useState('');
const handleFilter = useCallback((e) => {
setFilter(e.target.value);
}, []);
return (
<div>
<input onInput={handleFilter} />
{/* TodoItem won't re-render if handleFilter doesn't change */}
</div>
);
}
useRef
import { useRef } from 'preact/hooks';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>
</div>
);
}
4. React Compatibility
Using preact/compat
npm install preact
vite.config.ts:
import { defineConfig } from 'vite';
import preact from '@preact/preset-vite';
export default defineConfig({
plugins: [preact()],
resolve: {
alias: {
react: 'preact/compat',
'react-dom': 'preact/compat',
},
},
});
Now you can use React libraries!
// Works with Preact via compat
import { useState } from 'react';
import { createPortal } from 'react-dom';
5. Signals (Preact’s Secret Weapon)
npm install @preact/signals
import { signal, computed } from '@preact/signals';
const count = signal(0);
const doubled = computed(() => count.value * 2);
function Counter() {
// No useState needed! Signals auto-update
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubled}</p>
<button onClick={() => count.value++}>Increment</button>
</div>
);
}
Why Signals?
- No re-renders needed
- Automatically optimized
- Global state without context
6. Routing
npm install preact-router
import { Router, Route } from 'preact-router';
function App() {
return (
<Router>
<Home path="/" />
<Profile path="/profile/:user" />
<NotFound default />
</Router>
);
}
function Profile({ user }) {
return <h1>Profile: {user}</h1>;
}
7. Server-Side Rendering
import { render } from 'preact-render-to-string';
const html = render(<App />);
console.log(html); // <div>...</div>
8. Performance Tips
1. Use Signals for Global State
// Global signal (no context needed)
import { signal } from '@preact/signals';
export const user = signal(null);
// Use anywhere
function Header() {
return <div>Welcome, {user.value?.name}</div>;
}
2. Memoize Components
import { memo } from 'preact/compat';
const ExpensiveComponent = memo(({ data }) => {
// Only re-renders when data changes
return <div>{data}</div>;
});
3. Lazy Load Components
import { lazy, Suspense } from 'preact/compat';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
9. Differences from React
class vs className
// Preact: use "class" (HTML standard)
<div class="container">Hello</div>
// React: use "className"
<div className="container">Hello</div>
// Both work in Preact!
Event Naming
// Preact: lowercase events work
<input onchange={handleChange} />
// React: camelCase only
<input onChange={handleChange} />
// Both work in Preact!
No Synthetic Events
Preact uses native browser events (faster!):
function handleClick(e) {
// e is a native Event, not SyntheticEvent
console.log(e.target);
}
10. Real-World Example
Todo App with Signals
import { signal, computed } from '@preact/signals';
const todos = signal([
{ id: 1, text: 'Learn Preact', done: false },
]);
const filter = signal('all');
const filteredTodos = computed(() => {
if (filter.value === 'all') return todos.value;
return todos.value.filter(t =>
filter.value === 'done' ? t.done : !t.done
);
});
function TodoApp() {
const addTodo = (text) => {
todos.value = [...todos.value, {
id: Date.now(),
text,
done: false,
}];
};
const toggleTodo = (id) => {
todos.value = todos.value.map(t =>
t.id === id ? { ...t, done: !t.done } : t
);
};
return (
<div>
<input onKeyUp={(e) => {
if (e.key === 'Enter') {
addTodo(e.target.value);
e.target.value = '';
}
}} />
<div>
<button onClick={() => filter.value = 'all'}>All</button>
<button onClick={() => filter.value = 'active'}>Active</button>
<button onClick={() => filter.value = 'done'}>Done</button>
</div>
<ul>
{filteredTodos.value.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</li>
))}
</ul>
</div>
);
}
11. Bundle Size Comparison
| Library | Size (gzipped) | Load Time (3G) |
|---|---|---|
| Preact | 3KB | 60ms |
| Vue | 34KB | 680ms |
| React | 40KB | 800ms |
Summary
Preact is the perfect React alternative for performance:
- 3KB vs React’s 40KB (13x smaller)
- Same API - React knowledge transfers
- Signals for ultra-fast state
- Compatible with most React libraries
- Faster rendering and smaller bundles
Key Takeaways:
- Almost identical API to React
- Use
preact/compatfor React libraries - Signals eliminate re-renders
- Use
classinstead ofclassName - Perfect for performance-critical apps
Next Steps:
- Learn [React 18](/en/blog/react-18-deep-dive/
- Try [Solid.js](/en/blog/solid-js-complete-guide/
- Build with [Vite](/en/blog/vite-complete-guide/
Resources:
?�주 묻는 질문 (FAQ)
Q. ???�용???�무?�서 ?�제 ?�나??
A. Complete Preact guide for building fast web apps. Learn the lightweight React alternative with the same API, hooks, and ???�무?�서????본문???�제?� ?�택 가?�드�?참고???�용?�면 ?�니??
Q. ?�행?�로 ?�으�?좋�? 글?�?
A. �?글 ?�단???�전 글 ?�는 관??글 링크�??�라가�??�서?��?배울 ???�습?�다. C++ ?�리�?목차?�서 ?�체 ?�름???�인?????�습?�다.
Q. ??깊이 공�??�려�?
A. cppreference?� ?�당 ?�이브러�?공식 문서�?참고?�세?? 글 말�???참고 ?�료 링크???�용?�면 좋습?�다.
같이 보면 좋�? 글 (?��? 링크)
??주제?� ?�결?�는 ?�른 글?�니??
- [Lit Complete Guide | Fast Web Components Library](/en/blog/lit-complete-guide/
- [Zustand Complete Guide | Minimal React State](/en/blog/zustand-complete-guide/
- [Alpine.js Complete Guide | Lightweight JavaScript Framework](/en/blog/alpine-js-complete-guide/
??글?�서 ?�루???�워??(관??검?�어)
Preact, React, JavaScript, Performance, Frontend, Lightweight ?�으�?검?�하?�면 ??글???��????�니??