Setting Up Your React Dev Environment — Your First App with Vite, and Turning On React Compiler
Series: Restarting React — a beginner series for getting a solid grip on the React of 2026 (19.2 + React Compiler), from the ground up. Every example is written in TypeScript.
The React this series covers is probably a little different from the tutorials you’ve seen before. You’ll rarely see code that manually adds useMemo or useCallback, and I won’t explain why until the very last chapter. That’s because we start with React Compiler turned on from day one, and the compiler handles those optimizations for us automatically.
So the goal of this first chapter isn’t just “environment setup.” It’s to build the project template we’ll reuse in every chapter, with the right tools from the very start. Set it up well once, and every chapter after this just swaps in new examples on top of it.
Before we dive in, let’s take a moment to look at why React came to exist. When you understand the problem a tool was built to solve, every concept that follows clicks into place much more easily.
What Problem Was React Built to Solve?
Before React, making a web page dynamic meant manipulating the DOM by hand. Think back to the jQuery era: “find the element with id count, bump the number inside it by one, and hide the badge next to it at the same time…"
// The old way — you manually keep the screen in sync every time the data changes
const badge = document.querySelector('#unread-badge');
badge.textContent = count;
if (count === 0) {
badge.style.display = 'none';
} else {
badge.style.display = 'block';
}On a small screen that’s fine. The trouble starts as the app grows. The places where data (state) changes and the code that reflects those changes on screen end up scattered all over. Before long you get bugs where the data and the screen fall out of sync. A story often cited as the backdrop for React’s birth is Facebook’s “unread message count” not matching the actual messages. Miss a single screen-update line after changing the data, and the UI immediately starts lying.
The root cause is this: the developer had to spell out “how” to change things, one step at a time. This style is called imperative. For every path where state might change, you have to personally handle how the DOM gets updated, and the more paths there are, the easier it is to miss one.
React flipped the idea on its head. You just declare “when the state is this, the screen should look like this,” and React works out how to actually change the DOM. This style is called declarative.
// The React way — you simply declare "for this state, the screen looks like this"
function UnreadBadge({ count }: { count: number }) {
if (count === 0) return null;
return <span className="badge">{count}</span>;
}When count changes, React re-runs this function to compute the new screen, compares it against the previous one, and applies only what changed to the DOM. The developer never issues commands like “delete this, change that.” You just write “in this state, it looks like this.” The odds of the screen and the data drifting apart shrink structurally.
From this single idea, React’s core traits follow:
- Components — split the UI into independent, reusable pieces, like
UnreadBadgeabove. - Declarative rendering — a function that turns state into a screen. When the state changes, it re-renders.
- One-way data flow — data flows only from top to bottom, which makes it easy to trace where a value came from.
The thread that runs through this whole series: with React, what we have to worry about shifts from “how do I manipulate the DOM?” to “how do I design my state?” That’s why this series leans on the mental model of state and data flow rather than memorizing syntax. (React Compiler taking performance optimization off our hands is part of the same theme — it frees us to focus on state design.)
Alright, now that you have a feel for why we’re learning this, it’s time to choose our tools.
What We’ll Use, and What We Won’t
Let’s settle the tooling first, because this is where beginners hit their very first trap.
Old React tutorials almost all start with CRA (create-react-app). Don’t use it anymore. CRA is no longer recommended and has been dropped from the official React docs. If you find a guide that starts with npx create-react-app, take it as a sign the material is outdated.
Here’s the combination we’ll use:
- Vite — a fast build tool. The dev server starts instantly, and your changes appear on screen the moment you save.
- TypeScript — we use types from the very beginning. Not “add it later,” but from the first line. Once props and state carry types, you actually make fewer mistakes as a beginner.
- React Compiler — a build-time tool that automates memoization. It’s the premise of this whole series.
- pnpm — the package manager. npm or yarn work just as well; only the commands change, and you can follow along either way.
Prerequisite — Node.js
You need Node.js installed. Check the version in your terminal.
node -vv20 or higher is enough (ideally the latest LTS). If nothing prints or the number is too low, install the LTS version from nodejs.org.
Now let’s get pnpm ready. The method depends on your Node version.
Node 24 or lower — Corepack ships inside Node, so a single line does it.
corepack enableNode 25 or higher — Corepack was removed as of Node.js 25. In that case, install pnpm directly with npm.
npm install -g pnpmEither way, you end up able to run the
pnpmcommand. The difference: Corepack automatically matches the pnpm version pinned in your project'spackage.json, whereas a global install (-g) sticks with whatever version you installed. As a beginner, just use whichever one works on your Node version. If you're unsure, try the commands above in order — ifcorepack enablereturns "command not found," usenpm install -g pnpminstead.If you’d rather stick with npm, you can skip installing pnpm altogether. Just replace
pnpmwithnpmin every command in this article.⚠️ Don’t mix the two methods. If you run both Corepack and
npm install -g pnpm, you can end up with two conflicting versions of pnpm. If you later hit anERR_PNPM_UNEXPECTED_STORE(store location mismatch) error when installing packages, this conflict is the cause. Check your version withpnpm -v, unify on a single latest version withcorepack prepare pnpm@latest --activate(ornpm install -g pnpm@latest), and try again. If it still fails, do a clean reinstall:rm -rf node_modules pnpm-lock.yamlfollowed bypnpm install.
Step 1 — Create the Project
Create a new project with Vite.
pnpm create vite@latest restart-react --template react-tsrestart-react— the project (folder) name. Change it to anything you like.--template react-ts— the React + TypeScript template. Be sure to pick the one withts. (Plainreactgives you the JavaScript version.)
If you type just pnpm create vite@latest without the arguments, it will ask you for the framework and language interactively. In that case, choose React → TypeScript.
Get Inkweon Kim’s stories in your inbox
Join Medium for free to get updates from this writer.
Once it’s created, follow the prompts: move into the folder, install dependencies, and start the dev server.
cd restart-react
pnpm install
pnpm devWhen an address like http://localhost:5173 appears in the terminal, open it in your browser. If you see the default Vite + React screen, you're all set.
Step 2 — Reading the Folder Structure
It looks like a lot of files at first, but there are only a few you need to care about as a beginner.
restart-react/
├── index.html ← the single HTML file the app mounts into
├── package.json ← dependencies and scripts
├── tsconfig.json ← TypeScript config
├── vite.config.ts ← Vite config (we'll edit this shortly)
└── src/
├── main.tsx ← the app's entry point
├── App.tsx ← the top-level component
├── App.css
└── index.cssThe rest (public/, asset files, and so on) you can ignore for now. The key points: your entire React app mounts into a single index.html, and its starting point is src/main.tsx.
Step 3 — Dissecting the Entry Point
Open src/main.tsx. It looks roughly like this.
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)Reading it line by line reveals the whole process by which React ends up on your screen.
document.getElementById('root')— finds the<div id="root">insideindex.html. React draws the entire app inside this one box. (The!tells TypeScript "this element definitely exists.")createRoot(...).render(...)— creates a React root in that box and renders<App />inside it.<StrictMode>— a check mode that only runs during development.
What Is StrictMode?
This is the first question beginners ask: “I clearly set this up to run once — so why is it running twice?”
StrictMode deliberately renders your components twice in development mode. It looks like a bug, but it's intentional. React expects components to be "pure" (same input, same output), so it runs them twice to surface any case where the result differs or an odd side effect sneaks in. We'll cover this purity idea in earnest in Part 1. For now, just remember: double execution in development is normal, and the production build runs it only once.
Open src/App.tsx as well. This is the top-level component whose contents we'll fill in from here on. It's a function that starts with a capital letter and returns JSX — we'll dig into exactly what that is in the next chapter (Your First Component).
Step 4 — Turning On React Compiler (The Heart of This Series)
This is the most important part of the chapter. In Vite’s default template, React Compiler is off. You have to turn it on yourself.
First, install the compiler’s Babel plugin and the package that wires it into Vite.
pnpm install -D babel-plugin-react-compiler@latest @rolldown/plugin-babelThen open vite.config.ts. By default it looks roughly like this.
// vite.config.ts (before)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});Add the compiler preset to it.
// vite.config.ts (after)
import { defineConfig } from 'vite';
import react, { reactCompilerPreset } from '@vitejs/plugin-react';
import babel from '@rolldown/plugin-babel';
export default defineConfig({
plugins: [
react(),
babel({
presets: [reactCompilerPreset()],
}),
],
});This is the currently recommended approach (@vitejs/plugin-react 6.0.0 and up). The official docs pointed to the old setup (react({ babel: { plugins: [...] } })) for a while, which caused some confusion, but that inline approach was removed in 6.0.0. Even if you come across the old way online, use the approach above.
Version note (as of 2026–06): React Compiler works best with React 19. React 19 already includes the runtime the compiler needs, so it works out of the box with no separate runtime package. (React 17 and 18 require extra setup.)
Once you’ve changed the config, restart the dev server (Ctrl+C, then pnpm dev). Changes to the Vite config require a restart.
Step 5 — Confirming the Compiler Is On
There’s a way to verify it’s really working with your own eyes. Install the React Developer Tools browser extension, open your app in development mode, and look at the Components tab in React DevTools.
Components the compiler has optimized get a “Memo ✨” badge next to their name. If you see this ✨, it’s a sign the compiler is doing its job — even though we never wrote a single line of useMemo.
Wrapping Up
- Start new React projects with the Vite + TypeScript template (
--template react-ts), not CRA. - The app starts from a single
<div id="root">inindex.html, drawn in the ordersrc/main.tsx→src/App.tsx. - StrictMode’s double execution in development is normal — an intentional behavior for the purity check.
- You have to turn React Compiler on yourself. Install
babel-plugin-react-compiler+@rolldown/plugin-babeland addreactCompilerPreset()tovite.config.ts. - Confirm the compiler is working via the Memo ✨ badge in DevTools.
Now our workbench — the one we’ll reuse in every chapter — is ready. In the next chapter, we’ll build our first component on top of it and unpack exactly what that “capital-letter function returning JSX” in App.tsx really is.
References
- React Compiler — Installation — official React docs (checked: 2026–06–14)
- React Compiler v1.0 — official React blog, 2025–10–07
- Start a New React Project — official React docs (recommends Vite)
- @vitejs/plugin-react — npm (reactCompilerPreset)
Thousands of developers share what they’re building, learning, and discovering across our publications every month. One account connects you to our entire network of publications and communities. Explore more at plainenglish.io.











