Working seriously with React requires a small ecosystem of tools: a JavaScript runtime outside the browser (Node.js), a package manager (npm), a build tool that transforms your code and gives you a fast development server (Vite), a properly configured editor, and a browser extension for inspecting components. In this lesson you'll set up that environment from start to finish and create the CicloUrbano project you'll use throughout the course. By the end you'll have an app running in your browser and you'll understand what every generated file does.
Contents
- Prerequisites: Node.js and npm
- The editor and its extensions
- Creating the CicloUrbano project with Vite
- A tour of the folder structure
- The npm scripts:
dev,build,preview - Hot Module Replacement
- React DevTools in the browser
- Why Vite and not Create React App
- Prerequisites: Node.js and npm
React is written with a syntax (JSX) that browsers don't understand directly, and it's distributed in packages you need to download. Both of those require Node.js, the runtime that lets you execute JavaScript outside the browser, and npm (Node Package Manager), which installs alongside Node.
Which version to install
Always use an LTS (Long Term Support) version: these are the even-numbered releases (20, 22, 24...) with extended support, and the ones tooling treats as safe. Vite requires Node 20.19 or later. Download it from nodejs.org, or better yet, install a version manager like nvm (macOS/Linux) or fnm (cross-platform), which lets you keep several versions and switch between them per project.
Checking that everything's in place
Open a terminal and run:
Expected output (the exact numbers will vary, but this is the format):
If the command isn't recognized, Node isn't installed or isn't on the PATH; restart your terminal after installing it. If your Node version is below 20, update it before continuing — otherwise you'll run into confusing errors later on.
A note on package managers
| Manager | Install command | Notes |
|---|---|---|
| npm | npm install |
Comes with Node. It's what we'll use throughout the course |
| pnpm | pnpm install |
Faster, and saves disk space by sharing dependencies across projects |
| yarn | yarn |
A long-standing alternative, still widely used in existing projects |
All three solve the same problem, and the commands are nearly interchangeable. We'll use npm because it doesn't require installing anything extra.
- The editor and its extensions
The recommended editor is Visual Studio Code, free and with the best support for React. These extensions make a real difference day to day:
| Extension | What it's for |
|---|---|
| ESLint | Flags errors and bad practices as you type (misused hooks, unused variables, missing key) |
| Prettier | Formats your code automatically on save — no more arguing about quotes and indentation |
| ES7+ React/Redux/React-Native snippets | Shortcuts like rafce that generate the skeleton of a function component |
| Auto Rename Tag | Renaming an opening JSX tag automatically renames its closing tag |
| Error Lens | Shows the error message right on the line, without hovering over it |
One setting well worth turning on is format-on-save. In VS Code, Ctrl+, → search for "format on save" → check the box. Or set it directly in .vscode/settings.json inside the project:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
- Creating the CicloUrbano project with Vite
Vite (pronounced "veet," French for "fast") is today's standard build tool for React projects. It does two things: during development it spins up an almost-instant server that serves your code, and for production it generates the optimized files you'll upload to your host.
Go to the folder where you keep your projects and run:
Let's break that command down, because the double dashes trip a lot of people up:
npm create vite@latestdownloads and runs Vite's scaffolding wizard at its latest version, without installing it permanently.ciclourbanois the name of the project folder.- The
--separates npm's arguments from the wizard's arguments. Without it, npm would try to interpret--templateas one of its own options. --template reactselects the React template with JavaScript. (If you wanted TypeScript, it would bereact-ts— you'll see that in lesson 10-04, but this course uses JavaScript.)
Expected output:
Scaffolding project in /home/your-username/projects/ciclourbano... Done. Now run: cd ciclourbano npm install npm run dev
Follow those three commands:
npm install reads package.json, downloads the dependencies into the node_modules/ folder, and creates package-lock.json. It takes anywhere from a few seconds to a minute, depending on your connection:
Then start the development server:
VITE v7.0.0 ready in 187 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h + enter to show help
Open http://localhost:5173/ in your browser: you'll see the Vite + React welcome page with a counter. Leave this process running in its terminal while you work; to stop it, press Ctrl+C.
If port 5173 is already taken, Vite will use 5174, 5175, and so on. Always check the URL the console prints.
- A tour of the folder structure
Here's what Vite has generated:
ciclourbano/ ├── node_modules/ <- downloaded dependencies (never touch this, never commit it to git) ├── public/ <- static files served as-is │ └── vite.svg ├── src/ <- YOUR code: this is where you'll always work │ ├── assets/ │ │ └── react.svg │ ├── App.css │ ├── App.jsx <- the app's root component │ ├── index.css <- global styles │ └── main.jsx <- JavaScript entry point ├── .gitignore ├── eslint.config.js ├── index.html <- the actual HTML page the browser serves ├── package.json ├── package-lock.json ├── README.md └── vite.config.js
index.html: the real starting point
In a Vite project, the HTML isn't hidden away inside the configuration — it's a top-level file, and it's the real entry point.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>Two lines matter most:
<div id="root"></div>is the empty container where React will mount the whole application. Everything you'll see on screen ends up inside thatdiv.<script type="module" src="/src/main.jsx">loads your code as an ES module. It's the thread that connects the HTML to React.
Since it's your project, go ahead and personalize it now:
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CicloUrbano — Urban Bike Rental</title>
</head>src/main.jsx: where React starts
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>
);This is the file that connects React to the div#root in the HTML. We'll walk through it line by line in the next lesson; for now, just remember that this is where everything starts.
The rest of the pieces
| File / folder | What it's for |
|---|---|
src/App.jsx |
Root component. The entire CicloUrbano component tree hangs off it |
src/index.css |
Global styles: base typography, colors, margin reset |
src/App.css |
Styles for the App component (you'll see better strategies for this in Module 2) |
src/assets/ |
Images and resources you import from your code; Vite optimizes them and adds a hash when it builds |
public/ |
Resources served as-is, unprocessed, at the site root: public/logo.png is served as /logo.png. Use it for favicon.ico, robots.txt, or downloads |
package.json |
Project name, dependencies and runnable scripts |
package-lock.json |
Exact installed versions. Committed to git so the whole team installs the same thing |
vite.config.js |
Vite configuration (plugins, aliases, port, proxy) |
eslint.config.js |
Static code analysis rules |
.gitignore |
List of what git should ignore; includes node_modules/ and dist/ |
node_modules/ |
Downloaded dependencies. Huge, regenerated with npm install, and never committed to git |
The key difference between src/assets/ and public/: what's in assets goes through the build process (it's optimized, renamed with a hash for good caching, and removed if it's unused); what's in public is copied untouched and its name never changes.
vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()]
});It's minimal on purpose. The @vitejs/plugin-react plugin is what teaches Vite how to transform JSX and what enables hot component reloading. If you need a path alias or a proxy to an API later on, this is where you configure it.
How the pieces fit together
flowchart TD
A[Browser requests index.html] --> B["index.html contains div#root<br/>and loads /src/main.jsx"]
B --> C[main.jsx: createRoot + render]
C --> D[App.jsx: root component]
D --> E[CicloUrbano components<br/>src/components/]
F[Vite: development server] -.transforms JSX on the fly.-> B
- The npm scripts:
dev, build, preview
dev, build, previewOpen package.json yourself:
{
"name": "ciclourbano",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.4.0",
"eslint": "^9.25.0",
"vite": "^7.0.0"
}
}| Script | Command | What it does | When you use it |
|---|---|---|---|
dev |
npm run dev |
Starts the development server with hot reload at localhost:5173 |
The whole time you're coding |
build |
npm run build |
Generates the optimized production build in dist/ |
Before deploying |
preview |
npm run preview |
Serves what's in dist/ locally so you can check it |
After build, to validate before deploying |
lint |
npm run lint |
Scans the code for errors and bad practices | Before committing changes to git |
Try the full cycle now:
vite v7.0.0 building for production... ✓ 34 modules transformed. dist/index.html 0.46 kB │ gzip: 0.30 kB dist/assets/index-DiwrgTda.css 1.39 kB │ gzip: 0.72 kB dist/assets/index-C9n2Xk4p.js 143.41 kB │ gzip: 46.12 kB ✓ built in 612 ms
Notice two important details: preview uses a different port (4173) so you don't confuse it with the dev server, and the files in dist/ carry a hash in their name (index-C9n2Xk4p.js). That hash changes whenever the content changes, which lets the browser cache aggressively without ever serving a stale version.
On dependencies versus devDependencies: the former end up in the bundle that reaches the browser (react, react-dom); the latter are only used on your machine to build and analyze the code (vite, eslint). That's why the build output is far smaller than node_modules/.
- Hot Module Replacement
HMR (Hot Module Replacement) is the reason development with Vite feels so smooth. When you save a file, Vite doesn't reload the whole page — it sends only the changed module over a WebSocket, and React swaps it in live.
The practical difference is huge:
| Full reload | HMR | |
|---|---|---|
| Time until you see the change | 1-3 seconds | Tens of milliseconds |
| Application state | Lost — you're back to the start | Preserved |
| Scroll and focus | Reset | Kept |
The "state is preserved" part is what you'll appreciate the most: if you've half-filled a CicloUrbano booking form and you tweak a color in the CSS, you'll still see the form filled in.
Try it. With npm run dev running, open src/App.jsx, change any visible text, and save. The browser updates on its own, with no flicker. In the Vite console you'll see:
There are cases where HMR can't apply the change and reloads the whole page instead: editing vite.config.js, touching index.html, or restructuring a module in a way Vite can't reconcile. That's normal.
- React DevTools in the browser
React DevTools is the official extension that lets you inspect your application in React's own terms — components, props, state — instead of in terms of HTML nodes.
Installation
- Chrome / Edge: search for "React Developer Tools" in the Chrome Web Store and install it.
- Firefox: search for it on addons.mozilla.org.
- Safari or others: you can use the standalone version with
npx react-devtools.
After installing it, open http://localhost:5173, press F12, and you'll see two new tabs: Components and Profiler.
What the Components tab shows
- The component tree with real component names (
App,BikeList,BikeCard), instead of a tangle ofdivs. This is the main reason to install it. - Props and state of the selected component, in the right-hand panel, editable on the fly so you can test cases without touching the code.
- The parent component that renders it, and the full path up to the root.
- A selector (the arrow icon) to click an element on the page and jump straight to its component.
Check that it works: open Components and you'll see App in the tree, with StrictMode inside it. There's not much to see yet, but from lesson 01-03 onward this tab will be your main diagnostic tool.
The Profiler tab measures render performance. It's an excellent tool, but it requires understanding how React renders first; it's covered in the lesson Measuring Performance with React DevTools Profiler.
- Why Vite and not Create React App
For years, the standard way to create a React project was create-react-app (CRA). Today you shouldn't use it:
- It's discontinued. The official React documentation dropped it from its recommendations in 2023, and the package stopped being actively maintained. Installing it today shows warnings about outdated dependencies.
- It's slow. CRA uses webpack with Babel and bundles the entire application before it can serve anything: starting a medium-sized project could take 30 seconds or more, and every change took several seconds. Vite serves ES modules natively and uses esbuild (written in Go) for dependencies: it starts in under a second regardless of project size.
- It's opaque. Its configuration is hidden behind
react-scripts, and customizing it meant runningeject(an irreversible operation that dumps hundreds of lines of configuration into your repository) or resorting to external patches.vite.config.jsis five readable lines.
| Create React App | Vite | |
|---|---|---|
| Status | Discontinued | Active, de facto standard |
| Server startup | Tens of seconds | Under a second |
| Update after a change | Seconds | Milliseconds |
| Configuration | Hidden; irreversible eject |
Short, editable file |
| Production build | webpack | Rollup, with code splitting included |
Seeing CRA in an old tutorial is the best sign not to trust how current it is: if a tutorial uses create-react-app, it probably also uses class components and pre-hooks APIs. Treat it as historical context only.
Common Mistakes and Tips
- Running
npm run devoutside the project folder. This givesnpm error Missing script: "dev". Check withpwd(orls package.json) that you're insideciclourbano/. - Forgetting the
--in the creation command.npm create vite@latest ciclourbano --template reactdoesn't pass the template to the wizard, so it'll ask you interactively instead. It's not a serious error, but it's worth knowing why it happens. - Committing
node_modules/to git. It's tens of thousands of files. The.gitignoreVite generates already excludes it — don't delete that line. If someone clones the repository,npm installrebuilds it. - Editing files inside
node_modules/. Any change there disappears on the next install. If you need to modify a dependency, there are dedicated tools for it (patch-package), but that's almost never the right solution. - Running too old a version of Node. This is the number-one cause of baffling install or startup errors. Always check
node --versionbefore asking for help. - Closing the terminal running
npm run devand expecting the site to keep working. The development server is that process; kill it, andlocalhost:5173stops responding. - Mixing up the
devport and thepreviewport. 5173 serves your code in development, 4173 serves the contents ofdist/. If you edit something and don't see the change, check which port you're on. - Tip: add the project to git from day one (
git init,git add .,git commit -m "Initial CicloUrbano project"). Being able to roll back to a working point is priceless while you're learning.
Exercises
Exercise 1
Create the CicloUrbano project following the steps in this lesson, and then:
- Change the
<title>inindex.htmltoCicloUrbano — Urban Bike Rentaland thelangattribute toen. - With the development server running, change any text in
src/App.jsxand confirm the change appears without reloading the page. - Run the production build and serve it locally. Note which port it uses and the size of the generated JavaScript file.
Exercise 2
Classify each of these files or folders into one of three categories: (A) I edit it regularly, (B) it exists but I almost never touch it, (C) I never edit it by hand.
src/App.jsx · node_modules/ · package.json · package-lock.json · index.html · vite.config.js · src/components/ · dist/
Exercise 3
Place two images in the project: logo-ciclourbano.svg in public/ and urban-bike.svg in src/assets/. Explain what path you'd use to reference each one, and what happens to each file when you run npm run build.
Solutions
Solution 1.
Edit index.html:
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CicloUrbano — Urban Bike Rental</title>
</head>When you save src/App.jsx with different text, the browser updates within milliseconds without losing state (if you'd clicked the sample counter, it keeps its value) — that's HMR in action. The Vite console prints [vite] hmr update /src/App.jsx.
For the third point:
preview serves at http://localhost:4173/. The JavaScript file will be around 140-150 kB (about 45 kB gzip-compressed), which is essentially the weight of React and React DOM.
Solution 2.
| File / folder | Category | Reason |
|---|---|---|
src/App.jsx |
A | It's your root component; you touch it constantly |
src/components/ |
A | This is where all the CicloUrbano components will live |
package.json |
B | Changes when you add scripts; dependencies are managed by npm install |
index.html |
B | Title, language, favicon and metadata; barely touched afterward |
vite.config.js |
B | Only when you need an alias, a proxy, or a plugin |
node_modules/ |
C | Generated by npm; any change is lost |
package-lock.json |
C | Managed by npm; committed to git but never edited by hand |
dist/ |
C | Output of npm run build; regenerated, and not even committed to git |
Solution 3.
-
public/logo-ciclourbano.svgis served as-is from the site root. You reference it with an absolute path, and its name never changes:<img src="/logo-ciclourbano.svg" alt="CicloUrbano" />After
npm run buildit shows up atdist/logo-ciclourbano.svgwith the same name. It's the right choice when the path needs to be stable and predictable (favicon,robots.txt, images referenced from outside). -
src/assets/urban-bike.svgis imported from the code:import urbanBike from './assets/urban-bike.svg'; <img src={urbanBike} alt="Urban bike" />When it builds, Vite processes it, renames it with a hash (
dist/assets/urban-bike-B7kX2p1q.svg), and swaps in the reference. Advantages: optimal browser caching, a build-time error if the path is misspelled, and automatic removal if the file stops being used.
Conclusion
You now have a professional development environment: Node.js LTS and npm verified, an editor with ESLint and Prettier, the CicloUrbano project created with Vite, the development server running with hot reload, and React DevTools installed in your browser. You also know what every generated file does, how src/assets/ differs from public/, what each npm script is for, and why Vite has replaced Create React App.
So far you've only run someone else's code. In the next lesson, Hello World in React, you'll clean up the sample template, understand line by line how main.jsx mounts the application onto the DOM, and write your first two components of your own: Welcome and BikeCard.
React Course
Module 1: Getting Started with React
- What Is React?
- Setting Up the Development Environment
- Hello World in React
- JSX: A JavaScript Syntax Extension
- How React Renders: Virtual DOM and Reconciliation
Module 2: React Components
- Understanding Components
- Function vs Class Components
- Props: Passing Data to Components
- State: Managing Component State
- Styling Components: CSS, Modules and Utilities
Module 3: Working with Events
- Handling Events in React
- Conditional Rendering
- Lists and Keys
- Forms and Controlled Components
- Form Validation and Uncontrolled Components
- Accessibility in Interactive Components
Module 4: Advanced Component Concepts
- Lifting State Up
- Composition vs Inheritance
- React Lifecycle Methods
- Hooks: Introduction and Basic Use
- Error Boundaries: Catching Failures in the UI
Module 5: React Hooks
- The useState Hook
- The useEffect Hook
- The useRef Hook and DOM Access
- The useContext Hook
- The useReducer Hook
- Custom Hooks
Module 6: Routing in React
- Introducing React Router
- Setting Up React Router
- Nested Routes
- Programmatic Navigation
- Protected Routes and Access Control
Module 7: State Management
- Introduction to State Management
- The Context API
- Redux: Introduction and Setup
- Redux: Actions and Reducers
- Redux: Connecting to React
- Server State: Fetching, Caching and Syncing
Module 8: Performance Optimization
- Performance Optimization Techniques in React
- Memoization with React.memo
- The useMemo and useCallback Hooks
- Code Splitting and Lazy Loading
- Measuring Performance with React DevTools Profiler
Module 9: Testing React Applications
- Introduction to Testing
- Unit Testing with Jest
- Component Testing with React Testing Library
- Testing Asynchronous Code and Mocking APIs
- End-to-End Testing with Cypress
Module 10: Advanced Topics
- Server-Side Rendering (SSR) with Next.js
- Static Site Generation (SSG) with Next.js
- Suspense and React Server Components
- TypeScript with React
- React Native: Building Mobile Apps
