Build and serve a React frontend on your server. Handle routing, environment variables, and production optimization.
There are two main ways to deploy React:
For most apps, a static build with client-side routing is simpler and faster. Use SSR (Next.js) only if you need SEO for dynamic content.
Start with a new React project or use your existing one.
# Create new Vite React project
npm create vite@latest my-app -- --template react-ts
cd my-app
# Install dependencies
npm install
npm install react-router-dom
# Install Tailwind (optional)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Create an optimized build for deployment.
# Build the app
npm run build
# Output will be in 'dist' folder (Vite) or 'build' folder (CRA)
# This creates optimized, minified JS/CSS/HTML files
# Preview the build locally
npm run preview
Configure your server to serve the built React files.
# .htaccess in your dist folder (Apache)
RewriteEngine On
# Cache static assets (they have content hashes)
<FilesMatch "\.(js|css|png|jpg|gif|svg|ico|woff2?)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# Handle client-side routing
# If the requested file doesn't exist, serve index.html
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
React Router handles navigation in the browser. The server must be configured to support this.
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Navigation } from './components/Navigation';
import { Home, About, Products, ProductDetail, NotFound } from './pages';
function App() {
return (
<BrowserRouter>
<Navigation />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
Without proper server configuration, refreshing on a route like /about will return 404. The .htaccess rewrite rules (shown above) send all requests to index.html, letting React Router handle them.
Configure different settings for development and production.
# .env.development
VITE_API_URL=http://localhost:3001
VITE_APP_NAME=MyApp (Dev)
# .env.production
VITE_API_URL=https://api.myapp.com
VITE_APP_NAME=MyApp
// Usage in React code
const apiUrl = import.meta.env.VITE_API_URL;
const appName = import.meta.env.VITE_APP_NAME;
// Fetch from API
fetch(`${apiUrl}/products`)
.then(res => res.json())
.then(data => console.log(data));
Fetch data from your backend API.
// services/api.ts
const API_URL = import.meta.env.VITE_API_URL;
async function request(endpoint: string, options: RequestInit = {}) {
const token = localStorage.getItem('token');
const response = await fetch(`${API_URL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options.headers,
},
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
export const api = {
getProducts: () => request('/products'),
getProduct: (id: string) => request(`/products/${id}`),
createProduct: (data: Product) => request('/products', {
method: 'POST',
body: JSON.stringify(data),
}),
};
Make your React app fast for users.
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Lazy load pages
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Products = lazy(() => import('./pages/Products'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div className="loading">Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Products />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}