Overview
Deploying a frontend application involves more than just uploading files. This guide covers build optimization, hosting platforms, CI/CD pipelines, and production best practices.Build Optimization
Configure Production Build
package.json
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"build:analyze": "vite-bundle-visualizer"
}
}
Environment Variables
.env.production
VITE_API_URL=https://api.production.com
VITE_APP_ENV=production
VITE_ENABLE_ANALYTICS=true
VITE_SENTRY_DSN=your-sentry-dsn
config.ts
export const config = {
apiUrl: import.meta.env.VITE_API_URL,
environment: import.meta.env.VITE_APP_ENV,
enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
sentryDsn: import.meta.env.VITE_SENTRY_DSN,
};
Build Configuration
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { compression } from 'vite-plugin-compression';
export default defineConfig({
plugins: [
react(),
compression({
algorithm: 'gzip',
ext: '.gz',
}),
],
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
chunkSizeWarningLimit: 1000,
sourcemap: true,
},
});
Use code splitting and lazy loading to reduce initial bundle size. Load routes and heavy components on demand.
Code Splitting
routes.tsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Lazy load route components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
function LoadingFallback() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" />
</div>
);
}
export function AppRoutes() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />} />
<Route path="/admin" element={<AdminPanel />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Hosting Platforms
- Vercel
- Netlify
- AWS S3 + CloudFront
- Docker
Perfect for Next.js and React applications with zero configuration.Deploy:
vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"devCommand": "npm run dev",
"installCommand": "npm install",
"framework": "vite",
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
],
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "X-XSS-Protection",
"value": "1; mode=block"
}
]
},
{
"source": "/static/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}
npm i -g vercel
vercel --prod
Great for static sites with built-in CI/CD and edge functions.Deploy:
netlify.toml
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
[[headers]]
for = "/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
npm i -g netlify-cli
netlify deploy --prod
Scalable solution with full control over infrastructure.S3 Bucket Policy:
buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
nodejs: 18
pre_build:
commands:
- npm install
build:
commands:
- npm run build
artifacts:
files:
- '**/*'
base-directory: dist
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
Containerized deployment for any platform.Build and Run:
Dockerfile
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /static {
expires 1y;
add_header Cache-Control "public, immutable";
}
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}
docker build -t my-app .
docker run -p 80:80 my-app
CI/CD Pipeline
name: Deploy to Production
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run type check
run: npm run type-check
- name: Run tests
run: npm run test:ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: dist
path: dist/
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Always run tests and type checking in your CI pipeline before deploying. Catch errors early in the development process.
Performance Optimization
Asset Optimization
ImageOptimization.tsx
// Use modern image formats
export function OptimizedImage({ src, alt }: { src: string; alt: string }) {
return (
<picture>
<source srcSet={`${src}.webp`} type="image/webp" />
<source srcSet={`${src}.jpg`} type="image/jpeg" />
<img
src={`${src}.jpg`}
alt={alt}
loading="lazy"
decoding="async"
className="w-full h-auto"
/>
</picture>
);
}
// Preload critical assets
export function PreloadCriticalAssets() {
return (
<>
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
<link rel="preload" href="/critical.css" as="style" />
<link rel="preconnect" href="https://api.yourapp.com" />
</>
);
}
Caching Strategy
cache-config.ts
// Service Worker for offline support
export function registerServiceWorker() {
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/sw.js')
.then(registration => {
console.log('SW registered:', registration);
})
.catch(error => {
console.log('SW registration failed:', error);
});
});
}
}
public/sw.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/static/css/main.css',
'/static/js/main.js',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => response || fetch(event.request))
);
});
Monitoring and Error Tracking
monitoring.ts
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
export function initMonitoring() {
if (import.meta.env.PROD) {
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
new BrowserTracing(),
new Sentry.Replay({
maskAllText: true,
blockAllMedia: true,
}),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
environment: import.meta.env.VITE_APP_ENV,
beforeSend(event, hint) {
// Filter out non-critical errors
if (event.level === 'warning') {
return null;
}
return event;
},
});
}
}
// Analytics
export function trackPageView(page: string) {
if (window.gtag) {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: page,
});
}
}
export function trackEvent(eventName: string, parameters?: Record<string, any>) {
if (window.gtag) {
window.gtag('event', eventName, parameters);
}
}
Security Headers
security-headers.ts
export const securityHeaders = {
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
].join('; '),
};
Never commit API keys, tokens, or sensitive credentials to version control. Use environment variables and secret management services.
Production Checklist
- Environment variables configured correctly
- Source maps enabled for error tracking
- Analytics and monitoring set up
- Error tracking (Sentry) configured
- Security headers implemented
- HTTPS enabled
- Compression (gzip/brotli) enabled
- Assets optimized (images, fonts)
- Code splitting and lazy loading implemented
- Cache headers configured
- Service worker for offline support (if needed)
- SEO meta tags added
- Favicon and app icons included
- robots.txt and sitemap.xml created
- Performance testing completed
- Accessibility audit passed
- Browser compatibility tested
- Mobile responsiveness verified
- Load testing performed
- Backup and rollback strategy defined
Rollback Strategy
rollback.sh
#!/bin/bash
# Rollback to previous deployment
echo "Rolling back to previous version..."
# Vercel
vercel rollback
# Netlify
netlify rollback
# Docker
docker tag my-app:previous my-app:latest
docker push my-app:latest
# AWS S3
aws s3 sync s3://backup-bucket/previous-build s3://production-bucket --delete
echo "Rollback complete"
Next Steps
- Optimize Data Fetching for production
- Review Authentication security practices
- Continue improving with Building Components