Skip to content

ADR-031: Preact Over React for Islands

Accepted

Astro supports multiple UI frameworks for islands. The starter ships with @astrojs/preact as the default. This is not self-evident — React is far more widely known. This ADR documents why Preact was chosen and how users can still access the React ecosystem without switching.

FrameworkRuntime (gzipped)
Preact~3KB
Preact + compat~5KB
React + ReactDOM~45KB
Vue 3~22KB
SolidJS~7KB

With a 160KB total JS budget and a zero-JS baseline, React would consume ~28% of the entire budget before any application code. Preact costs ~2% of the same budget. For a performance-first starter this difference is the entire value proposition.

The most important thing to understand: Preact ships a full React compatibility layer.

preact/compat implements the complete React API. To use React component libraries with Preact, add these aliases to astro.config.mjs:

vite: {
resolve: {
alias: {
react: 'preact/compat',
'react-dom': 'preact/compat',
'react-dom/server': 'preact/compat/server',
},
},
},

With these aliases in place:

  • React hooks (useState, useEffect, useContext, useRef, etc.) work identically
  • JSX syntax is unchanged
  • forwardRef, createContext, memo, lazy, Suspense all work
  • Most React component libraries (Radix UI, Headless UI, React Hook Form, etc.) work without modification
  • The bundle cost increases from ~3KB to ~5KB — still 9x smaller than React

Pros: Largest ecosystem, most familiar to developers, best IDE tooling, most third-party components

Cons: ~45KB runtime gzipped — incompatible with performance budget goals for a starter that defaults to zero JS

Pros: ~3-5KB runtime, full React API compatibility, access to React ecosystem via aliases, identical JSX syntax

Cons: Less familiar name, occasional edge-case incompatibilities with React libraries that use internal React APIs

Pros: Smallest compiled output, no shared runtime

Cons: Different syntax (not JSX), no React ecosystem compatibility, steeper learning curve for React developers

Pros: True zero JS

Cons: Eliminates all interactive island capability — too restrictive for a general-purpose starter

Users should consider switching from Preact to React if:

  1. A required library uses React internal APIs not covered by preact/compat (rare but possible with some animation libraries)
  2. The project’s JS budget is not a concern (e.g. a full SPA built on top of the starter)
  3. The team has strong React expertise and the compat aliases cause confusion

To switch, replace @astrojs/preact with @astrojs/react in astro.config.mjs and package.json, and remove the compat aliases.

Use Preact as the default islands framework. Document preact/compat aliases prominently so users understand they have full React ecosystem access. This is not a limitation — it is a deliberate performance optimisation that costs nothing in API compatibility.

  • ~3KB islands runtime vs ~45KB for React — 15x smaller
  • Full React API available via preact/compat
  • Most React component libraries work without modification
  • Consistent with zero-JS philosophy — JS additions are minimal and justified
  • Developers unfamiliar with Preact may not realise React libraries work
  • Rare incompatibilities with libraries using React internals (e.g. some versions of React Spring, Framer Motion)
  • IDE error messages may reference Preact types rather than React types
  • JSX syntax is identical — no learning curve for React developers
  • preact/compat aliases are opt-in, not configured by default in the starter (to avoid confusion for users who don’t need them)

Add a guide to docs/patterns/ documenting how to configure preact/compat aliases and which popular React libraries have been tested.

Status: Not yet implemented. The docs/patterns/ directory exists but does not yet contain a preact/compat guide.

  • Testable consequences:
    • TC-1: react and react-dom are not dependencies; preact is.
  • Checks:
    • TC-1 → check no-react-dep (status: warn)
  • Not machine-checkable: whether preact/compat aliasing remains prominently documented.
  • Graduation log: (empty at creation; entries added when a check changes status)

Date: 2026-02-18
Participants: Template maintainers
Outcome: Accepted