Preact Compatibility with React

How to use Preact as a drop-in React replacement and which React libraries work with preact/compat.

Preact is a 4KB alternative to React with the same core API. The preact/compat module provides a compatibility layer that lets you use most React libraries unchanged.

Setting Up preact/compat

With Vite

// 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",
      "react-dom/client": "preact/compat",
    },
  },
});

With Next.js (experimental)

npm install next preact preact-render-to-string
// next.config.js
module.exports = {
  reactProductionProfiling: false,
  webpack: (config) => {
    config.resolve.alias = {
      ...config.resolve.alias,
      react: "preact/compat",
      "react-dom": "preact/compat",
    };
    return config;
  },
};

What Works

Library Works? Notes
React Router Full support via preact/compat
Redux Works unchanged
Zustand Works unchanged
React Query / TanStack Query Works unchanged
Framer Motion ⚠️ Partial — some animation features may differ
React Testing Library Use @testing-library/preact instead
styled-components ⚠️ Works but increases bundle size significantly
MUI (Material UI) ⚠️ Partial — relies on some React internals
Radix UI Works via preact/compat
Headless UI Works via preact/compat

Key API Differences

Feature React Preact Description
Event system SyntheticEvent Native DOM events Preact passes real browser events directly
Event pooling Yes (deprecated) No No e.persist() needed in Preact
onChange Fires on input change Fires on input change Preact uses native input event for consistency
SVG support Full Full Preact handles SVG attributes correctly
String refs Supported (legacy) Not supported Use callback refs or useRef instead
PropTypes Built-in (deprecated) Not included Use TypeScript for type safety

Lifecycle Mapping

React Class Lifecycle Preact / preact/compat
componentDidMount useEffect(() => {}, []) or onMount in class
componentDidUpdate useEffect(() => {}, [deps])
componentWillUnmount useEffect cleanup or onUnmount in class
shouldComponentUpdate React.memo or memo from preact/compat
getDerivedStateFromProps useState with initializer or getDerivedStateFromProps in class

Preact-Only Features

// Hook into VDOM diffing with hooks
import { options } from "preact";

// Log every render
options.render = (vdom) => {
  console.log("rendering:", vdom.type);
};

// Preact Devtools
import "preact/debug"; // Adds helpful development warnings

Testing with Preact

// Use @testing-library/preact instead of @testing-library/react
import { render, screen, fireEvent } from "@testing-library/preact";
import userEvent from "@testing-library/user-event";
import { Counter } from "./Counter";

test("increments on click", async () => {
  const user = userEvent.setup();
  render(<Counter />);
  const button = screen.getByRole("button");
  await user.click(button);
  expect(button).toHaveTextContent("1");
});