Title here
Summary here
# Install dependencies
npm install
# Build for production
npm run build
# Output directory: dist/
# .env.production
VITE_API_URL=https://api.haddock.example.com
VITE_WS_URL=wss://api.haddock.example.com/events
VITE_GITHUB_CLIENT_ID=your_github_client_id
// public/config.json
{
"apiUrl": "https://api.haddock.example.com",
"wsUrl": "wss://api.haddock.example.com/events",
"githubClientId": "your_github_client_id"
}
# Stage 1: Build
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Serve
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
version: '3.8'
services:
frontend:
build:
context: .
dockerfile: Dockerfile
ports:
- "80:80"
environment:
- NODE_ENV=production
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Enable gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
# SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location /assets/ {
expires 1y;
add_header Cache-Control "public, no-transform";
}
}
name: Deploy Frontend
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
// Add more chunks as needed
}
}
}
}
})
// src/main.tsx
if (import.meta.env.PROD) {
setupErrorTracking({
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: 'production'
})
}
// src/app.tsx
import { web-vitals } from 'web-vitals'
web-vitals.report(console.log)
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self';" always;
// CSP nonce generation for inline scripts
const nonce = crypto.randomBytes(16).toString('base64')
document.querySelector('script')?.setAttribute('nonce', nonce)