Help
Support Us

Getting Started

New to Preact? New to Virtual DOM? Check out the tutorial.

This guide helps you get up and running to start developing Preact apps, using 3 popular options. If you're new to Preact, we recommend starting with Vite.



No build tools route

Preact is packaged to be used directly in the browser, and doesn't require any build or tools:

<script type="module">
  import { h, render } from 'https://esm.sh/preact';

  // Create your app
  const app = h('h1', null, 'Hello World!');

  render(app, document.body);
</script>

🔨 Edit on Glitch

The primary drawback of developing this way is the lack of JSX, which requires a build step. An ergonomic and performant alternative to JSX is documented in the next section.

Alternatives to JSX

Writing raw h or createElement calls can be tedious. JSX has the advantage of looking similar to HTML, which makes it easier to understand for many developers in our experience. JSX requires a build step though, so we highly recommend an alternative called HTM.

HTM is a JSX-like syntax that works in standard JavaScript. Instead of requiring a build step, it uses JavaScript's own Tagged Templates syntax, which was added in 2015 and is supported in all modern browsers. This is an increasingly popular way to write Preact apps, since there are fewer moving parts to understand than a traditional front-end build tooling setup.

<script type="module">
  import { h, render } from 'https://esm.sh/preact';
  import htm from 'https://esm.sh/htm';

  // Initialize htm with Preact
  const html = htm.bind(h);

  function App (props) {
    return html`<h1>Hello ${props.name}!</h1>`;
  }

  render(html`<${App} name="World" />`, document.body);
</script>

🔨 Edit on Glitch

Tip: HTM also provides a convenient single-import Preact version:

import { html, render } from 'https://esm.sh/htm/preact/standalone'

For a more full example, see Using Preact with HTM and ImportMaps, and for more information on HTM, check out its documentation.

Create a Vite-Powered Preact App

Vite has become an incredibly popular tool for building applications across many frameworks in the past couple of years, and Preact is no exception. It's built upon popular tooling like ES modules, Rollup, and ESBuild. Vite, through our initializer or their Preact template, requires no configuration or prior knowledge to get started and this simplicity makes it a very popular way to use Preact.

To get up and running with Vite quickly, you can use our initializer create-preact. This is an interactive command-line interface (CLI) app that can be run in the terminal on your machine. Using it, you can create a new application by running the following:

npm init preact

This will walk you through creating a new Preact app and gives you some options such as TypeScript, routing (via preact-iso), and ESLint support.

Tip: None of these decisions need to be final, you can always add or remove them from your project later if you change your mind.

Getting ready for development

Now we're ready to start our application. To start a development server, run the following command inside your newly generated project folder:

# Go into the generated project folder
cd my-preact-app

# Start a development server
npm run dev

Once the server has started, it will print a local development URL to open in your browser. Now you're ready to start coding your app!

Making a production build

There comes a time when you need to deploy your app somewhere. Vite ships with a handy build command which will generate a highly-optimized production build.

npm run build

Upon completion, you'll have a new dist/ folder which can be deployed directly to a server.

For a full list of all available commands and their options, check out the Vite CLI Documentation.

Integrating Into An Existing Pipeline

If you already have an existing tooling pipeline set up, it's very likely that this includes a bundler. The most popular choices are webpack, rollup or parcel. Preact works out of the box with all of them, no major changes needed!

Setting up JSX

To transpile JSX, you need a Babel plugin that converts it to valid JavaScript code. The one we all use is @babel/plugin-transform-react-jsx. Once installed, you need to specify the function for JSX that should be used:

{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", {
      "pragma": "h",
      "pragmaFrag": "Fragment",
    }]
  ]
}

Babel has some of the best documentation out there. We highly recommend checking it out for questions surrounding Babel and how to set it up.

Aliasing React to Preact

At some point, you'll probably want to make use of the vast React ecosystem. Libraries and Components originally written for React work seamlessly with our compatibility layer. To make use of it, we need to point all react and react-dom imports to Preact. This step is called aliasing.

Note: If you're using Vite, Preact CLI, or WMR, these aliases are automatically handled for you by default.

Aliasing in Webpack

To alias any package in Webpack, you need to add the resolve.alias section to your config. Depending on the configuration you're using, this section may already be present, but missing the aliases for Preact.

const config = {
   //...snip
  "resolve": {
    "alias": {
      "react": "preact/compat",
      "react-dom/test-utils": "preact/test-utils",
      "react-dom": "preact/compat",     // Must be below test-utils
      "react/jsx-runtime": "preact/jsx-runtime"
    },
  }
}

Aliasing in Node

When running in Node, bundler aliases (Webpack, Rollup, etc.) will not work, as can be seen in NextJS. To fix this, we can use aliases directly in our package.json:

{
  "dependencies": {
    "react": "npm:@preact/compat",
    "react-dom": "npm:@preact/compat",
  }
}

Aliasing in Parcel

Parcel uses the standard package.json file to read configuration options under an alias key.

{
  "alias": {
    "react": "preact/compat",
    "react-dom/test-utils": "preact/test-utils",
    "react-dom": "preact/compat",
    "react/jsx-runtime": "preact/jsx-runtime"
  },
}

Aliasing in Rollup

To alias within Rollup, you'll need to install @rollup/plugin-alias. The plugin will need to be placed before your @rollup/plugin-node-resolve

import alias from '@rollup/plugin-alias';

module.exports = {
  plugins: [
    alias({
      entries: [
        { find: 'react', replacement: 'preact/compat' },
        { find: 'react-dom/test-utils', replacement: 'preact/test-utils' },
        { find: 'react-dom', replacement: 'preact/compat' },
        { find: 'react/jsx-runtime', replacement: 'preact/jsx-runtime' }
      ]
    })
  ]
};

Aliasing in Jest

Jest allows the rewriting of module paths similar to bundlers. These rewrites are configured using regular expressions in your Jest configuration:

{
  "moduleNameMapper": {
    "^react$": "preact/compat",
    "^react-dom/test-utils$": "preact/test-utils",
    "^react-dom$": "preact/compat",
    "^react/jsx-runtime$": "preact/jsx-runtime"
  }
}

Aliasing in TypeScript

TypeScript, even when used alongside a bundler, has its own process of resolving types. In order to ensure Preact's types are used in place of React's, you will want to add the following configuration to your tsconfig.json (or jsconfig.json):

{
  "compilerOptions": {
    ...
    "skipLibCheck": true,
    "baseUrl": "./",
    "paths": {
      "react": ["./node_modules/preact/compat/"],
      "react-dom": ["./node_modules/preact/compat/"]
    }
  }
}

Additionally, you may want to enable skipLibCheck as we do in the example above. Some React libraries make use of types that may not be provided by preact/compat (though we do our best to fix these), and as such, these libraries could be the source of TypeScript compilation errors. By setting skipLibCheck, you can tell TS that it doesn't need to do a full check of all .d.ts files (usually these are limited to your libraries in node_modules) which will fix these errors.

Using Preact with HTM and ImportMaps

An Import Map is a newer feature that allows you to control how browsers resolve module specifiers, usually to convert bare specifiers such as preact to a CDN URL like https://esm.sh/preact. While many do prefer the aesthetics import maps can provide, there are also real advantages to using them, such as more control over module resolution (read on to see how to alias) and solving the burden (as well as possible bugs) that comes with copying CDN URLs from file to file.

Here's an example of a import map in use:

<script type="importmap">
  {
    "imports": {
      "preact": "https://esm.sh/preact@10.19.2",
      "preact/": "https://esm.sh/preact@10.19.2/",
      "htm/preact": "https://esm.sh/htm@3.1.1/preact?external=preact"
    }
  }
</script>

<script type="module">
  import { render } from 'preact';
  import { useReducer } from 'preact/hooks';
  import { html } from 'htm/preact';

  export function App() {
    const [count, add] = useReducer((a, b) => a + b, 0);

    return html`
      <button onClick=${() => add(-1)}>Decrement</button>
      <input readonly size="4" value=${count} />
      <button onClick=${() => add(1)}>Increment</button>
    `;
  }

  render(html`<${App} />`, document.body);
</script>

Note: We use ?external=preact in the example above as many CDNs will helpfully provide the module you're asking for as well as its dependencies. However, this can trip up Preact as it (and React too) expect to be loaded as singletons (only 1 instance active at a time). Using ?external tells esm.sh that it doesn't need to provide a copy of preact, we can handle that ourselves with our import map

You can even use import maps to support aliasing:

<script type="importmap">
  {
    "imports": {
      "react": "https://esm.sh/preact@10.19.2/compat",
      "react-dom": "https://esm.sh/preact@10.19.2/compat"
    }
  }
</script>