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.
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.com;
root /var/www/myapp/dist;
index index.html;
# Cache static assets (they have content hashes)
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Handle client-side routing
location / {
try_files $uri $uri/ /index.html;
}
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
}
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 try_files directive in Nginx (or equivalent) sends 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>
);
}