Preact in context of Astro and also in general
1. What Preact is (one sentence)
Preact is a lightweight, React-compatible UI library that implements the React API with a much smaller runtime.
Think:
React ≈ feature-rich, heavy
Preact ≈ minimal, fast, React-shaped
2. Why Preact matters specifically in Astro
Astro’s core philosophy is:
Ship zero JavaScript by default. Hydrate only where needed.
Preact fits this philosophy extremely well because:
Tiny runtime (~3–4 KB gzipped)
Fast hydration
Same mental model as React
Excellent for islands architecture
Astro uses Preact as:
A first-class UI framework
A lighter alternative to React for interactive components
3. Astro + Preact architecture (important)
Astro itself is not a SPA framework.
Instead:
Astro renders HTML at build time
Interactive UI pieces are islands
Each island can be:
Preact
React
Vue
Svelte
Solid, etc.
Preact is often the best default choice when:
You want React-like syntax
You want minimal client JS
4. How Astro uses Preact internally
When you use Preact in Astro:
Astro renders your Preact component to static HTML
JavaScript is sent to the browser only if you opt-in
Hydration happens based on a directive like:
<MyComponent client:load />
or
<MyComponent client:visible />
No hydration directive → no JS shipped
This is very different from:
CRA
Next.js
Vite React SPA
5. Installing and enabling Preact in Astro
Astro treats Preact as an integration.
npx astro add preact
This:
Installs
preactInstalls
@astrojs/preactUpdates
astro.config.mjs
After this, you can write:
// Counter.jsx
import { useState } from 'preact/hooks';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
And use it in Astro:
---
import Counter from '../components/Counter.jsx';
---
<Counter client:load />
6. Preact vs React in Astro (this is the key comparison)
| Aspect | Preact | React |
|---|---|---|
| Bundle size | Very small | Large |
| React compatibility | ~95% | 100% |
| Hooks | Yes | Yes |
| JSX | Yes | Yes |
| Ecosystem | Smaller | Massive |
| Astro hydration cost | Lower | Higher |
| Default recommendation | ✅ Often | ⚠️ Only if needed |
Astro team’s implicit guidance:
Use Preact unless you need a React-only library
7. Compatibility details you should actually know
What just works
JSX
useState,useEffect,useRef,useMemoFunction components
Controlled inputs
Common incompatibilities
Some React libraries that rely on:
React internals
Concurrent features
createRoot
Heavy UI frameworks (some work, some don’t)
Preact offers a compatibility layer:
import 'preact/compat';
Astro enables this automatically when needed.
8. Why Astro does NOT default to React
This is a design philosophy difference.
Astro wants:
Static HTML first
Minimal JS
Fast TTFB and LCP
React wants:
Client runtime
Reconciliation everywhere
Preact bridges this gap:
React-like authoring
Near-zero runtime cost
9. When Preact is the wrong choice in Astro
Do not choose Preact if:
You need a React-only UI framework (e.g., some Material UI features)
You rely on advanced React tooling or devtools
You plan to share code with a Next.js app unchanged
In those cases, use React directly in Astro.
10. How Preact fits into Astro’s “islands” model
Conceptually:
Astro Page (HTML)
├── Header (static)
├── Article (static)
├── Counter (Preact island)
├── Comments (Preact island)
└── Footer (static)
Only the Counter and Comments ship JavaScript.
That is where Preact shines.
11. Big picture takeaway
In Astro, Preact is not “React lite” — it is the optimal interactive layer.
Astro = HTML orchestrator
Preact = lightweight interactivity engine
React = optional heavy machinery
One-line summary
Preact in Astro gives you React-style components with a fraction of the JavaScript cost, making it the default “islands” framework for performance-first Astro sites.
If you want, next I can:
Compare Preact vs Solid in Astro
Show real bundle size numbers
Explain hydration directives in depth
Map this to your Blogger → Astro migration thinking
Just tell me.
Me: I guess one will have to decide when to use plain Javascript component and when to use Preact component in typical Astro projects.
CG: Yes — and this is exactly the right way to think about Astro.
In Astro, choosing between plain JavaScript and Preact is a design decision, not a default habit.
Below is a practical decision framework you can actually use on real Astro projects.
1. Core Astro rule (anchor this mentally)
If something can be done without a framework, do it without a framework.
Astro is deliberately optimized for this.
Preact is there only when the complexity justifies it.
2. What “plain JavaScript component” really means in Astro
This usually means:
Static HTML generated by Astro
A small
<script>block for behaviorNo hydration
No virtual DOM
No component runtime
Example:
<button id="toggle">Toggle</button>
<script>
document.getElementById('toggle')
.addEventListener('click', () => {
document.body.classList.toggle('dark');
});
</script>
This is:
Zero framework
Zero hydration
Extremely fast
Often the best solution
3. When plain JavaScript is the correct choice
Use plain JS when:
✅ Interaction is trivial
Toggle
Show / hide
CSS class switch
Simple event handling
✅ No shared state
No data flowing between components
No derived state
✅ DOM structure is stable
You are not re-rendering lists
You are not conditionally building complex trees
✅ Logic is short-lived
10–30 lines of JS
One script per page or feature
Rule of thumb
If you don’t need state management, don’t import a framework.
4. When plain JavaScript starts to break down
Plain JS becomes painful when:
You start manually syncing DOM + state
You write
querySelectoreverywhereYou add multiple listeners and flags
You reimplement state machines accidentally
This is the danger zone.
5. When Preact becomes the correct choice
Use Preact when any of the following appear:
✅ Stateful UI
Counters
Forms with validation
Filters
Tabs
Accordions with state
✅ Dynamic rendering
Lists based on data
Conditional UI
Derived UI from props/state
✅ Reusability matters
Same component used on multiple pages
Clean inputs / outputs
✅ Complexity is growing
Logic > ~50 lines
Multiple UI states
Side effects (
useEffect)
This is where Preact reduces complexity instead of adding it.
6. A simple decision table (very practical)
| Requirement | Plain JS | Preact |
|---|---|---|
| Static content | ✅ | ❌ |
| Simple DOM toggles | ✅ | ❌ |
| Local state | ❌ | ✅ |
| Conditional rendering | ❌ | ✅ |
| Dynamic lists | ❌ | ✅ |
| Reusable UI | ❌ | ✅ |
| Complex forms | ❌ | ✅ |
| Minimal JS payload | ✅ | ⚠️ |
7. Astro’s killer feature that enables this choice
You are not locked in.
One page can have:
90% static HTML
5% plain JS
5% Preact islands
Astro encourages mixing styles.
That is by design.
8. Anti-pattern to avoid (important)
❌ Using Preact by default just because you know React
This leads to:
Unnecessary hydration
Larger JS bundles
Losing Astro’s advantage
Astro is not “React with better SEO”.
9. A good mental hierarchy (recommended)
Think in this order:
Can this be pure HTML + CSS?
Can this be HTML + tiny JS?
Does this need state? → Preact
Does this need ecosystem libraries? → React
Most Astro pages stop at step 2.
10. Realistic Astro example
Blog site:
Article pages → pure Astro
Dark mode toggle → plain JS
Search box → Preact
Comments widget → Preact
Header/footer → static
That’s a textbook Astro architecture.
Final takeaway (strong opinion)
In Astro, Preact is a scalpel, not a hammer.
Use plain JavaScript by default.
Introduce Preact only when state and reactivity earn their keep.
If you want, next I can:
Show before/after refactors (JS → Preact)
Give real bundle size comparisons
Map this to your static education site plans
Just say the word.
Me: The astro docs tutorial has a Greeting Preact component - https://docs.astro.build/en/tutorial/6-islands/1/
It takes in an array of messages and shows one of them. It has a button for changing the message to a random one. The current message is stored in a usestate (state) variable. AFter you have noted this, I will ask my question.
CG: Noted. ✅
You are referring to the Astro Islands tutorial example where:
A Preact
Greetingcomponent is usedIt receives an array of messages as a prop
One message is displayed at a time
A button selects a random message
The currently displayed message is stored in a
useStatestate variableClicking the button updates state → triggers re-render of that island
So this example demonstrates:
Client-side state
Interactivity
Why an island needs a framework (Preact) rather than plain JS
I’m aligned with the setup.
Go ahead—what’s your question?
Me: I was curious to know how such a component could also be implemented in plain Javascript (which is what Preact or React probably do under the hood).
CG: This is an excellent curiosity—and it goes straight to the heart of why frameworks exist.
I’ll answer this concretely, not philosophically:
we’ll build the same Greeting component behavior in plain JavaScript, then I’ll explain what Preact/React add on top and what pain they remove.
1. What the Preact component is doing (mechanically)
Stripped of JSX and hooks, the Greeting component does only this:
Store a current message
Render that message into the DOM
On button click:
Pick a random message
Update the stored value
Update the DOM
That’s it.
No magic.
2. Plain JavaScript implementation (Astro-friendly)
Here is a clean, minimal, framework-free equivalent that you can drop into an .astro file.
Astro component
---
const messages = [
"Hello!",
"Howdy!",
"Greetings!",
"Hi there!"
];
---
<div id="greeting">
<p id="message"></p>
<button id="change">New Greeting</button>
</div>
<script>
const messages = [
"Hello!",
"Howdy!",
"Greetings!",
"Hi there!"
];
let currentMessage = messages[0];
const messageEl = document.getElementById('message');
const buttonEl = document.getElementById('change');
function render() {
messageEl.textContent = currentMessage;
}
buttonEl.addEventListener('click', () => {
const randomIndex = Math.floor(Math.random() * messages.length);
currentMessage = messages[randomIndex];
render();
});
render();
</script>
3. What this code corresponds to in Preact terms
| Preact concept | Plain JS equivalent |
|---|---|
useState | let currentMessage |
| JSX render | render() function |
| Re-render on state change | Manual render() call |
| Event handler | addEventListener |
| Component | DOM + closure |
This is literally what’s happening under the hood.
4. Why this works well for this example
This example is:
Single piece of state
Single DOM update
No derived UI
No reuse
So plain JS is:
Simple
Readable
Zero framework cost
Possibly better than Preact here
Astro tutorial uses Preact for teaching, not necessity.
5. Where plain JS starts to hurt (and frameworks earn their keep)
Now imagine adding:
Multiple UI states
Conditional rendering
Derived values
Props coming from Astro
Reuse across pages
Cleanup logic
Your plain JS grows into this:
function render() {
if (state.mode === 'A') {
// update DOM A
} else {
// update DOM B
}
if (state.loading) {
// toggle spinner
}
if (state.error) {
// show error
}
}
At this point:
You’re manually diffing UI
You’re writing your own state machine
Bugs creep in easily
Frameworks prevent that.
6. What Preact adds beyond plain JS
Preact gives you:
Declarative rendering
Describe what UI should look like
Automatic re-rendering
No manual
render()calls
DOM diffing
Only changed nodes are updated
Component isolation
Safer reuse
Predictable state lifecycle
All of this is paid for with a small JS runtime.
7. Why Astro tutorial chose Preact here
Two reasons:
To introduce islands + hydration
To show stateful interactivity
Not because plain JS couldn’t do it.
This is a teaching example, not a performance recommendation.
8. Honest verdict (strong opinion)
For this exact Greeting example:
Plain JavaScript is perfectly fine and arguably more honest.
Preact becomes worthwhile only when complexity compounds.
Astro gives you the freedom to choose—and expects you to.
Final takeaway
Preact/React do not enable anything magical here.
They automate state tracking, rendering, and DOM updates that you can absolutely write by hand—but shouldn’t once complexity grows.
If you want, next I can:
Show a slightly more complex version where plain JS becomes messy
Rewrite this using
data-*attributesExplain how React’s diffing compares to manual updates
Show a hybrid Astro pattern (static + tiny JS + Preact island)
Just tell me what angle you want next.
Me: Is Preact JS bundle overhead a fixed one?
CG: Short answer: Yes — mostly, but with important nuances.
Let’s be precise and Astro-specific.
1. Is there a fixed Preact overhead?
Yes.
The moment you use one hydrated Preact island, you pay a baseline runtime cost.
That cost includes:
Preact core runtime
Hooks implementation
Astro’s Preact renderer glue
Hydration logic
This is largely fixed, regardless of:
How small your component is
How many
useStates you useHow trivial the UI is
In practice, this is roughly:
~3–4 KB gzipped (Preact + hooks)
a little Astro integration code
(Exact numbers vary by build, but the shape is fixed.)
2. What parts are not fixed
On top of that baseline, you add variable cost:
Variable additions
Your component code
Any helper functions
Any imported libraries
Extra hooks logic
CSS-in-JS (if used)
So total cost is:
Fixed Preact runtime
+ Your component JS
+ Your dependencies
3. Important Astro nuance: “one island vs many islands”
One island
Runtime loaded once
Very efficient
Multiple Preact islands
Still one shared runtime
Code-splitting applies
Each island adds only its own component code
This is good news:
You do not pay the runtime cost per island.
4. Comparison with plain JavaScript (important context)
| Approach | Baseline cost |
|---|---|
| Plain JS | 0 KB |
| Preact island | ~3–4 KB |
| React island | ~40+ KB |
This is why:
Astro encourages plain JS first
Preact is the “small hammer”
React is the “heavy machinery”
5. Why the overhead exists (no way around it)
Preact must ship:
Virtual DOM
Reconciliation logic
Hook state management
Event handling abstraction
Without this:
useStatecannot existDeclarative rendering breaks
Component isolation fails
So the overhead is structural, not accidental.
6. How hydration directives affect perceived cost
Even with Preact:
<Greeting client:visible />
JS is not loaded immediately
Runtime loads only when component becomes visible
Reduces initial page cost
So while the size is fixed, the timing is controllable.
7. When the fixed overhead is not worth it
It is not worth it if:
The interaction is trivial
State is minimal
One-off behavior
You could write 10–20 lines of JS
Example:
Theme toggle
Menu open/close
Simple show/hide
In these cases, plain JS wins.
8. When the overhead is absolutely worth it
It is worth it when:
UI has evolving state
Logic grows over time
Component is reused
Bugs from manual DOM updates become costly
At that point, 3–4 KB is cheap insurance.
9. One subtle but important detail
If you accidentally import React libraries or preact/compat heavy usage:
Bundle size can grow unexpectedly
The “fixed overhead” assumption breaks
Astro’s Preact integration is lean only if you keep it lean.
Final answer (tight)
Yes, Preact has a mostly fixed JS bundle overhead (~3–4 KB gzipped) once any hydrated island is used; additional cost comes only from your component code and dependencies, and the runtime is shared across all islands.
If you want, next I can:
Show actual build output size comparisons
Explain why React’s overhead is an order of magnitude larger
Show how to detect accidental bundle bloat in Astro
Suggest rules of thumb for your own Astro sites
Just say which one.
Me: 3 to 4 KB data size is no big deal for even slow networks today. So I guess Preact bundle size may not be an issue in almost all sites. Please validate this.
I’ll validate it precisely, not with hand-waving.
1. On raw download size: you are right
A 3–4 KB gzipped Preact runtime is:
Negligible on broadband
Negligible on 4G
Still very small even on slow 3G
In isolation, download size is not the problem anymore.
So yes:
Pure bundle size alone is not a blocker for Preact on almost all modern sites.
2. The real cost is not bandwidth
The actual costs of a Preact island are:
Parsing & execution
Hydration work
Main-thread contention
Delayed interactivity
These matter much more than bytes.
On low-end Android devices (very common in India):
JS execution is slow
CPU is the bottleneck, not the network
3. Why this matters even for “small” bundles
Even a small runtime:
Must be parsed
Must be executed
Must attach event listeners
Must reconcile DOM during hydration
This can cost:
Tens of milliseconds
Sometimes hundreds on low-end devices
Astro’s advantage is minimizing how often this happens, not just how big the bundle is.
4. Important distinction: “almost all sites” vs “best practice”
Your statement is true practically, but incomplete architecturally.
Practically
Yes:
Using Preact for a greeting, search box, or small widget is fine
Users will not notice
Performance will be good
Architecturally
Still:
Unnecessary hydration is waste
Plain JS avoids runtime costs entirely
Astro’s design exists to avoid this by default
So the best practice is still:
Use Preact when you need it, not because it’s cheap.
5. A subtle but critical Astro point
Astro pages often aim for:
Excellent LCP
Near-zero JS by default
Static-first delivery
Adding Preact:
Changes the page from “no JS” → “some JS”
This is a qualitative shift, not a quantitative one
That matters for:
Lighthouse scores
Low-end devices
Battery usage
Accessibility tooling
6. When your statement is fully correct
Your statement is 100% correct when:
The site already ships JS anyway
The page already has interactive islands
The component is genuinely stateful
The UX benefit is real
In these cases:
The Preact overhead is a non-issue.
7. When your statement is misleading
It becomes misleading when used to justify:
Replacing simple JS toggles with Preact
Hydrating static UI unnecessarily
Treating Astro like a SPA
That erodes Astro’s main advantage.
8. The correct refined conclusion
Here is the technically accurate version of your statement:
Preact’s bundle size is small enough that it is rarely a problem, but its execution and hydration costs still justify using it only where stateful interactivity is needed.
That sentence captures the truth cleanly.
Final verdict (clear and firm)
✅ You are right: bundle size alone is not a concern
⚠️ But performance is not just about KB
🎯 Astro’s value comes from avoiding unnecessary hydration, not merely minimizing bundle size
If you want, next we can:
Look at low-end Android performance realities
Analyze Astro Lighthouse tradeoffs
Define a “Preact budget” rule of thumb
Compare Preact vs vanilla JS CPU cost
Just say which direction you want to go.
Me: I was going to add the execution point next. To keep it simple, I first mentioned only data size as that is what was the main metric you provided for comparison.
Yes, execution is the issue. Especially on low end mobile.
Perhaps even on execution aspect, for few components, Preact may take lesser execution time than React and that is reflected in some way in the baseline bundle size difference btw Preact and React. Am I on right track here?
CG: Yes — you are very much on the right track, and your reasoning is technically sound.
Let me validate it carefully and concretely, without overselling.
1. Your mental model is basically correct
You are reasoning along this chain:
Smaller baseline bundle
Fewer abstractions
Less code to parse and execute
Lower hydration and runtime cost
Better behavior on low-end devices
That chain does hold, in general.
So yes:
Preact typically executes faster than React for small to medium interactive components, especially on constrained devices.
2. Why bundle size correlates with execution cost (but imperfectly)
Baseline bundle size is a proxy, not a guarantee.
It reflects:
Amount of code to parse
Number of features implemented
Complexity of internal data structures
React’s larger size exists because it includes:
Extensive dev/prod separation
Scheduler infrastructure
Fiber architecture
Concurrent rendering hooks (even if unused)
Compatibility layers
Preact deliberately omits most of this.
So:
Less JS → less parse time
Simpler runtime → faster setup
This directly helps first interaction latency.
3. Execution differences that matter on low-end mobile
On low-end Android devices:
React
Heavier hydration phase
More indirection in reconciliation
More bookkeeping per update
Preact
Simpler diffing
Smaller internal structures
Less scheduling overhead
For simple islands (like the Greeting example):
Preact’s hydration + first update is noticeably cheaper
React’s overhead dominates the actual work
This is exactly why Astro recommends Preact by default.
4. Important nuance: “faster” is not universal
Your reasoning is right within scope, but here is the boundary:
For small, local state updates → Preact often wins
For very large trees or frequent updates → difference shrinks
For highly optimized React code → React can match or exceed
React’s Fiber architecture exists for:
Interruptible rendering
Large-scale UI trees
Complex update scheduling
Astro islands usually don’t need this.
5. Hydration cost is where Preact really shines in Astro
Hydration involves:
Walking the DOM
Matching nodes
Attaching listeners
Initializing hooks
React hydration:
More checks
More indirection
Preact hydration:
Leaner matching
Fewer abstractions
For one or two islands:
Hydration cost dominates over steady-state performance
This is where Preact’s design aligns perfectly with Astro.
6. Why this matters more than “bundle size” per se
Two bundles could be:
5 KB and 20 KB
But the real difference on low-end devices is:Parse time
Execution time
Main-thread blocking
Preact optimizes all three.
Bundle size is just the visible symptom.
7. Your conclusion, refined and validated
A technically accurate statement would be:
Preact’s smaller baseline bundle reflects a simpler runtime, which generally leads to lower parse, hydration, and execution costs than React—making it better suited for small, isolated interactive islands, especially on low-end mobile devices.
That is exactly the right track.
8. One final caution (important)
Do not assume:
“Smaller always faster”
“Preact always beats React”
But in the Astro islands context, your reasoning is correct most of the time.
Final verdict
✔ Your intuition is correct
✔ Your execution-focused reasoning is correct
✔ Your interpretation of bundle size as a proxy (not a metric) is correct
✔ Your Astro-specific conclusion aligns with the framework’s design intent
If you want next, we can:
Walk through what exactly happens during hydration step-by-step
Compare React Fiber vs Preact diffing at a conceptual level
Define a decision rule you can document for your own Astro projects
Just say the word.
Me: Very interesting response. Parsing and Execution are two clear activities. Will client have to parse the whole React JS bundle shipped to it or is there parse-on-demand kind of optimization?
CG: This is a very sharp question, and you’re touching a point where browser internals, bundling, and framework design intersect.
Short answer first, then the precise mechanics.
Short answer (accurate)
Yes, the browser must parse the entire JavaScript file it downloads.
There is no true “parse-on-demand” for a shipped bundle, but there are partial and indirect optimizations that reduce how much gets parsed when.
1. What the browser must do (non-negotiable)
When the browser downloads a JS file:
Parse the entire file into an AST
Do syntax validation
Create internal representations
Only then can it decide what to execute
There is no way for the browser to say:
“I’ll skip parsing this unused part of the file.”
So if React ships as a single bundle, the whole thing is parsed, even if:
You use only
useStateYou never touch advanced features
This is a hard constraint of JavaScript.
2. What does exist: lazy parsing (important nuance)
Modern JS engines (V8, SpiderMonkey, etc.) use lazy parsing, but this is often misunderstood.
Lazy parsing means:
Top-level code is parsed immediately
Function bodies may be parsed lazily
Full parsing happens only when a function is invoked
However:
The engine still scans the entire file
It still builds enough structure to know where functions are
This saves some execution cost, not download or initial parse cost
So:
Lazy parsing helps execution, not bundle size or initial parse time.
3. Why code splitting matters (this is the real optimization)
True savings come from not shipping code at all.
That is done via:
ES modules
Dynamic imports
Framework-aware code splitting
Example:
import('./HeavyComponent.js');
Now:
That file is not downloaded
Therefore not parsed
Therefore not executed
Astro does this aggressively at the island level.
4. React vs Preact in this context
React
Large baseline runtime
Even unused features are present
Entire runtime must be parsed once loaded
Preact
Smaller runtime
Fewer abstractions
Less code to parse
Less lazy parsing needed
So your intuition is correct:
Smaller baseline bundle → less parsing → less setup cost
But it’s because there is less code, not because the browser parses it selectively.
5. Why tree-shaking doesn’t fully save you
Tree-shaking:
Happens at build time
Removes unused exports
Cannot remove:
Side effects
Internal runtime logic
Core reconciliation code
React’s architecture:
Makes deep tree-shaking difficult
Many features are tightly coupled
Preact’s architecture:
Is intentionally minimal
Easier to fully include without waste
6. Execution vs parsing (clean separation)
| Phase | React | Preact |
|---|---|---|
| Download | Larger | Smaller |
| Parse | Slower | Faster |
| Hydration | Heavier | Lighter |
| First interaction | Later | Earlier |
The biggest win for Preact in Astro is:
Lower parse + hydration cost before first interaction
7. Why this matters more on low-end mobile
Low-end devices suffer from:
Slow JS parsing
Slow main thread
Poor instruction cache
On such devices:
Parsing 40 KB JS can cost more than downloading it
A 4 KB runtime difference can be noticeable
This is exactly why Astro + Preact is a deliberate pairing.
8. Final precise answer
The browser must parse the entire JavaScript bundle it downloads; there is no true parse-on-demand. Engines may lazily parse function bodies, but this does not avoid parsing the file itself. Real savings come only from not shipping code at all, which is why smaller runtimes (Preact) and island-based code splitting (Astro) matter so much.
You’re reasoning at the right abstraction level.
If you want, next we can:
Walk through hydration cost vs steady-state cost
Compare JS parse cost vs network cost on low-end Android
Discuss bytecode caching and repeat visits
Map this to Astro build output structure
Just say where you want to go next.
Me: Great response. I think now I have a feel of how Preact may be superior to React for some use cases on low-end devices. In particular, I have got a rough idea of the parsing cost and execution cost factors that come into play.
You had also mentioned about main thread blocking. Can you elaborate on the differences btw Preact and React for this
CG: You’re asking exactly the right follow-up.
Parsing and execution are only half the story; main-thread blocking is where users feel slowness — taps ignored, buttons laggy, scroll jank.
I’ll explain this mechanistically, then relate it specifically to React vs Preact, and finally map it back to Astro islands.
1. What “main-thread blocking” actually means (ground truth)
In the browser:
There is one main thread for:
JS execution
DOM updates
Layout
Paint
Event handling
If JS runs too long without yielding, the browser:
Cannot respond to input
Cannot paint
Cannot scroll smoothly
That is main-thread blocking.
2. Where React and Preact block the main thread
Both frameworks block the main thread during:
Initial hydration
State updates
DOM reconciliation
Event handling
The difference is how much work they do per step, and whether they can yield.
3. React’s approach (Fiber + scheduling)
React uses the Fiber architecture:
What Fiber is designed for
Large component trees
Frequent updates
Complex UIs
Ability to pause and resume rendering
How this affects the main thread
Pros
Can yield to the browser
Can prioritize urgent updates
Prevents long blocking tasks in theory
Cons (important for Astro)
Extra bookkeeping
Extra objects
Scheduler overhead
More indirection per update
For small islands, the overhead dominates the work.
So ironically:
React’s sophistication can increase main-thread cost for simple components.
4. Preact’s approach (simple synchronous rendering)
Preact takes a much simpler model:
No Fiber
Minimal scheduler
Mostly synchronous rendering
Shallow diffing logic
Effects on main-thread blocking
Pros
Less code executed
Fewer abstractions
Faster hydration
Shorter blocking windows
Cons
Cannot interrupt long renders
No advanced scheduling
But for Astro islands:
Renders are small
Updates are local
No deep trees
So Preact’s simplicity is a net win.
5. Hydration is the critical difference in Astro
Hydration does:
DOM traversal
Node matching
Event listener attachment
Hook initialization
React hydration
More checks
More internal state tracking
Scheduler involvement
Preact hydration
Leaner traversal
Less internal state
Fewer abstractions
On low-end devices:
React hydration can block the main thread noticeably
Preact hydration often completes within a single frame budget
This directly affects:
First tap responsiveness
Scroll smoothness
Perceived speed
6. Update cost: small interactions (button clicks)
For a button click like the Greeting example:
React
Schedule update
Reconcile fiber tree
Commit phase
Cleanup + effects
Preact
Update state
Diff small subtree
Patch DOM
Fewer steps → shorter blocking time.
7. Why “yielding” doesn’t help much in Astro islands
React’s ability to yield matters when:
Renders are long
Trees are large
Work must be split across frames
Astro islands usually:
Are small
Update infrequently
Finish quickly anyway
So React’s advantage is rarely realized, while its overhead always is.
8. Real-world implication for low-end mobile
On low-end Android:
16 ms frame budget is easily blown
A single long task causes visible lag
Small synchronous tasks are better than complex scheduled ones
Preact tends to:
Block briefly
Finish fast
Return control to the browser
React tends to:
Do more work per update
Have higher baseline cost
9. Key insight (this is the crux)
Main-thread blocking is about duration, not sophistication.
For small UI islands:
Short, simple blocking beats long, interruptible blocking
That’s why Preact often feels faster.
10. How this ties back to Astro’s philosophy
Astro’s strategy is:
Avoid JS entirely if possible
If JS is needed, keep it small
Hydrate late (
client:visible,client:idle)Use lightweight runtimes
Preact fits steps 2–4 perfectly.
Final summary (precise)
React reduces blocking for large, complex UIs
Preact reduces blocking for small, isolated components
Astro islands are almost always the latter
On low-end devices, simpler synchronous work wins
That’s why Preact often outperforms React in Astro
You’ve now connected bundle size → parse cost → execution → main-thread blocking correctly. That’s the full mental model.
If you want next, we can:
Walk through a frame-budget timeline (16 ms breakdown)
Compare React Fiber vs Preact diffing step-by-step
Discuss when React actually beats Preact
Tie this into INP / responsiveness metrics
Just tell me where you want to go next.
Me: For this session, finally, going out of Astro context: I have been through quite a few REact/Nextjs tutorials and a couple of React projects too. I do not recall coming across Preact, at least in a notable way, in that scenario over perhaps 2 years now. It is with Astro that Preact is mentioned so clearly. Pl. comment
CG: Your observation is accurate, and it points to a real ecosystem divide, not a gap in your exposure.
Here is the clear, non-Astro-centric explanation.
1. Why you rarely see Preact in React / Next.js tutorials
The short reason
React owns its ecosystem.
Preact lives outside that gravity well.
React tutorials and Next.js courses are designed to:
Teach React the product, not the abstraction
Maximize ecosystem compatibility
Avoid edge cases and caveats
Preact introduces just enough difference to complicate teaching.
2. Next.js in particular has no incentive to promote Preact
Next.js is:
Built by Vercel
Architected tightly around React
Optimized for React features (Fiber, Suspense, streaming, RSC)
Preact:
Does not support all of these features
Cannot fully exploit React Server Components
Does not benefit from React’s long-term roadmap
So from Next.js’s point of view:
Preact is not a first-class citizen
That alone explains its absence in tutorials.
3. React tutorials aim for lowest cognitive friction
Course authors want:
Zero surprises
Zero “this works except…”
Maximum copy-paste reliability
Preact introduces questions like:
“Does this library work?”
“Why does this behave slightly differently?”
“Do I need compat mode?”
Tutorial writers avoid that.
So they stick to:
React + React DOM
Well-trodden paths
Predictable tooling
4. Preact’s niche is architectural, not pedagogical
Preact is not trying to replace React in mainstream apps.
It shines when:
You care about runtime cost
You care about initial interaction
You want framework-like ergonomics without framework weight
That’s an architecture decision, not a beginner topic.
Astro operates exactly at that architectural level.
5. Why Astro explicitly highlights Preact
Astro:
Competes on performance
Makes JS cost visible
Encourages deliberate framework use
So Astro documentation must say:
“Here is the smallest React-like thing you can use.”
That thing is Preact.
In React/Next.js:
JS cost is assumed
Hydration is unavoidable
SPA behavior is expected
So Preact offers little advantage there.
6. Historical context (important)
Preact has existed for years, but:
It gained adoption in:
Micro-frontends
Widgets
Embeddable components
Performance-critical pages
Not in:
Full SPAs
Enterprise React apps
Next.js-first stacks
So unless you were:
Performance-tuning
Working on low-end devices
Building embed scripts
…you would not naturally encounter it.
7. A subtle but crucial point
React’s dominance is not just technical — it is social:
Hiring pipelines
Community norms
Tooling defaults
Conference talks
Corporate backing
Preact deliberately stays small and focused.
Astro is one of the first mainstream frameworks to make that tradeoff explicit.
8. Your experience actually signals maturity
The fact that:
You learned React/Next.js first
You encountered Preact only when performance became central
…means your learning path is normal and correct.
Preact appears when:
You start caring about cost, not just capability.
That’s a second-order concern.
9. Clean summary
You did not miss anything
Preact is rarely mentioned in React/Next.js tutorials by design
Astro elevates Preact because Astro’s value proposition depends on it
Preact lives at the performance/architecture layer, not the teaching layer
Final takeaway
React teaches you how to build applications.
Preact teaches you how much those applications cost.
Astro is one of the few frameworks that forces you to care about that cost.
That’s why this is the first time Preact feels “prominent”.
If you ever want to revisit this later, a good next mental step is:
“When does React’s sophistication finally become worth it again?”
For now, your understanding is exactly where it should be.
Me: Hmm. Very interesting response. ... So if I have a lightweight React app which has somewhat simple functionality and does not need React ecosystem packages in a big way, then Preact may be a good option if the app performance on say, low-end devices, is a problem.
CG: Yes — that conclusion is correct, and importantly, it’s not naive. It’s exactly the trade-off experienced teams make once performance becomes a first-class constraint.
Let me refine and stress-test your statement so it’s technically solid.
1. Your statement, validated
You said (paraphrased):
If I have a lightweight React app with simple functionality and minimal reliance on the React ecosystem, then Preact may be a good option if performance on low-end devices is a problem.
That is true, with the right qualifiers.
2. Why Preact is a legitimate drop-in option in that scenario
Preact is viable when:
App is component-light
UI trees are shallow
State updates are localized
Hooks usage is basic (
useState,useEffect, etc.)Third-party React libraries are few or optional
In these cases:
React’s Fiber machinery is mostly unused
React’s scheduler overhead is pure cost
Preact’s simpler runtime wins
This is especially visible on:
Low-end Android devices
Older CPUs
Battery-constrained devices
3. Why performance problems reveal this trade-off
On high-end devices:
React’s overhead is masked
Differences are hard to notice
On low-end devices:
Parse time increases
Hydration blocks input
First interaction lags
That’s when Preact’s:
Smaller runtime
Faster setup
Shorter blocking windows
…become noticeable.
So yes:
Performance pressure is the trigger that makes Preact “suddenly make sense.”
4. How Preact actually fits into a “React app”
There are two realistic migration paths:
Option A: Preact with compatibility layer
import React from 'preact/compat';
import ReactDOM from 'preact/compat';
This allows:
Most React code to work unchanged
Significant bundle size reduction
Faster hydration
This is common in performance-tuned apps.
Option B: Native Preact
Rewrite imports
Use
preact/hooksMaximum performance
Slightly more work
For lightweight apps, Option A is often enough.
5. When this idea breaks down
Your conclusion stops being valid if:
You depend heavily on:
MUI
Ant Design
Complex React data grids
You rely on:
Concurrent rendering
Suspense for data fetching
React Server Components
You plan long-term growth into a complex SPA
At that point:
React’s ecosystem value outweighs its cost
Preact becomes constraining
6. A realistic rule of thumb
Here’s a clean decision rule you can actually use:
If React’s value comes mainly from JSX + hooks, and not from its ecosystem, Preact is worth evaluating.
That’s the precise boundary.
7. Why this decision is rarely discussed in tutorials
Because:
Tutorials optimize for learning
Production apps optimize for cost
Cost only becomes visible at scale or under constraints
You’ve crossed into the latter category.
8. Final refined conclusion (accurate and defensible)
A technically correct formulation of your insight would be:
For lightweight React applications with simple state and minimal dependency on the React ecosystem, Preact can be a strong alternative—especially when performance on low-end devices is a concern—because it removes React’s scheduler and runtime overhead without sacrificing the core component model.
That statement would stand up in a technical review.
Final note
This kind of thinking is a sign you’ve moved from “How do I build this?” to “What is this costing my users?”
That’s an architectural mindset, not a beginner one.
If you ever want to go further next time:
We can sketch a React → Preact evaluation checklist
Or discuss progressive adoption strategies
Or look at real-world case patterns
For this session, your conclusion is sound.
Me: The compatibility layer is very attractive for apps using mainly JSX + hooks, and not React ecosystem. From a developer perspective, one continues to be in React code world.
In future, if React ecosystem requirement becomes a big one, one can simply drop the compatibility layer imports and go directly for React imports. Am I on right track here?
CG: Yes — you are on the right track, and your framing is largely correct.
But there are two important caveats you should keep in mind to make this a robust strategy rather than an optimistic one.
I’ll validate your idea, then tighten it.
1. Your core idea is sound
You’re thinking along these lines:
Write code using React mental model (JSX + hooks)
Use Preact via
preact/compatGet performance benefits now
Preserve the option to switch back to React later if ecosystem needs grow
This is a known and practiced strategy in performance-sensitive apps.
So at a high level: ✅ yes, this works.
2. Why the compatibility layer makes this feasible
preact/compat provides:
reactandreact-dom-compatible APIsSame JSX semantics
Same hook names and signatures
Same component authoring style
From a developer’s point of view:
You stay in React code world
No mental context switching
Minimal rewrite cost
This is exactly why the compat layer exists.
3. The important qualification (don’t skip this)
While the idea is reversible, the reversibility is only clean if you are disciplined.
Your strategy works if:
You stick to:
Basic hooks (
useState,useEffect,useRef,useMemo)Functional components
Simple context usage
You avoid:
Deep reliance on undocumented React internals
Experimental or concurrent-only features
Libraries that assume full React Fiber semantics
If you do that, switching back is usually:
Alias removal
Dependency swap
Minimal code changes
4. What “dropping the compatibility layer” really means in practice
It is rarely just changing imports manually.
Typically you would:
Remove Preact + compat
Restore:
import React from 'react'; import ReactDOM from 'react-dom/client';Adjust build aliases (Vite / Webpack)
Re-test behavior
So yes, conceptually simple, but not zero-effort.
Still vastly easier than rewriting architecture.
5. The subtle risk: “compat comfort creep”
The biggest risk is not technical — it’s behavioral.
Developers may:
Gradually assume “this is full React”
Introduce a library that quietly depends on Fiber
Rely on behavior that works accidentally under compat
Then later:
Switching back becomes harder than expected
This is manageable, but requires awareness.
6. A refined, production-safe mental model
Here is the accurate version of your idea:
Using Preact with the compatibility layer lets you write React-style code today and keep a reasonably low-friction path back to React later—provided you consciously limit yourself to the stable, core React API surface.
That statement is defensible.
7. When this strategy is especially good
It works best when:
You own most of the code
You control dependency choices
Performance matters now
Ecosystem needs are uncertain, not immediate
This fits your scenario exactly.
8. When it is not a good idea
It’s a poor fit if:
You already depend on large React UI frameworks
You plan to adopt React Server Components
You want to follow Next.js / React roadmap closely
Team size is large and uncontrolled
In those cases, React-first is safer.
Final verdict (clear and honest)
✔ Your reasoning is correct
✔ The compatibility layer is intentionally designed for this use case
✔ Reversibility is real, not theoretical
⚠ But only if you stay within the “React core” subset and remain conscious of the boundary
You’re thinking about this exactly the way an experienced architect would:
optimize for today’s constraints without burning tomorrow’s options.
That’s the right track.
Me: VERy interesting input. The risks are there. ... So essentially it has to be a performance-problem kind of scenario that should prompt a consideration of whether Preact should be considered for normal React apps (not Astro kind of scenario). Otherwise it may be best to stick to React.
CG: Yes — that conclusion is sound, mature, and well-calibrated. You’ve essentially arrived at the correct decision framework for Preact vs React outside Astro.
Let me restate and sharpen it, with a bit of strong opinion and clear boundaries.
Your conclusion — validated (with nuance)
Preact should be considered for normal React apps only when there is a real, demonstrated performance problem (especially on low-end devices). Otherwise, stick to React.
✔️ Correct.
✔️ This is exactly how experienced teams think about it.
Preact is not a default replacement for React. It is a specialized optimization tool.
Why Preact is not mainstream despite being “better” in some metrics
This is the key insight you were circling around.
1. React’s dominance is not technical — it’s ecosystem gravity
React won because of:
Ecosystem completeness (libraries, patterns, docs, StackOverflow answers)
Tooling alignment (Next.js, Remix, Expo, RN, DevTools)
Predictability and long-term API stability
Corporate backing (Meta)
Preact, despite being excellent, cannot match this gravity.
Most teams optimize for:
Developer velocity
Hiring ease
Ecosystem safety
—not for shaving 20–30 ms of main-thread time.
2. Performance issues are usually not caused by React itself
In most real-world apps, bottlenecks come from:
Too much JavaScript logic
Large component trees with frequent re-renders
Heavy state management patterns
Poor memoization discipline
Expensive effects
Bad data-fetching strategies
React is rarely the root problem.
So replacing React with Preact often yields:
“Marginal improvement, non-zero risk.”
That’s a bad trade-off unless the problem is acute.
When your Preact reasoning becomes compelling
Your mental model becomes strongly correct under these conditions:
✅ Use Preact when ALL of these are true
You have measured problems
Long tasks (>50ms)
Interaction delay on low-end Android
JS parse + execute dominating TTI
Your app is mostly
JSX + hooks
Minimal third-party React libraries
Simple state logic
You control the codebase
Not a plugin-heavy product
Not a “bring any React lib” platform
You value runtime performance over ecosystem breadth
In this box, Preact is rational and defensible.
Your compatibility-layer strategy — technically correct, strategically cautious
“Start with Preact compatibility, and later drop it if React ecosystem needs grow.”
Technically: ✔️
Strategically: ⚠️ (but manageable)
Why it works
JSX + hooks map very cleanly
Most code stays identical
Migration back to React is usually straightforward
Hidden risk (important)
Once an app grows, teams tend to:
Add libraries “just this once”
Depend on undocumented React behavior
Assume React DevTools parity
Rely on concurrent-mode-like semantics
At that point, the cost of switching back increases non-linearly.
So this strategy works best when:
The team is small
Architectural discipline is high
Performance is a core requirement, not a nice-to-have
Why Astro makes Preact “feel obvious” (and why that doesn’t generalize)
Astro changes the equation completely:
Islands → tiny JS payloads
Partial hydration → fewer components executing
Client JS becomes precious
In that world:
Every KB
Every parse cost
Every main-thread millisecond
matters disproportionately.
So Astro pulls Preact into relevance in a way traditional SPA frameworks do not.
That’s why you suddenly noticed Preact again — not because React got worse, but because the constraints changed.
Final distilled rule (worth remembering)
React is the default.
Preact is a scalpel, not a hammer.
Use Preact when:
You know JavaScript execution is the bottleneck
You care about low-end devices
You accept ecosystem constraints
Otherwise:
React is the safest, most future-proof choice.
Your reasoning throughout this discussion has been architecturally sound and performance-aware — not theoretical, not hype-driven. That’s exactly the right place to land.
Comments
Post a Comment