fix: Configure Vite proxy to eliminate CORS issues in development

Implemented a comprehensive CORS proxy solution that works with both
local and remote backends during development.

## Changes

### Vite Configuration (vite.config.ts)
- Use loadEnv() to properly read VITE_API_BASE_URL from .env
- Configure proxy to forward /api/* requests to backend
- Add detailed logging for proxy requests and responses
- Support changeOrigin, rewrite, secure=false, and websockets

### API Client (src/lib/api/client.ts)
- In development: Always use /api prefix (proxied)
- In production: Use direct VITE_API_BASE_URL
- Add console logging to show proxy configuration in dev mode
- Automatic detection of environment (DEV vs PROD)

### Error Handling (route loaders)
- Fix console.error() calls that caused TypeError with circular refs
- Use error.message instead of logging full error objects
- Affects: +page.ts, matches/+page.ts

### Documentation
- docs/LOCAL_DEVELOPMENT.md: Complete rewrite with proxy explanation
  - Quick start guide for both production API and local backend
  - Detailed proxy flow diagrams
  - Comprehensive troubleshooting section
  - Clear examples and logs

- docs/CORS_PROXY.md: Technical deep-dive on proxy implementation
  - How the proxy works internally
  - Configuration options explained
  - Testing procedures
  - Common issues and solutions

- .env.example: Updated with proxy documentation

## How It Works

Development Flow:
1. Frontend makes request: /api/matches
2. Vite proxy intercepts and forwards to: ${VITE_API_BASE_URL}/matches
3. Backend responds (no CORS headers needed)
4. Proxy returns response to frontend (same-origin)

Production Flow:
1. Frontend makes request directly to: https://api.csgow.tf/matches
2. Backend responds with CORS headers
3. Browser allows request (CORS enabled on backend)

## Benefits
 No CORS errors in development
 Works with local backend (localhost:8000)
 Works with remote backend (api.csgow.tf)
 Simple configuration (just set VITE_API_BASE_URL)
 Detailed logging for debugging
 Production build unaffected (direct requests)

## Testing
Verified with production API:
- curl https://api.csgow.tf/matches ✓
- Dev server proxy logs show successful forwarding ✓
- Browser Network tab shows /api/* requests ✓
- No CORS errors in console ✓

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-04 21:34:26 +01:00
parent e81be2cf68
commit 274f5b3b53
7 changed files with 636 additions and 35 deletions

View File

@@ -7,8 +7,9 @@
# ============================================ # ============================================
# Backend API Base URL # Backend API Base URL
# Default: http://localhost:8000 (local development) # Development: Vite proxy forwards /api to this URL (default: http://localhost:8000)
# Production: https://api.csgow.tf or your backend URL # Production: Set to your actual backend URL (e.g., https://api.csgow.tf)
# Note: In development, the frontend uses /api and Vite proxies to this URL
VITE_API_BASE_URL=http://localhost:8000 VITE_API_BASE_URL=http://localhost:8000
# API request timeout in milliseconds # API request timeout in milliseconds

234
docs/CORS_PROXY.md Normal file
View File

@@ -0,0 +1,234 @@
# CORS Proxy Configuration
This document explains how the CORS proxy works in the CS2.WTF frontend.
## Problem: CORS in Development
When developing a frontend that talks to an API on a different origin, browsers enforce CORS (Cross-Origin Resource Sharing) policies. This causes errors like:
```
Access to fetch at 'https://api.csgow.tf/matches' from origin 'http://localhost:5173'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
```
## Solution: Vite Development Proxy
The Vite dev server includes a built-in proxy that solves this problem by making all API requests appear same-origin.
### Configuration
**File**: `vite.config.ts`
```typescript
import { loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:8000';
return {
server: {
proxy: {
'/api': {
target: apiBaseUrl,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
secure: false,
ws: true
}
}
}
};
});
```
### How It Works
1. **API Client** (in development) makes requests to `/api/*`:
```typescript
// src/lib/api/client.ts
const API_BASE_URL = import.meta.env.DEV ? '/api' : VITE_API_BASE_URL;
```
2. **Vite Proxy** intercepts requests to `/api/*` and forwards them:
```
Browser Request: GET http://localhost:5173/api/matches?limit=6
Vite Proxy: Intercepts /api/* requests
Backend Request: GET https://api.csgow.tf/matches?limit=6
Response: ← Returns data through proxy
Browser: ← Receives response (appears same-origin)
```
3. **Browser sees same-origin request** - no CORS error!
### Configuration Options
| Option | Value | Purpose |
|--------|-------|---------|
| `target` | `env.VITE_API_BASE_URL` | Where to forward requests |
| `changeOrigin` | `true` | Updates `Origin` header to match target |
| `rewrite` | Remove `/api` prefix | Maps `/api/matches` → `/matches` |
| `secure` | `false` | Allow self-signed certificates |
| `ws` | `true` | Enable WebSocket proxying |
### Environment Variables
**`.env`**:
```env
# Proxy will forward /api/* to this URL
VITE_API_BASE_URL=https://api.csgow.tf
# Or use local backend
# VITE_API_BASE_URL=http://localhost:8000
```
### Logging
The proxy logs all requests for debugging:
```bash
[Vite Config] API Proxy target: https://api.csgow.tf
[Proxy] GET /api/matches?limit=6 -> https://api.csgow.tf/matches?limit=6
[Proxy ✓] GET /api/matches?limit=6 -> 200
[Proxy] GET /api/match/123 -> https://api.csgow.tf/match/123
[Proxy ✓] GET /api/match/123 -> 200
```
Error logging:
```bash
[Proxy Error] ECONNREFUSED
[Proxy Error] Make sure backend is running at: http://localhost:8000
```
## API Client Configuration
**File**: `src/lib/api/client.ts`
```typescript
const getAPIBaseURL = (): string => {
// In production builds, use the configured URL directly
if (import.meta.env.PROD) {
return import.meta.env?.VITE_API_BASE_URL || 'https://api.csgow.tf';
}
// In development mode, ALWAYS use the Vite proxy to avoid CORS issues
// The proxy will forward /api requests to VITE_API_BASE_URL
return '/api';
};
```
This ensures:
- ✅ **Development**: Always uses `/api` (proxy handles CORS)
- ✅ **Production**: Uses direct URL (backend has CORS enabled)
## Testing the Proxy
### 1. Check Vite Config Loads Environment
Start dev server and look for:
```bash
npm run dev
# Should show:
[Vite Config] API Proxy target: https://api.csgow.tf
```
### 2. Check API Client Configuration
Open browser console, look for:
```
[API Client] Development mode - using Vite proxy
[API Client] Frontend requests: /api/*
[API Client] Proxy target: https://api.csgow.tf
```
### 3. Check Network Requests
Open DevTools → Network tab:
- ✅ Requests should go to `/api/*` (not full URL)
- ✅ Response should be `200 OK`
- ✅ No CORS errors in console
### 4. Check Proxy Logs
Terminal should show:
```
[Proxy] GET /api/matches -> https://api.csgow.tf/matches
[Proxy ✓] GET /api/matches -> 200
```
## Common Issues
### Issue 1: Proxy Not Loading .env
**Symptom**: Proxy uses default `http://localhost:8000` instead of `.env` value
**Cause**: `vite.config.ts` not loading environment variables
**Fix**: Use `loadEnv()` in config:
```typescript
import { loadEnv } from 'vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:8000';
// ...
});
```
### Issue 2: Still Getting CORS Errors
**Symptom**: Browser console shows CORS errors
**Possible Causes**:
1. API client not using `/api` prefix in development
2. Request bypassing proxy somehow
3. Running production build instead of dev server
**Fix**:
1. Check API client logs show: `Development mode - using Vite proxy`
2. Verify Network tab shows requests to `/api/*`
3. Run `npm run dev` (not `npm run preview`)
### Issue 3: Connection Refused
**Symptom**: `[Proxy Error] ECONNREFUSED`
**Cause**: Backend is not running at the configured URL
**Fix**:
- If using local backend: Start `csgowtfd` on port 8000
- If using production API: Check `VITE_API_BASE_URL=https://api.csgow.tf`
## Production Build
In production, the proxy is **not used**. The frontend makes direct requests to the backend:
```typescript
// Production build
const API_BASE_URL = 'https://api.csgow.tf';
// Direct request (no proxy)
fetch('https://api.csgow.tf/matches');
```
The production API must have CORS enabled:
```
Access-Control-Allow-Origin: https://cs2.wtf
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
```
## Summary
| Environment | Frontend URL | API Requests | CORS |
|-------------|-------------|--------------|------|
| **Development** | `http://localhost:5173` | `/api/*` → Proxy → Backend | ✅ Proxy handles |
| **Production** | `https://cs2.wtf` | Direct to backend | ✅ Backend CORS |
The proxy is a **development-only** feature that makes local development smooth and eliminates CORS headaches.

318
docs/LOCAL_DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,318 @@
# Local Development Setup
This guide will help you set up the CS2.WTF frontend for local development.
## Prerequisites
- **Node.js**: v18.x or v20.x (check with `node --version`)
- **npm**: v9.x or higher (comes with Node.js)
- **Backend API**: Either local csgowtfd service OR access to production API
## Quick Start
### 1. Install Dependencies
```bash
npm install
```
### 2. Environment Configuration
The `.env` file already exists in the project. You can use it as-is or modify it:
**Option A: Use Production API** (Recommended for frontend development)
```env
# Use the live production API - no local backend needed
VITE_API_BASE_URL=https://api.csgow.tf
VITE_API_TIMEOUT=10000
VITE_DEBUG_MODE=true
VITE_ENABLE_ANALYTICS=false
```
**Option B: Use Local Backend** (For full-stack development)
```env
# Use local backend (requires csgowtfd running on port 8000)
VITE_API_BASE_URL=http://localhost:8000
VITE_API_TIMEOUT=10000
VITE_DEBUG_MODE=true
VITE_ENABLE_ANALYTICS=false
```
### 3. Start the Development Server
```bash
npm run dev
```
The frontend will be available at `http://localhost:5173`
You should see output like:
```
[Vite Config] API Proxy target: https://api.csgow.tf
[API Client] Development mode - using Vite proxy
[API Client] Frontend requests: /api/*
[API Client] Proxy target: https://api.csgow.tf
➜ Local: http://localhost:5173/
```
### 4. (Optional) Start Local Backend
Only needed if using `VITE_API_BASE_URL=http://localhost:8000`:
```bash
# In the csgowtfd repository
cd ../csgowtfd
go run cmd/csgowtfd/main.go
```
Or use Docker:
```bash
docker-compose up csgowtfd
```
## How the CORS Proxy Works
The Vite dev server includes a **built-in proxy** that eliminates CORS issues during development:
### Development Mode Flow
```
1. Browser makes request to: http://localhost:5173/api/matches
2. Vite intercepts and proxies to: ${VITE_API_BASE_URL}/matches
3. Backend responds
4. Vite forwards response to browser
```
### Benefits
-**No CORS errors** - All requests appear same-origin to the browser
-**Works with any backend** - Local or remote
-**No backend CORS config needed** - Proxy handles it
-**Simple configuration** - Just set `VITE_API_BASE_URL`
### Proxy Logs
You'll see detailed proxy activity in the terminal:
```bash
[Proxy] GET /api/matches?limit=6 -> https://api.csgow.tf/matches?limit=6
[Proxy ✓] GET /api/matches?limit=6 -> 200
[Proxy] GET /api/match/123 -> https://api.csgow.tf/match/123
[Proxy ✓] GET /api/match/123 -> 200
```
### Production vs Development
| Mode | API Base URL | CORS |
|------|-------------|------|
| **Development** (`npm run dev`) | `/api` (proxied to `VITE_API_BASE_URL`) | ✅ No issues |
| **Production** (`npm run build`) | `VITE_API_BASE_URL` (direct) | ✅ Backend has CORS enabled |
## Troubleshooting
### No Data Showing / Network Errors
**Problem**: Frontend loads but shows no matches, players show "Failed to load" errors.
**Solutions**:
1. **Check what backend you're using**:
```bash
# Look at your .env file
cat .env | grep VITE_API_BASE_URL
```
2. **If using production API** (`https://api.csgow.tf`):
```bash
# Test if production API is accessible
curl https://api.csgow.tf/matches?limit=1
```
Should return JSON data. If not, production API may be down.
3. **If using local backend** (`http://localhost:8000`):
```bash
# Test if local backend is running
curl http://localhost:8000/matches?limit=1
```
If you get "Connection refused", start the backend service.
4. **Check proxy logs**:
- Look at the terminal running `npm run dev`
- You should see `[Proxy]` messages showing requests being forwarded
- If you see `[Proxy Error]`, check the error message
5. **Check browser console**:
- Open DevTools → Console tab
- Look for `[API Client]` messages showing proxy configuration
- Network tab should show requests to `/api/*` (not external URLs)
6. **Restart dev server**:
```bash
# Stop dev server (Ctrl+C)
npm run dev
```
### CORS Errors (Should Not Happen)
If you see CORS errors in the browser console, the proxy isn't working:
**Symptoms**:
- Browser console shows: `CORS policy: No 'Access-Control-Allow-Origin' header`
- Network tab shows requests going to `https://api.csgow.tf` directly (not `/api`)
**Fix**:
1. Verify you're in development mode (not production build)
2. Check API client logs show: `Development mode - using Vite proxy`
3. Restart dev server with clean cache:
```bash
rm -rf .svelte-kit
npm run dev
```
### Port Already in Use
If port 5173 is already in use:
```bash
# Vite will automatically try the next available port
npm run dev
# Or specify a custom port
npm run dev -- --port 3000
```
### Backend Connection Issues
If the backend is on a different host/port, update `.env`:
```env
# Custom backend location
VITE_API_BASE_URL=http://192.168.1.100:8080
```
Then restart the dev server.
## Development Workflow
### 1. Make Changes
Edit files in `src/`. The dev server has hot module replacement (HMR):
- Component changes reload instantly
- Route changes reload the page
- Store changes reload affected components
### 2. Type Checking
Run TypeScript type checking:
```bash
npm run check # Check once
npm run check:watch # Watch mode
```
### 3. Linting
```bash
npm run lint # Check for issues
npm run lint:fix # Auto-fix issues
npm run format # Run Prettier
```
### 4. Testing
```bash
# Unit tests
npm run test # Run once
npm run test:watch # Watch mode
npm run test:coverage # Generate coverage report
# E2E tests
npm run test:e2e # Headless
npm run test:e2e:ui # Playwright UI
```
## API Endpoints
The backend provides these endpoints (see `docs/API.md` for full details):
- `GET /matches` - List all matches
- `GET /match/:id` - Get match details
- `GET /match/:id/rounds` - Get round economy data
- `GET /match/:id/weapons` - Get weapon statistics
- `GET /match/:id/chat` - Get chat messages
- `GET /player/:id` - Get player profile
### How Requests Work
**In Development** (`npm run dev`):
```
Frontend code: api.matches.getMatches()
API Client: GET /api/matches
Vite Proxy: GET https://api.csgow.tf/matches
Response: ← Data returned to frontend
```
**In Production** (deployed app):
```
Frontend code: api.matches.getMatches()
API Client: GET https://api.csgow.tf/matches (direct)
Response: ← Data returned to frontend
```
The API client automatically uses the correct URL based on environment.
## Mock Data (Alternative: No Backend)
If you want to develop without any backend (local or production), enable MSW mocking:
1. Update `.env`:
```env
VITE_ENABLE_MSW_MOCKING=true
```
2. Restart dev server
The app will use mock data from `src/mocks/handlers/`.
**Note**: Mock data is limited and may not reflect all features. **Production API is recommended** for most development work.
## Building for Production
```bash
# Build
npm run build
# Preview production build locally
npm run preview
```
The preview server runs on `http://localhost:4173` and uses the production API configuration.
## Environment Variables Reference
| Variable | Default | Description |
|----------|---------|-------------|
| `VITE_API_BASE_URL` | `http://localhost:8000` | Backend API base URL |
| `VITE_API_TIMEOUT` | `10000` | Request timeout (ms) |
| `VITE_ENABLE_LIVE_MATCHES` | `false` | Enable live match polling |
| `VITE_ENABLE_ANALYTICS` | `false` | Enable analytics tracking |
| `VITE_DEBUG_MODE` | `false` | Enable debug logging |
| `VITE_ENABLE_MSW_MOCKING` | `false` | Use mock data instead of API |
## Getting Help
- **Frontend Issues**: Check browser console for errors
- **API Issues**: Check backend logs and proxy output in terminal
- **Type Errors**: Run `npm run check` for detailed messages
- **Build Issues**: Delete `.svelte-kit/` and `node_modules/`, then `npm install`
## Next Steps
- Read `TODO.md` for current development status
- Check `docs/DESIGN.md` for design system documentation
- Review `docs/API.md` for complete API reference
- See `README.md` for project overview

View File

@@ -5,13 +5,28 @@ import { APIException } from '$lib/types';
/** /**
* API Client Configuration * API Client Configuration
*/ */
const API_BASE_URL = const getAPIBaseURL = (): string => {
typeof window !== 'undefined' // In production builds, use the configured URL directly
? import.meta.env?.VITE_API_BASE_URL || 'http://localhost:8000' if (import.meta.env.PROD) {
: 'http://localhost:8000'; return import.meta.env?.VITE_API_BASE_URL || 'https://api.csgow.tf';
}
// In development mode, ALWAYS use the Vite proxy to avoid CORS issues
// The proxy will forward /api requests to VITE_API_BASE_URL
// This works regardless of whether the backend is local or remote
return '/api';
};
const API_BASE_URL = getAPIBaseURL();
const API_TIMEOUT = Number(import.meta.env?.VITE_API_TIMEOUT) || 10000; const API_TIMEOUT = Number(import.meta.env?.VITE_API_TIMEOUT) || 10000;
// Log the API configuration in development
if (import.meta.env.DEV) {
console.log('[API Client] Development mode - using Vite proxy');
console.log('[API Client] Frontend requests: /api/*');
console.log('[API Client] Proxy target:', import.meta.env?.VITE_API_BASE_URL || 'http://localhost:8000');
}
/** /**
* Base API Client * Base API Client
* Provides centralized HTTP communication with error handling * Provides centralized HTTP communication with error handling

View File

@@ -23,7 +23,7 @@ export const load: PageLoad = async ({ parent }) => {
}; };
} catch (error) { } catch (error) {
// Log error but don't fail the page load // Log error but don't fail the page load
console.error('Failed to load featured matches:', error); console.error('Failed to load featured matches:', error instanceof Error ? error.message : String(error));
// Return empty data - page will show without featured matches // Return empty data - page will show without featured matches
return { return {

View File

@@ -33,7 +33,7 @@ export const load: PageLoad = async ({ url }) => {
} }
}; };
} catch (error) { } catch (error) {
console.error('Failed to load matches:', error); console.error('Failed to load matches:', error instanceof Error ? error.message : String(error));
// Return empty state on error // Return empty state on error
return { return {

View File

@@ -1,32 +1,65 @@
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config'; import { defineConfig } from 'vitest/config';
import { loadEnv } from 'vite';
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [sveltekit()], // Load env file based on `mode` in the current working directory.
test: { // Set the third parameter to '' to load all env regardless of the `VITE_` prefix.
include: ['src/**/*.{test,spec}.{js,ts}'], const env = loadEnv(mode, process.cwd(), '');
globals: true, const apiBaseUrl = env.VITE_API_BASE_URL || 'http://localhost:8000';
environment: 'jsdom',
setupFiles: ['./src/tests/setup.ts'], console.log('[Vite Config] API Proxy target:', apiBaseUrl);
coverage: {
provider: 'v8', return {
reporter: ['text', 'json', 'html'], plugins: [sveltekit()],
exclude: [ test: {
'node_modules/', include: ['src/**/*.{test,spec}.{js,ts}'],
'src/tests/', globals: true,
'**/*.d.ts', environment: 'jsdom',
'**/*.config.*', setupFiles: ['./src/tests/setup.ts'],
'**/mockData', coverage: {
'**/.svelte-kit' provider: 'v8',
] reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'src/tests/',
'**/*.d.ts',
'**/*.config.*',
'**/mockData',
'**/.svelte-kit'
]
}
},
server: {
port: 5173,
strictPort: false,
proxy: {
// Proxy API requests to backend to avoid CORS issues in development
// All requests to /api/* are forwarded to the backend
'/api': {
target: apiBaseUrl,
changeOrigin: true, // Updates the Origin header to match the target
rewrite: (path) => path.replace(/^\/api/, ''), // Remove /api prefix
secure: false, // Allow self-signed certificates
ws: true, // Proxy websockets
configure: (proxy, options) => {
proxy.on('error', (err, _req, _res) => {
console.error('[Proxy Error]', err.message);
console.error('[Proxy Error] Make sure backend is running at:', apiBaseUrl);
});
proxy.on('proxyReq', (proxyReq, req, _res) => {
console.log(`[Proxy] ${req.method} ${req.url} -> ${apiBaseUrl}${proxyReq.path}`);
});
proxy.on('proxyRes', (proxyRes, req, _res) => {
console.log(`[Proxy ✓] ${req.method} ${req.url} -> ${proxyRes.statusCode}`);
});
}
}
}
},
preview: {
port: 4173,
strictPort: false
} }
}, };
server: {
port: 5173,
strictPort: false
},
preview: {
port: 4173,
strictPort: false
}
}); });