Local setup, debugging, and development workflow.
- Prerequisites
- Quick Start
- Project Structure
- Development Workflow
- Testing
- Debugging
- Code Style
- Common Issues
- Contributing
- Node.js 18.0.0 or higher
- npm 9+ (or yarn/pnpm)
- Git
- VS Code with extensions:
- ESLint
- Tailwind CSS IntelliSense
- TypeScript Vue Plugin (Volar)
- Prettier
node --version # v18.0.0+
npm --version # 9+
git --version # 2.0+# Clone repository
git clone https://github.com/nirholas/crypto-data-aggregator.git
cd crypto-data-aggregator
# Install dependencies
npm install
# Start development server
npm run devcrypto-data-aggregator/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API routes
│ │ │ ├── market/ # Market data endpoints
│ │ │ ├── defi/ # DeFi endpoints
│ │ │ └── ...
│ │ ├── coin/[coinId]/ # Dynamic routes
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ └── globals.css # Global styles
│ │
│ ├── components/ # React components
│ │ ├── alerts/ # Alert system
│ │ ├── cards/ # Card components
│ │ ├── charts/ # Chart components
│ │ └── ...
│ │
│ └── lib/ # Core utilities
│ ├── market-data.ts # API client
│ ├── cache.ts # Caching
│ ├── portfolio.ts # Portfolio logic
│ └── ...
│
├── public/ # Static assets
├── docs/ # Documentation
├── package.json
├── tsconfig.json
├── tailwind.config.js
└── vitest.config.ts
| File | Purpose |
|---|---|
src/lib/market-data.ts |
CoinGecko/DeFiLlama API client |
src/lib/cache.ts |
In-memory cache with TTL |
src/lib/api-utils.ts |
Response helpers, ETag generation |
src/app/api/*/route.ts |
API route handlers |
src/components/ThemeProvider.tsx |
Dark/light mode context |
src/components/alerts/AlertsProvider.tsx |
Price alerts context |
# Development
npm run dev # Start dev server with hot reload
# Building
npm run build # Production build
npm start # Start production server
# Testing
npm test # Run tests in watch mode
npm run test:run # Run tests once
npm run test:ui # Open Vitest UI
npm run test:coverage # Generate coverage report
# Code Quality
npm run lint # ESLint check
npm run lint -- --fix # Auto-fix lint issues
# Analysis
npm run analyze # Bundle analysis (ANALYZE=true)The dev server supports Fast Refresh:
- Components: Instant updates, state preserved
- API Routes: Automatic reload on save
- CSS: Instant style updates
Create .env.local for local overrides:
# Optional: Higher rate limits
COINGECKO_API_KEY=your_key
# Optional: Use Pro API
COINGECKO_BASE_URL=https://pro-api.coingecko.com/api/v3- Vitest - Test runner
- Testing Library - React component testing
- jsdom - DOM simulation
# Watch mode (default)
npm test
# Single run
npm run test:run
# With coverage
npm run test:coverage
# Interactive UI
npm run test:ui// src/lib/market-data.test.ts
import { describe, it, expect, vi } from 'vitest';
import { getTopCoins, formatPrice } from './market-data';
describe('formatPrice', () => {
it('formats large prices correctly', () => {
expect(formatPrice(45000)).toBe('$45,000');
});
it('formats small prices with decimals', () => {
expect(formatPrice(0.00005)).toBe('$0.00005');
});
it('handles null values', () => {
expect(formatPrice(null)).toBe('$0.00');
});
});
describe('getTopCoins', () => {
it('returns array of coins', async () => {
const coins = await getTopCoins(10);
expect(Array.isArray(coins)).toBe(true);
});
});import { vi } from 'vitest';
// Mock fetch
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{ id: 'bitcoin', name: 'Bitcoin' }]),
})
) as any;// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './vitest.setup.ts',
include: ['**/*.test.{ts,tsx}'],
},
});Create .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev",
"serverReadyAction": {
"pattern": "started server on .+, url: (https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}Add debug logging:
// src/app/api/market/coins/route.ts
export async function GET(request: NextRequest) {
console.log('[API] /market/coins', {
params: Object.fromEntries(request.nextUrl.searchParams),
timestamp: new Date().toISOString(),
});
// ... handler code
}import { newsCache } from '@/lib/cache';
// Check cache stats
console.log('Cache stats:', newsCache.stats());
// Clear cache for testing
newsCache.clear();Use browser DevTools:
- Open Network tab
- Filter by
Fetch/XHR - Inspect request/response
For server-side:
// Add timing to API responses
import { withTiming } from '@/lib/api-utils';
const startTime = Date.now();
const data = await fetchData();
return Response.json(withTiming(data, startTime));
// Response includes { _meta: { responseTimeMs: 45 } }- Strict mode enabled
- No
anytypes (useunknownor proper types) - Explicit return types for exported functions
// Good
export function formatPrice(price: number | null): string {
// ...
}
// Avoid
export function formatPrice(price) {
// ...
}- Functional components only
- Use TypeScript interfaces for props
- Prefer named exports
// Good
interface CoinCardProps {
coin: TokenPrice;
onClick?: () => void;
}
export function CoinCard({ coin, onClick }: CoinCardProps) {
return <div onClick={onClick}>{coin.name}</div>;
}- Components:
PascalCase.tsx - Utilities:
kebab-case.ts - Tests:
*.test.tsor*.test.tsx
Order imports:
- React/Next.js
- External packages
- Internal components (
@/components/) - Internal utilities (
@/lib/) - Types
- Styles
import { useState, useEffect } from 'react';
import useSWR from 'swr';
import { Loader } from 'lucide-react';
import { CoinCard } from '@/components/cards/CoinCard';
import { getTopCoins } from '@/lib/market-data';
import type { TokenPrice } from '@/lib/market-data';Run lint check:
npm run lintFix auto-fixable issues:
npm run lint -- --fixSymptom: API returns 429 errors
Solution:
- Wait for rate limit window (60s)
- Add
COINGECKO_API_KEYfor higher limits - Increase cache TTLs
// Check rate limit status
import { rateLimitState } from '@/lib/market-data';
console.log('Requests this window:', rateLimitState.requestCount);Symptom: "Text content does not match server-rendered HTML"
Cause: Client/server render mismatch
Solution: Use useEffect for client-only data
// Wrong
function Component() {
return <div>{Date.now()}</div>; // Different on client/server
}
// Correct
function Component() {
const [time, setTime] = useState<number | null>(null);
useEffect(() => {
setTime(Date.now());
}, []);
return <div>{time ?? 'Loading...'}</div>;
}Symptom: "localStorage is not defined"
Solution: Check for browser environment
// In hooks or utilities
if (typeof window === 'undefined') return [];
// Or use useEffect
useEffect(() => {
const stored = localStorage.getItem('key');
// ...
}, []);Symptom: npm run build fails
Solutions:
-
Check TypeScript errors:
npx tsc --noEmit
-
Clear cache:
rm -rf .next node_modules/.cache npm run build
-
Check for missing dependencies:
npm ci
Symptom: "Cannot find module '@/lib/...'"
Solution: Check tsconfig.json paths:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}Symptom: 500 error on API routes
Debug:
# Check server logs
npm run dev
# Look for errors in terminal outputCommon fixes:
- Ensure Edge Runtime compatibility (no Node.js APIs)
- Check async/await handling
- Verify external API responses
main- Production branchfeature/*- New featuresfix/*- Bug fixesdocs/*- Documentation
- Fork and clone
- Create feature branch
- Make changes
- Run tests:
npm run test:run - Run lint:
npm run lint - Build:
npm run build - Commit with descriptive message
- Open PR against
main
Follow conventional commits:
feat: add portfolio export feature
fix: resolve hydration error in CoinCard
docs: update API documentation
refactor: simplify cache logic
test: add market-data unit tests
- TypeScript types are correct
- Tests pass
- No ESLint errors
- Build succeeds
- Documentation updated (if needed)
- No console.log statements
# Analyze bundle size
ANALYZE=true npm run buildVS Code extensions for this project:
// .vscode/extensions.json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss",
"esbenp.prettier-vscode",
"vitest.explorer"
]
}# Reinstall dependencies
rm -rf node_modules && npm install
# Reset Next.js cache
rm -rf .next
# Full clean rebuild
rm -rf .next node_modules && npm install && npm run build
# Check for outdated packages
npm outdated
# Update packages
npm update