937 lines
18 KiB
Markdown
937 lines
18 KiB
Markdown
# Debugging Guide
|
|
|
|
Comprehensive guide to debugging Flashpoint Web application across all services.
|
|
|
|
## Table of Contents
|
|
|
|
- [Development Tools](#development-tools)
|
|
- [Backend Debugging](#backend-debugging)
|
|
- [Frontend Debugging](#frontend-debugging)
|
|
- [Game Service Debugging](#game-service-debugging)
|
|
- [Database Debugging](#database-debugging)
|
|
- [Network Debugging](#network-debugging)
|
|
- [Performance Debugging](#performance-debugging)
|
|
- [Common Issues](#common-issues)
|
|
|
|
---
|
|
|
|
## Development Tools
|
|
|
|
### VS Code Configuration
|
|
|
|
**Recommended Extensions:**
|
|
- ESLint
|
|
- Prettier
|
|
- TypeScript and JavaScript Language Features
|
|
- Debugger for Chrome
|
|
- REST Client
|
|
- SQLite Viewer
|
|
- GitLens
|
|
|
|
**VS Code Debug Configuration** (`.vscode/launch.json`):
|
|
|
|
```json
|
|
{
|
|
"version": "0.2.0",
|
|
"configurations": [
|
|
{
|
|
"name": "Backend: Debug",
|
|
"type": "node",
|
|
"request": "launch",
|
|
"runtimeExecutable": "npm",
|
|
"runtimeArgs": ["run", "dev"],
|
|
"cwd": "${workspaceFolder}/backend",
|
|
"console": "integratedTerminal",
|
|
"internalConsoleOptions": "neverOpen",
|
|
"skipFiles": ["<node_internals>/**"]
|
|
},
|
|
{
|
|
"name": "Frontend: Debug Chrome",
|
|
"type": "chrome",
|
|
"request": "launch",
|
|
"url": "http://localhost:5173",
|
|
"webRoot": "${workspaceFolder}/frontend/src",
|
|
"sourceMapPathOverrides": {
|
|
"webpack:///src/*": "${webRoot}/*"
|
|
}
|
|
},
|
|
{
|
|
"name": "Game Service: Debug",
|
|
"type": "node",
|
|
"request": "launch",
|
|
"runtimeExecutable": "npm",
|
|
"runtimeArgs": ["run", "dev"],
|
|
"cwd": "${workspaceFolder}/game-service",
|
|
"console": "integratedTerminal"
|
|
},
|
|
{
|
|
"name": "Backend: Attach",
|
|
"type": "node",
|
|
"request": "attach",
|
|
"port": 9229,
|
|
"restart": true,
|
|
"skipFiles": ["<node_internals>/**"]
|
|
}
|
|
],
|
|
"compounds": [
|
|
{
|
|
"name": "Full Stack: Debug",
|
|
"configurations": [
|
|
"Backend: Debug",
|
|
"Frontend: Debug Chrome",
|
|
"Game Service: Debug"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Browser DevTools
|
|
|
|
**Chrome DevTools Features:**
|
|
- Elements tab: Inspect React components
|
|
- Console: View logs and errors
|
|
- Network tab: Monitor API requests
|
|
- Application tab: Check localStorage, cookies
|
|
- Performance tab: Profile rendering
|
|
- React DevTools: Component tree and state
|
|
|
|
**Useful Console Commands:**
|
|
|
|
```javascript
|
|
// Clear console
|
|
clear()
|
|
|
|
// Copy object to clipboard
|
|
copy(object)
|
|
|
|
// Get DOM element
|
|
$('#element-id')
|
|
$$('.class-name')
|
|
|
|
// Monitor function calls
|
|
monitor(functionName)
|
|
unmonitor(functionName)
|
|
|
|
// View localStorage
|
|
localStorage
|
|
|
|
// Clear all storage
|
|
localStorage.clear()
|
|
sessionStorage.clear()
|
|
```
|
|
|
|
---
|
|
|
|
## Backend Debugging
|
|
|
|
### Starting Backend with Debugger
|
|
|
|
**Method 1: VS Code Debugger**
|
|
|
|
1. Set breakpoints in TypeScript files
|
|
2. Press F5 or select "Backend: Debug" from Run panel
|
|
3. Server starts with debugger attached
|
|
|
|
**Method 2: Node Inspector**
|
|
|
|
```bash
|
|
cd backend
|
|
|
|
# Start with inspector
|
|
node --inspect-brk -r tsx/cjs src/server.ts
|
|
|
|
# In Chrome, navigate to:
|
|
chrome://inspect
|
|
```
|
|
|
|
**Method 3: tsx with inspect flag**
|
|
|
|
```bash
|
|
cd backend
|
|
npm run dev -- --inspect
|
|
|
|
# Or with break on start
|
|
npm run dev -- --inspect-brk
|
|
```
|
|
|
|
### Logging
|
|
|
|
**Winston Logger Configuration:**
|
|
|
|
```typescript
|
|
// src/utils/logger.ts
|
|
import winston from 'winston';
|
|
|
|
export const logger = winston.createLogger({
|
|
level: process.env.LOG_LEVEL || 'info',
|
|
format: winston.format.combine(
|
|
winston.format.timestamp(),
|
|
winston.format.errors({ stack: true }),
|
|
winston.format.json()
|
|
),
|
|
transports: [
|
|
new winston.transports.Console({
|
|
format: winston.format.combine(
|
|
winston.format.colorize(),
|
|
winston.format.simple()
|
|
)
|
|
}),
|
|
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
|
new winston.transports.File({ filename: 'logs/combined.log' })
|
|
]
|
|
});
|
|
```
|
|
|
|
**Logging Best Practices:**
|
|
|
|
```typescript
|
|
// Include context in logs
|
|
logger.info('User login attempt', { username, ip: req.ip });
|
|
|
|
// Log errors with stack traces
|
|
try {
|
|
await riskyOperation();
|
|
} catch (error) {
|
|
logger.error('Operation failed', { error, context: additionalData });
|
|
throw error;
|
|
}
|
|
|
|
// Use appropriate log levels
|
|
logger.debug('Detailed debug info'); // Development only
|
|
logger.info('Normal operations'); // General info
|
|
logger.warn('Warning condition'); // Potential issues
|
|
logger.error('Error occurred'); // Errors that need attention
|
|
```
|
|
|
|
**Adjust Log Level:**
|
|
|
|
```bash
|
|
# In .env
|
|
LOG_LEVEL=debug
|
|
|
|
# Or at runtime
|
|
export LOG_LEVEL=debug
|
|
npm run dev
|
|
```
|
|
|
|
### Request/Response Debugging
|
|
|
|
**Log all requests:**
|
|
|
|
```typescript
|
|
// Middleware to log requests
|
|
app.use((req, res, next) => {
|
|
logger.debug('Incoming request', {
|
|
method: req.method,
|
|
path: req.path,
|
|
query: req.query,
|
|
body: req.body,
|
|
headers: req.headers
|
|
});
|
|
next();
|
|
});
|
|
```
|
|
|
|
**Log responses:**
|
|
|
|
```typescript
|
|
app.use((req, res, next) => {
|
|
const originalSend = res.send;
|
|
|
|
res.send = function (data) {
|
|
logger.debug('Response', {
|
|
path: req.path,
|
|
statusCode: res.statusCode,
|
|
data: data
|
|
});
|
|
return originalSend.call(this, data);
|
|
};
|
|
|
|
next();
|
|
});
|
|
```
|
|
|
|
### Debugging Database Queries
|
|
|
|
**Enable SQL logging:**
|
|
|
|
```typescript
|
|
// DatabaseService.ts
|
|
const db = new Database(dbPath, {
|
|
verbose: console.log // Log all SQL queries
|
|
});
|
|
|
|
// Or with custom logger
|
|
const db = new Database(dbPath, {
|
|
verbose: (sql: string) => {
|
|
logger.debug('SQL Query', { sql });
|
|
}
|
|
});
|
|
```
|
|
|
|
**Inspect query results:**
|
|
|
|
```typescript
|
|
const stmt = db.prepare('SELECT * FROM game WHERE id = ?');
|
|
const result = stmt.get(gameId);
|
|
|
|
logger.debug('Query result', {
|
|
sql: stmt.source,
|
|
params: [gameId],
|
|
result: result
|
|
});
|
|
```
|
|
|
|
### Debugging Authentication
|
|
|
|
**Log JWT operations:**
|
|
|
|
```typescript
|
|
// utils/jwt.ts
|
|
export function verifyToken(token: string) {
|
|
try {
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
|
logger.debug('Token verified', { userId: decoded.userId });
|
|
return decoded;
|
|
} catch (error) {
|
|
logger.warn('Token verification failed', { error, token });
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
**Debug middleware:**
|
|
|
|
```typescript
|
|
// middleware/auth.ts
|
|
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
const authHeader = req.headers.authorization;
|
|
|
|
logger.debug('Auth middleware', {
|
|
path: req.path,
|
|
hasAuthHeader: !!authHeader,
|
|
authHeader: authHeader?.substring(0, 20) + '...'
|
|
});
|
|
|
|
// Rest of middleware
|
|
}
|
|
```
|
|
|
|
### Debugging Services
|
|
|
|
**Add debug points in services:**
|
|
|
|
```typescript
|
|
export class GameService {
|
|
public getGameById(id: string): Game | null {
|
|
logger.debug('GameService.getGameById called', { id });
|
|
|
|
const game = this.db.prepare('SELECT * FROM game WHERE id = ?').get(id);
|
|
|
|
logger.debug('GameService.getGameById result', {
|
|
id,
|
|
found: !!game,
|
|
game: game
|
|
});
|
|
|
|
return game ?? null;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Frontend Debugging
|
|
|
|
### React DevTools
|
|
|
|
**Installation:**
|
|
- Chrome: Install "React Developer Tools" extension
|
|
- Firefox: Install "React Developer Tools" add-on
|
|
|
|
**Usage:**
|
|
1. Open browser DevTools
|
|
2. Select "Components" or "Profiler" tab
|
|
3. Inspect component tree, props, state, hooks
|
|
|
|
**Debug Component State:**
|
|
|
|
```tsx
|
|
// Add debug logging to components
|
|
export function GameCard({ game }: GameCardProps) {
|
|
console.log('GameCard render', { game });
|
|
|
|
const [isHovered, setIsHovered] = useState(false);
|
|
|
|
useEffect(() => {
|
|
console.log('GameCard mounted', { gameId: game.id });
|
|
|
|
return () => {
|
|
console.log('GameCard unmounted', { gameId: game.id });
|
|
};
|
|
}, [game.id]);
|
|
|
|
return <div>...</div>;
|
|
}
|
|
```
|
|
|
|
### React Query DevTools
|
|
|
|
**Enable React Query DevTools:**
|
|
|
|
```tsx
|
|
// App.tsx
|
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
|
|
export default function App() {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
<Router>
|
|
{/* App content */}
|
|
</Router>
|
|
<ReactQueryDevtools initialIsOpen={false} />
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Features:**
|
|
- View all queries and their states
|
|
- Manually refetch queries
|
|
- Clear cache
|
|
- Inspect query data
|
|
- Track loading states
|
|
|
|
### Debugging API Calls
|
|
|
|
**Log API requests:**
|
|
|
|
```typescript
|
|
// lib/api.ts
|
|
import axios from 'axios';
|
|
|
|
const api = axios.create({
|
|
baseURL: import.meta.env.VITE_API_URL
|
|
});
|
|
|
|
// Request interceptor
|
|
api.interceptors.request.use(
|
|
(config) => {
|
|
console.log('API Request', {
|
|
method: config.method,
|
|
url: config.url,
|
|
params: config.params,
|
|
data: config.data
|
|
});
|
|
return config;
|
|
},
|
|
(error) => {
|
|
console.error('API Request Error', error);
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
// Response interceptor
|
|
api.interceptors.response.use(
|
|
(response) => {
|
|
console.log('API Response', {
|
|
url: response.config.url,
|
|
status: response.status,
|
|
data: response.data
|
|
});
|
|
return response;
|
|
},
|
|
(error) => {
|
|
console.error('API Response Error', {
|
|
url: error.config?.url,
|
|
status: error.response?.status,
|
|
data: error.response?.data
|
|
});
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
```
|
|
|
|
### Debugging State Management
|
|
|
|
**Zustand DevTools:**
|
|
|
|
```typescript
|
|
// store/auth.ts
|
|
import { create } from 'zustand';
|
|
import { devtools } from 'zustand/middleware';
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
setUser: (user: User | null) => void;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>()(
|
|
devtools(
|
|
(set) => ({
|
|
user: null,
|
|
setUser: (user) => set({ user }, false, 'setUser')
|
|
}),
|
|
{ name: 'AuthStore' }
|
|
)
|
|
);
|
|
```
|
|
|
|
**Manual state logging:**
|
|
|
|
```typescript
|
|
export const useAuthStore = create<AuthState>()((set) => ({
|
|
user: null,
|
|
setUser: (user) => {
|
|
console.log('Auth state update', { user });
|
|
set({ user });
|
|
}
|
|
}));
|
|
```
|
|
|
|
### Debugging Hooks
|
|
|
|
**Add debug logging to custom hooks:**
|
|
|
|
```typescript
|
|
export function useGameSelection(initialGameId?: string) {
|
|
const [selectedGameId, setSelectedGameId] = useState(initialGameId);
|
|
|
|
console.log('useGameSelection', {
|
|
selectedGameId,
|
|
initialGameId
|
|
});
|
|
|
|
const { data: game, isLoading, error } = useQuery({
|
|
queryKey: ['game', selectedGameId],
|
|
queryFn: () => fetchGame(selectedGameId!),
|
|
enabled: !!selectedGameId
|
|
});
|
|
|
|
useEffect(() => {
|
|
console.log('useGameSelection: game data updated', {
|
|
gameId: selectedGameId,
|
|
game,
|
|
isLoading,
|
|
error
|
|
});
|
|
}, [selectedGameId, game, isLoading, error]);
|
|
|
|
return { selectedGameId, setSelectedGameId, game, isLoading, error };
|
|
}
|
|
```
|
|
|
|
### Debugging Renders
|
|
|
|
**React.StrictMode helps catch issues:**
|
|
|
|
```tsx
|
|
// main.tsx
|
|
import { StrictMode } from 'react';
|
|
|
|
createRoot(document.getElementById('root')!).render(
|
|
<StrictMode>
|
|
<App />
|
|
</StrictMode>
|
|
);
|
|
```
|
|
|
|
**Track render count:**
|
|
|
|
```tsx
|
|
import { useRef } from 'react';
|
|
|
|
export function GameCard({ game }: GameCardProps) {
|
|
const renderCount = useRef(0);
|
|
renderCount.current++;
|
|
|
|
console.log(`GameCard rendered ${renderCount.current} times`, { game });
|
|
|
|
return <div>...</div>;
|
|
}
|
|
```
|
|
|
|
**Use React Profiler:**
|
|
|
|
```tsx
|
|
import { Profiler, ProfilerOnRenderCallback } from 'react';
|
|
|
|
const onRenderCallback: ProfilerOnRenderCallback = (
|
|
id,
|
|
phase,
|
|
actualDuration,
|
|
baseDuration,
|
|
startTime,
|
|
commitTime
|
|
) => {
|
|
console.log('Profiler', {
|
|
id,
|
|
phase,
|
|
actualDuration,
|
|
baseDuration
|
|
});
|
|
};
|
|
|
|
export function GameList() {
|
|
return (
|
|
<Profiler id="GameList" onRender={onRenderCallback}>
|
|
{/* Component tree */}
|
|
</Profiler>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Game Service Debugging
|
|
|
|
### Enabling Debug Logging
|
|
|
|
```bash
|
|
# In game-service/.env
|
|
LOG_LEVEL=debug
|
|
|
|
# Restart service
|
|
cd game-service
|
|
npm run dev
|
|
```
|
|
|
|
### Debugging Proxy Requests
|
|
|
|
**Log proxy requests:**
|
|
|
|
```typescript
|
|
// servers/proxy-server.ts
|
|
app.use((req, res, next) => {
|
|
logger.debug('Proxy request', {
|
|
url: req.url,
|
|
method: req.method,
|
|
headers: req.headers,
|
|
ip: req.ip
|
|
});
|
|
next();
|
|
});
|
|
```
|
|
|
|
**Debug file resolution:**
|
|
|
|
```typescript
|
|
// handlers/file-handler.ts
|
|
export function resolveFile(requestPath: string): string | null {
|
|
const paths = [
|
|
join(HTDOCS_PATH, requestPath),
|
|
join(GAMES_PATH, requestPath),
|
|
// ... other paths
|
|
];
|
|
|
|
logger.debug('Resolving file', { requestPath, searchPaths: paths });
|
|
|
|
for (const path of paths) {
|
|
if (existsSync(path)) {
|
|
logger.debug('File found', { requestPath, resolvedPath: path });
|
|
return path;
|
|
}
|
|
}
|
|
|
|
logger.debug('File not found', { requestPath });
|
|
return null;
|
|
}
|
|
```
|
|
|
|
### Debugging ZIP File Access
|
|
|
|
**Log ZIP operations:**
|
|
|
|
```typescript
|
|
// services/zip-manager.ts
|
|
export class ZipManager {
|
|
public getFileFromZip(zipPath: string, filePath: string): Buffer | null {
|
|
logger.debug('Getting file from ZIP', { zipPath, filePath });
|
|
|
|
try {
|
|
const zip = new StreamZip.async({ file: zipPath });
|
|
const data = await zip.entryData(filePath);
|
|
|
|
logger.debug('File extracted from ZIP', {
|
|
zipPath,
|
|
filePath,
|
|
size: data.length
|
|
});
|
|
|
|
return data;
|
|
} catch (error) {
|
|
logger.error('Failed to extract from ZIP', {
|
|
zipPath,
|
|
filePath,
|
|
error
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Database Debugging
|
|
|
|
### SQLite Browser
|
|
|
|
**Desktop Tools:**
|
|
- DB Browser for SQLite (free)
|
|
- SQLiteStudio
|
|
- TablePlus
|
|
|
|
**VS Code Extensions:**
|
|
- SQLite Viewer
|
|
- SQLite
|
|
|
|
### Query Debugging
|
|
|
|
**Test queries directly:**
|
|
|
|
```bash
|
|
# Open database in SQLite CLI
|
|
sqlite3 backend/user.db
|
|
|
|
# Run queries
|
|
SELECT * FROM users;
|
|
SELECT * FROM play_sessions WHERE user_id = 'abc';
|
|
|
|
# Show table schema
|
|
.schema users
|
|
|
|
# Enable column headers
|
|
.headers on
|
|
|
|
# Enable column mode
|
|
.mode column
|
|
|
|
# Exit
|
|
.quit
|
|
```
|
|
|
|
**Analyze query performance:**
|
|
|
|
```sql
|
|
EXPLAIN QUERY PLAN
|
|
SELECT * FROM game
|
|
WHERE platform = 'Flash'
|
|
AND library = 'arcade';
|
|
```
|
|
|
|
### Database Locks
|
|
|
|
**Check for locks:**
|
|
|
|
```bash
|
|
# Linux/Mac
|
|
lsof backend/user.db
|
|
|
|
# Windows
|
|
# Use Process Explorer to find processes with file handle
|
|
```
|
|
|
|
**Handle lock errors in code:**
|
|
|
|
```typescript
|
|
try {
|
|
const result = db.prepare('INSERT INTO users ...').run(data);
|
|
} catch (error) {
|
|
if (error.code === 'SQLITE_BUSY') {
|
|
logger.warn('Database locked, retrying...', { error });
|
|
// Retry logic
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Network Debugging
|
|
|
|
### Chrome Network Tab
|
|
|
|
**Filter requests:**
|
|
- XHR/Fetch: API calls only
|
|
- JS/CSS: Asset loading
|
|
- All: Complete picture
|
|
|
|
**Inspect request:**
|
|
- Headers: Authorization, Content-Type
|
|
- Payload: Request body
|
|
- Preview: Response data
|
|
- Timing: Performance metrics
|
|
|
|
### Debugging CORS Issues
|
|
|
|
**Check CORS headers in response:**
|
|
|
|
```
|
|
Access-Control-Allow-Origin: http://localhost:5173
|
|
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
|
|
Access-Control-Allow-Headers: Content-Type, Authorization
|
|
```
|
|
|
|
**Backend CORS configuration:**
|
|
|
|
```typescript
|
|
// server.ts
|
|
import cors from 'cors';
|
|
|
|
app.use(cors({
|
|
origin: process.env.DOMAIN || 'http://localhost:5173',
|
|
credentials: true,
|
|
exposedHeaders: ['X-Total-Count']
|
|
}));
|
|
|
|
// Debug CORS
|
|
app.use((req, res, next) => {
|
|
console.log('CORS check', {
|
|
origin: req.headers.origin,
|
|
method: req.method,
|
|
path: req.path
|
|
});
|
|
next();
|
|
});
|
|
```
|
|
|
|
### Using curl for API Testing
|
|
|
|
```bash
|
|
# GET request
|
|
curl http://localhost:3100/api/games
|
|
|
|
# GET with auth
|
|
curl -H "Authorization: Bearer TOKEN" \
|
|
http://localhost:3100/api/games
|
|
|
|
# POST request
|
|
curl -X POST \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username":"test","password":"test"}' \
|
|
http://localhost:3100/api/auth/login
|
|
|
|
# Debug with verbose output
|
|
curl -v http://localhost:3100/api/games
|
|
|
|
# Save response to file
|
|
curl -o response.json http://localhost:3100/api/games
|
|
```
|
|
|
|
### Using HTTPie
|
|
|
|
```bash
|
|
# Install
|
|
npm install -g httpie
|
|
|
|
# GET request
|
|
http GET http://localhost:3100/api/games
|
|
|
|
# POST with auth
|
|
http POST http://localhost:3100/api/auth/login \
|
|
username=test \
|
|
password=test
|
|
|
|
# With auth token
|
|
http GET http://localhost:3100/api/games \
|
|
Authorization:"Bearer TOKEN"
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Debugging
|
|
|
|
### Backend Performance
|
|
|
|
**Measure request duration:**
|
|
|
|
```typescript
|
|
app.use((req, res, next) => {
|
|
const start = Date.now();
|
|
|
|
res.on('finish', () => {
|
|
const duration = Date.now() - start;
|
|
logger.info('Request completed', {
|
|
method: req.method,
|
|
path: req.path,
|
|
status: res.statusCode,
|
|
duration: `${duration}ms`
|
|
});
|
|
|
|
if (duration > 1000) {
|
|
logger.warn('Slow request detected', {
|
|
path: req.path,
|
|
duration: `${duration}ms`
|
|
});
|
|
}
|
|
});
|
|
|
|
next();
|
|
});
|
|
```
|
|
|
|
**Profile database queries:**
|
|
|
|
```typescript
|
|
function profileQuery<T>(name: string, query: () => T): T {
|
|
const start = Date.now();
|
|
const result = query();
|
|
const duration = Date.now() - start;
|
|
|
|
logger.debug('Query performance', { name, duration: `${duration}ms` });
|
|
|
|
return result;
|
|
}
|
|
|
|
// Usage
|
|
const games = profileQuery('searchGames', () =>
|
|
db.prepare('SELECT * FROM game WHERE title LIKE ?').all(`%${query}%`)
|
|
);
|
|
```
|
|
|
|
### Frontend Performance
|
|
|
|
**React Profiler:**
|
|
|
|
```tsx
|
|
import { Profiler } from 'react';
|
|
|
|
<Profiler id="GameList" onRender={(id, phase, actualDuration) => {
|
|
console.log(`${id} ${phase}: ${actualDuration}ms`);
|
|
}}>
|
|
<GameList />
|
|
</Profiler>
|
|
```
|
|
|
|
**Chrome Performance Tab:**
|
|
1. Open DevTools > Performance
|
|
2. Click Record
|
|
3. Interact with application
|
|
4. Click Stop
|
|
5. Analyze flame graph for slow operations
|
|
|
|
**Lighthouse Audit:**
|
|
1. Open DevTools > Lighthouse
|
|
2. Select categories (Performance, Accessibility, etc.)
|
|
3. Click "Generate report"
|
|
4. Review recommendations
|
|
|
|
---
|
|
|
|
## Common Issues
|
|
|
|
See [Common Pitfalls](./common-pitfalls.md) for detailed troubleshooting of frequent issues.
|
|
|
|
---
|
|
|
|
## Additional Resources
|
|
|
|
- [VS Code Debugging Docs](https://code.visualstudio.com/docs/editor/debugging)
|
|
- [Chrome DevTools Guide](https://developer.chrome.com/docs/devtools/)
|
|
- [React DevTools](https://react.dev/learn/react-developer-tools)
|
|
- [Winston Logger Docs](https://github.com/winstonjs/winston)
|
|
- [Common Pitfalls](./common-pitfalls.md)
|