Skip to main content

Environment Setup — Vite + React Project Configuration

Why Choose Vite?

Until 2021, Create React App (CRA) was the official scaffolding tool. However, CRA used Webpack internally, causing the development server to take tens of seconds to start. The current React official documentation recommends Vite, Next.js, Remix, and others instead of CRA.

CRA vs Vite Comparison

FeatureCRAVite
BundlerWebpackesbuild (dev) + Rollup (build)
Cold start20–60 seconds1–3 seconds
HMR speedSlowInstant
MaintenanceEffectively discontinuedActively developed
Bundle sizeLargerSmaller

Creating a Vite + React Project

Basic Setup

npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

TypeScript Template

npm create vite@latest my-react-app -- --template react-ts

Interactive Mode

npm create vite@latest
# Follow the prompts:
# ✔ Project name: my-app
# ✔ Select a framework: React
# ✔ Select a variant: JavaScript / TypeScript / JavaScript + SWC / TypeScript + SWC

SWC (Speedy Web Compiler) is an ultra-fast Babel replacement written in Rust. If performance matters, choose the react-swc variant.


Project Folder Structure

my-react-app/
├── public/ # Static files (copied as-is)
│ └── vite.svg
├── src/ # Source code
│ ├── assets/ # Images, fonts, etc.
│ ├── components/ # Reusable components
│ ├── pages/ # Page components (when using a router)
│ ├── hooks/ # Custom hooks
│ ├── App.jsx # Root component
│ ├── App.css
│ ├── main.jsx # Entry point
│ └── index.css
├── index.html # Vite's entry HTML
├── vite.config.js # Vite configuration
├── package.json
└── .eslintrc.cjs # ESLint configuration

main.jsx — Entry Point

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>,
);

App.jsx — Root Component

import { useState } from 'react';
import reactLogo from './assets/react.svg';
import './App.css';

function App() {
const [count, setCount] = useState(0);

return (
<div>
<h1>Vite + React</h1>
<button onClick={() => setCount(count + 1)}>
count is {count}
</button>
</div>
);
}

export default App;

Development Commands

npm run dev      # Start development server (default port: 5173)
npm run build # Production build (generates dist/ folder)
npm run preview # Preview build output locally
npm run lint # Run ESLint

Changing the Port

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
plugins: [react()],
server: {
port: 3000, // Development server port
open: true, // Automatically open browser
},
});

Path Alias Configuration

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@hooks': path.resolve(__dirname, './src/hooks'),
},
},
});

You can then use absolute paths for imports.

// Before
import Button from '../../components/Button';

// After
import Button from '@components/Button';

ESLint + Prettier Setup

Installation

npm install -D eslint-config-prettier prettier
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin # For TypeScript

.eslintrc.cjs

module.exports = {
root: true,
env: { browser: true, es2024: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier', // Disable rules that conflict with Prettier
],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
plugins: ['react-refresh'],
rules: {
'react/react-in-jsx-scope': 'off', // React 17+ automatic JSX Transform
'react-refresh/only-export-components': 'warn',
},
};

.prettierrc

{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}

Installing React DevTools

Install as a browser extension.

Key Features

TabPurpose
ComponentsBrowse component tree, inspect and edit props/state
ProfilerMeasure rendering performance, see which components rendered and why

DevTools Tips

// Set displayName on a component (useful for anonymous functions)
const MemoizedCard = React.memo(function Card({ title }) {
return <div>{title}</div>;
});
MemoizedCard.displayName = 'MemoizedCard';

src/
├── features/
│ ├── auth/
│ │ ├── components/ LoginForm.jsx, RegisterForm.jsx
│ │ ├── hooks/ useAuth.js
│ │ ├── api.js # Auth-related API calls
│ │ └── index.js # Public API export
│ └── products/
│ ├── components/ ProductCard.jsx, ProductList.jsx
│ ├── hooks/ useProducts.js
│ └── index.js
├── shared/
│ ├── components/ Button.jsx, Modal.jsx, Input.jsx
│ ├── hooks/ useDebounce.js, useLocalStorage.js
│ └── utils/ formatDate.js, validators.js
├── App.jsx
└── main.jsx

Pro Tips

1. Vite Environment Variables

# .env
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
// Using in a component
const apiUrl = import.meta.env.VITE_API_URL;

Only variables prefixed with VITE_ are exposed to the client.

2. Export Rules for HMR

// ✅ One named export or default export per component
export default function Counter() { /* ... */ }

// ❌ Exporting a component and other values from the same file causes HMR warnings
export const CONSTANT = 42;
export default function Counter() { /* ... */ }

3. Checking Build Output

npm run build
# Check file sizes in the dist/ folder
# index-[hash].js: app code
# vendor-[hash].js: node_modules code (split into chunks)

You can use the vite-bundle-visualizer package to visually analyze the bundle composition.