React

Parcel works great for building single or multi-page React applications. It includes a first-class development experience with Fast Refresh, and supports JSX, TypeScript, and many styling methodologies out of the box.

Quick start

#

To quickly scaffold a new client-only React app with Parcel, run the following commands:

npm create parcel@next react-client my-react-app
cd my-react-app
npm start

Replace npm with yarn or pnpm to use your preferred package manager. See below for a deep dive.

Getting started

#

First, install react and react-dom into your project:

yarn add react react-dom

Most Parcel apps start with an HTML file. Parcel follows the dependencies (such as a <script> tag) from there to build your app.

src/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My Parcel App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="index.js"></script>
</body>
</html>
src/index.js:
import { createRoot } from "react-dom/client";
import { App } from "./App";

const container = document.getElementById("app");
const root = createRoot(container);
root.render(<App />);
src/App.js:
export function App() {
return <h1>Hello world!</h1>;
}

As you can see, we’ve referenced index.js from a <script> element in our HTML file. This imported react-dom and used it to render our App component into the <div id="app"> element in our page.

See Building a web app with Parcel for more details on getting started with a new project.

JSX

#

Parcel supports JSX automatically when it detects you are using React. If you’re using React 17 or later, it also automatically enables the modern JSX transform, which means you don't even need to import React for JSX to work, as you can see in App.js in the above example.

To learn more about JSX, see Introducing JSX and JSX In Depth in the React docs, and the JSX section from Parcel's JavaScript docs for details on how you can configure some details of how it's handled.

Fast Refresh

#

Parcel has first-class support for React Fast Refresh, which gives you quick feedback as you edit your code without needing to reload the page. In most cases, it can preserve component state as code is edited, even if you make an error. See the Hot reloading docs for details on how this works.

Tips

#

For more tips, see the official React Fast Refresh docs.

React Compiler

#

React Compiler automatically memoizes your components and hooks, so you don't need to manually use useMemo, useCallback, or React.memo to avoid unnecessary re-renders. Parcel includes React Compiler out of the box. To enable it, set the reactCompiler option for @parcel/transformer-js in your .parcelrc:

.parcelrc:
{
"extends": "@parcel/config-default",
"transformers": {
"*.{js,jsx,ts,tsx}": [
{
"plugin": "@parcel/transformer-js",
"config": {
"reactCompiler": true
}
}
]
}
}

React Compiler runs on your source code, but not on code in node_modules. It works with Fast Refresh, and resets memoized values when a component is hot reloaded.

By default, React Compiler targets React 19. If the react dependency in your package.json is version 17 or 18, Parcel compiles for that version instead, and you'll also need to install the react-compiler-runtime package:

yarn add react-compiler-runtime

See React Compiler in the JavaScript docs for the other options you can set. To see why React Compiler skipped a function, run Parcel with --log-level verbose.

TypeScript

#

TypeScript is supported out of the box. You can reference a .ts or .tsx file from your HTML page, and Parcel will compile it as you'd expect.

To add TypeScript definitions for React, install the following packages into your project:

yarn add @types/react @types/react-dom --dev

See the TypeScript docs for more details on using TypeScript with Parcel.

Flow

#

Flow types are not stripped by Parcel's builtin transpiler. To use Flow, install @parcel/transformer-babel and @babel/preset-flow, add @parcel/transformer-babel to your .parcelrc, and add @babel/preset-flow to your Babel config. Since Babel runs before Parcel's builtin transpiler, you'll also need to add @babel/plugin-syntax-jsx to your Babel config so that it can parse JSX.

See the Flow docs for more details on using Flow with Parcel.

Styling

#

Parcel supports many different ways of styling applications written with React.

CSS

#

You can import a CSS file into a JavaScript or TypeScript file to load it along with a component.

Button.js:
import './Button.css';

export function Button({ children }) {
return (
<button className="button">
{children}
</button>
);
}
Button.css:
.button {
background: hotpink;
}

You can also load CSS using a standard <link rel="stylesheet"> element in your HTML file, but referencing CSS from your components helps make it clear which components depend on which CSS. This can also help with code splitting because only the CSS necessary for the components that you render will be loaded.

Parcel also supports CSS languages like SASS, Less, and Stylus. See CSS for more details on how CSS is processed by Parcel.

CSS modules

#

By default, CSS imported from JavaScript is global. If two CSS files define the same class names, they will potentially clash and overwrite each other. To solve this, Parcel supports CSS modules.

CSS modules treat the classes defined in each file as unique. Each class name is renamed to include a unique hash, and a map is exported to JavaScript to allow referencing these renamed class names.

To use CSS modules, create a file with the .module.css extension, and import it from a JavaScript file with a namespace import. Then, you can use the exports of the CSS module when rendering elements in JSX.

Button.js:
import * as classes from './Button.module.css';

export function Button({ children }) {
return (
<button className={classes.button}>
{children}
</button>
);
}
Button.module.css:
.button {
background: hotpink;
}

See CSS modules to learn more about how Parcel handles CSS modules.

CSS-in-JS

#

CSS-in-JS libraries like Styled Components, Emotion, and many others work well with Parcel. Some may require build configuration, such as a Babel plugin. To enable it, install @parcel/transformer-babel, add it to your .parcelrc, and create a Babel configuration in your project.

For example, to use Emotion, install the Babel plugin, add @parcel/transformer-babel to your .parcelrc, and create a .babelrc in your project:

yarn add @parcel/transformer-babel@next @emotion/babel-plugin --dev
yarn add @emotion/react
.parcelrc:
{
"extends": "@parcel/config-default",
"transformers": {
"*.{js,mjs,jsx,ts,tsx}": ["@parcel/transformer-babel", "..."]
}
}
.babelrc:
{
"plugins": ["@emotion/babel-plugin"]
}

You’ll also need to set the jsxImportSource option in a tsconfig.json or jsconfig.json so that Emotion's JSX pragma is used instead of the default one. This enables the css prop to work.

jsconfig.json:
{
"compilerOptions": {
"jsxImportSource": "@emotion/react"
}
}

Now, you can render elements with CSS-in-JS:

Button.js:
import { css } from "@emotion/react";

export function Button({ children }) {
return (
<button
css={css`
background: hotpink;
&:hover {
background: purple;
}
`
}

>

{children}
</button>
);
}

Tailwind CSS

#

Tailwind CSS is a popular utility-first CSS framework. Parcel includes a built-in Tailwind plugin, @parcel/transformer-tailwind, which builds a CSS file containing only the classes you use in your code. The following works with Tailwind CSS v4.

To use it, first, install Tailwind:

yarn add tailwindcss --dev

Next, add the Tailwind plugin to your .parcelrc, and import Tailwind from your CSS file.

.parcelrc:
{
"extends": "@parcel/config-default",
"transformers": {
"*.css": ["@parcel/transformer-tailwind", "..."]
}
}
styles.css:
@import "tailwindcss";

Finally, you can reference Tailwind classes from any files scanned by Tailwind:

Button.js:
export function Button({ children }) {
return (
<button className="p-2 rounded bg-blue-500 hover:bg-blue-600 transition">
{children}
</button>
);
}

Images

#

You can reference external images from JSX using the URL constructor. Parcel also supports using query parameters to resize and convert images to a different format. It also handles image optimization, and includes a content hash in output filenames for long term browser caching.

Logo.js:
const logo = new URL('logo.svg', import.meta.url);

export function Logo() {
return <img src={logo} alt="logo" />;
}

See URL dependencies in the JavaScript docs for more details about this syntax, and the Image docs for more information about how Parcel handles images.

SVG

#

External SVG files can be referenced as described above. You can also import SVGs as React components which can be rendered directly in JSX.

First, add the @parcel/transformer-svg-jsx plugin to your .parcelrc. This plugin is included with Parcel, so it does not need to be installed.

.parcelrc:
{
"extends": "@parcel/config-default",
"transformers": {
"*.svg": ["@parcel/transformer-svg-jsx"]
}
}

Now, you can import SVGs from your component files and render them just like any other component.

AddButton.js:
import AddIcon from "./AddIcon.svg";

export function AddButton() {
return (
<button aria-label="Add">
<AddIcon />
</button>
);
}

The above example showed how to convert every SVG file to JSX, but you may want to be more selective in some cases. See Importing as a React component in the SVG docs for more details.

See the SVG docs for more about how Parcel transforms and optimizes SVG files.

Code splitting

#

Code splitting helps reduce initial page load size by lazily loading sections of your app. This can be accomplished by using the dynamic import() syntax, along with React.lazy.

This example lazily loads a Profile component when a user clicks a button. When it sees the dynamic import(), Parcel moves the Profile component into a separate bundle from the Home component and loads it on demand. React.lazy handles turning this into a component, and Suspense handles rendering a fallback while it is loading.

Home.js:
import React, { Suspense } from "react";

const Profile = React.lazy(() => import("./Profile"));

export function Home() {
let [showProfile, setShowProfile] = React.useState(false);

return (
<main>
<h1>Home</h1>
<button onClick={() => setShowProfile(true)}>Show Profile</button>
{showProfile && (
<Suspense fallback={<div>Loading...</div>}>
<Profile />
</Suspense>
)}
</main>
);
}
Profile.js:
export default function Profile() {
return <h2>Profile</h2>;
}

See the Code Splitting docs for more details about code splitting in Parcel, and Code Splitting in the React docs for more about Suspense and React.lazy.