ultra-dex 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -2
- package/assets/agents/0-orchestration/orchestrator.md +225 -0
- package/assets/agents/00-AGENT_INDEX.md +138 -0
- package/assets/agents/1-leadership/cto.md +186 -0
- package/assets/agents/1-leadership/planner.md +205 -0
- package/assets/agents/1-leadership/research.md +285 -0
- package/assets/agents/2-development/backend.md +472 -0
- package/assets/agents/2-development/database.md +516 -0
- package/assets/agents/2-development/frontend.md +144 -0
- package/assets/agents/3-security/auth.md +168 -0
- package/assets/agents/3-security/security.md +335 -0
- package/assets/agents/4-devops/devops.md +587 -0
- package/assets/agents/5-quality/debugger.md +188 -0
- package/assets/agents/5-quality/documentation.md +167 -0
- package/assets/agents/5-quality/reviewer.md +213 -0
- package/assets/agents/5-quality/testing.md +280 -0
- package/assets/agents/6-specialist/performance.md +323 -0
- package/assets/agents/6-specialist/refactoring.md +343 -0
- package/assets/agents/AGENT-INSTRUCTIONS.md +315 -0
- package/assets/agents/README.md +232 -0
- package/assets/cursor-rules/00-ultra-dex-core.mdc +48 -0
- package/assets/cursor-rules/01-database.mdc +50 -0
- package/assets/cursor-rules/02-api.mdc +81 -0
- package/assets/cursor-rules/03-auth.mdc +70 -0
- package/assets/cursor-rules/04-frontend.mdc +92 -0
- package/assets/cursor-rules/05-payments.mdc +88 -0
- package/assets/cursor-rules/06-testing.mdc +104 -0
- package/assets/cursor-rules/07-security.mdc +94 -0
- package/assets/cursor-rules/08-deployment.mdc +92 -0
- package/assets/cursor-rules/09-error-handling.mdc +137 -0
- package/assets/cursor-rules/10-performance.mdc +123 -0
- package/assets/cursor-rules/11-nextjs-v15.mdc +307 -0
- package/assets/cursor-rules/12-multi-tenancy.mdc +282 -0
- package/assets/cursor-rules/README.md +78 -0
- package/assets/cursor-rules/load.ps1 +108 -0
- package/assets/cursor-rules/load.sh +102 -0
- package/assets/docs/BUILD-AUTH-30M.md +113 -0
- package/assets/docs/CHECKLIST-21-STEP.md +86 -0
- package/assets/docs/CODEMAP.md +229 -0
- package/assets/docs/CUSTOMIZATION.md +127 -0
- package/assets/docs/LAUNCH-POSTS.md +238 -0
- package/assets/docs/QUICK-REFERENCE.md +338 -0
- package/assets/docs/README.md +21 -0
- package/assets/docs/ROADMAP.md +480 -0
- package/assets/docs/TROUBLESHOOTING.md +148 -0
- package/assets/docs/TUTORIAL.md +182 -0
- package/assets/docs/VERIFICATION.md +108 -0
- package/assets/docs/VISION-V2.md +187 -0
- package/assets/docs/WORKFLOW-DIAGRAMS.md +463 -0
- package/assets/docs/index.html +550 -0
- package/assets/live-templates/next15-prisma-clerk/.env.example +3 -0
- package/assets/live-templates/next15-prisma-clerk/README.md +10 -0
- package/assets/live-templates/next15-prisma-clerk/app/layout.tsx +7 -0
- package/assets/live-templates/next15-prisma-clerk/app/page.tsx +8 -0
- package/assets/live-templates/next15-prisma-clerk/next.config.js +6 -0
- package/assets/live-templates/next15-prisma-clerk/package.json +22 -0
- package/assets/live-templates/next15-prisma-clerk/prisma/schema.prisma +34 -0
- package/assets/live-templates/remix-supabase/.env.example +2 -0
- package/assets/live-templates/remix-supabase/README.md +9 -0
- package/assets/live-templates/remix-supabase/app/root.tsx +19 -0
- package/assets/live-templates/remix-supabase/app/routes/_index.tsx +8 -0
- package/assets/live-templates/remix-supabase/app/utils/supabase.server.ts +6 -0
- package/assets/live-templates/remix-supabase/package.json +20 -0
- package/assets/live-templates/remix-supabase/remix.config.js +6 -0
- package/assets/live-templates/sveltekit-drizzle/.env.example +1 -0
- package/assets/live-templates/sveltekit-drizzle/README.md +9 -0
- package/assets/live-templates/sveltekit-drizzle/drizzle/schema.ts +7 -0
- package/assets/live-templates/sveltekit-drizzle/drizzle.config.ts +5 -0
- package/assets/live-templates/sveltekit-drizzle/package.json +21 -0
- package/assets/live-templates/sveltekit-drizzle/src/lib/db.ts +5 -0
- package/assets/live-templates/sveltekit-drizzle/src/routes/+page.svelte +2 -0
- package/assets/live-templates/sveltekit-drizzle/svelte.config.js +5 -0
- package/assets/live-templates/sveltekit-drizzle/vite.config.js +5 -0
- package/assets/saas-plan/04-Imp-Template.md +5546 -0
- package/assets/templates/CASE-STUDY-TEMPLATE.md +139 -0
- package/assets/templates/MASTER-PLAN-TEMPLATE.md +647 -0
- package/assets/templates/ORDER-TRACKER-TEMPLATE.md +731 -0
- package/assets/templates/PHASE-TRACKER-TEMPLATE.md +577 -0
- package/assets/templates/README.md +419 -0
- package/bin/ultra-dex.js +643 -29
- package/package.json +3 -3
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Testing Rules
|
|
2
|
+
|
|
3
|
+
## Test Stack
|
|
4
|
+
|
|
5
|
+
- Unit/Integration: Vitest
|
|
6
|
+
- E2E: Playwright
|
|
7
|
+
- API: Supertest or native fetch
|
|
8
|
+
|
|
9
|
+
## File Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
tests/
|
|
13
|
+
├── unit/ # Pure function tests
|
|
14
|
+
├── integration/ # API + DB tests
|
|
15
|
+
└── e2e/ # Full user flow tests
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Unit Tests
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// tests/unit/utils.test.ts
|
|
22
|
+
import { describe, it, expect } from 'vitest';
|
|
23
|
+
import { formatCurrency } from '@/lib/utils';
|
|
24
|
+
|
|
25
|
+
describe('formatCurrency', () => {
|
|
26
|
+
it('formats USD correctly', () => {
|
|
27
|
+
expect(formatCurrency(1999, 'USD')).toBe('$19.99');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('handles zero', () => {
|
|
31
|
+
expect(formatCurrency(0, 'USD')).toBe('$0.00');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API Integration Tests
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// tests/integration/api/users.test.ts
|
|
40
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
41
|
+
import { prisma } from '@/lib/prisma';
|
|
42
|
+
|
|
43
|
+
describe('POST /api/users', () => {
|
|
44
|
+
beforeEach(async () => {
|
|
45
|
+
await prisma.user.deleteMany();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('creates a user with valid data', async () => {
|
|
49
|
+
const res = await fetch('/api/users', {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
body: JSON.stringify({ email: 'test@example.com', name: 'Test' }),
|
|
52
|
+
});
|
|
53
|
+
expect(res.status).toBe(201);
|
|
54
|
+
|
|
55
|
+
const user = await prisma.user.findUnique({ where: { email: 'test@example.com' } });
|
|
56
|
+
expect(user).not.toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('rejects invalid email', async () => {
|
|
60
|
+
const res = await fetch('/api/users', {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
body: JSON.stringify({ email: 'invalid', name: 'Test' }),
|
|
63
|
+
});
|
|
64
|
+
expect(res.status).toBe(400);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## E2E Tests
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// tests/e2e/auth.spec.ts
|
|
73
|
+
import { test, expect } from '@playwright/test';
|
|
74
|
+
|
|
75
|
+
test('user can sign up and access dashboard', async ({ page }) => {
|
|
76
|
+
await page.goto('/signup');
|
|
77
|
+
await page.fill('[name="email"]', 'new@example.com');
|
|
78
|
+
await page.fill('[name="password"]', 'securePassword123');
|
|
79
|
+
await page.click('button[type="submit"]');
|
|
80
|
+
|
|
81
|
+
await expect(page).toHaveURL('/dashboard');
|
|
82
|
+
await expect(page.locator('h1')).toContainText('Welcome');
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Coverage Targets
|
|
87
|
+
|
|
88
|
+
- Business logic: >80%
|
|
89
|
+
- API routes: >70%
|
|
90
|
+
- UI components: >60%
|
|
91
|
+
- E2E critical paths: 100%
|
|
92
|
+
|
|
93
|
+
## What to Test
|
|
94
|
+
|
|
95
|
+
- Happy path (it works)
|
|
96
|
+
- Edge cases (empty, null, max values)
|
|
97
|
+
- Error handling (invalid input, network failure)
|
|
98
|
+
- Authorization (can't access others' data)
|
|
99
|
+
|
|
100
|
+
## What NOT to Test
|
|
101
|
+
|
|
102
|
+
- Third-party libraries (Stripe, NextAuth)
|
|
103
|
+
- Framework internals
|
|
104
|
+
- Trivial getters/setters
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Security Rules
|
|
2
|
+
|
|
3
|
+
## Environment Variables
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# .env.local (NEVER commit)
|
|
7
|
+
DATABASE_URL="postgresql://..."
|
|
8
|
+
NEXTAUTH_SECRET="..." # openssl rand -base64 32
|
|
9
|
+
STRIPE_SECRET_KEY="sk_..."
|
|
10
|
+
STRIPE_WEBHOOK_SECRET="whsec_..."
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- Use `.env.example` with placeholder values
|
|
14
|
+
- Rotate secrets on any suspected breach
|
|
15
|
+
- Different secrets per environment
|
|
16
|
+
|
|
17
|
+
## Input Validation
|
|
18
|
+
|
|
19
|
+
ALWAYS validate at API boundary:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Zod schema
|
|
23
|
+
const userInput = z.object({
|
|
24
|
+
email: z.string().email().max(255),
|
|
25
|
+
name: z.string().min(1).max(100).trim(),
|
|
26
|
+
bio: z.string().max(1000).optional(),
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## SQL Injection Prevention
|
|
31
|
+
|
|
32
|
+
- Use Prisma (parameterized queries by default)
|
|
33
|
+
- Never interpolate user input into raw SQL
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// NEVER do this
|
|
37
|
+
await prisma.$queryRaw`SELECT * FROM users WHERE id = ${userId}`;
|
|
38
|
+
|
|
39
|
+
// Do this instead
|
|
40
|
+
await prisma.user.findUnique({ where: { id: userId } });
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## XSS Prevention
|
|
44
|
+
|
|
45
|
+
- React escapes by default
|
|
46
|
+
- Never use `dangerouslySetInnerHTML` with user content
|
|
47
|
+
- Sanitize HTML if needed: `DOMPurify.sanitize(userHtml)`
|
|
48
|
+
|
|
49
|
+
## CSRF Protection
|
|
50
|
+
|
|
51
|
+
- NextAuth includes CSRF tokens
|
|
52
|
+
- For custom forms, use `next-csrf` or similar
|
|
53
|
+
|
|
54
|
+
## Rate Limiting
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { Ratelimit } from '@upstash/ratelimit';
|
|
58
|
+
import { Redis } from '@upstash/redis';
|
|
59
|
+
|
|
60
|
+
const ratelimit = new Ratelimit({
|
|
61
|
+
redis: Redis.fromEnv(),
|
|
62
|
+
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 req/min
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// In API route
|
|
66
|
+
const { success } = await ratelimit.limit(ip);
|
|
67
|
+
if (!success) {
|
|
68
|
+
return Response.json({ error: 'Rate limited' }, { status: 429 });
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Authorization Checklist
|
|
73
|
+
|
|
74
|
+
- [ ] Verify user is authenticated
|
|
75
|
+
- [ ] Verify user owns the resource
|
|
76
|
+
- [ ] Verify user has required role/permission
|
|
77
|
+
- [ ] Log access attempts
|
|
78
|
+
|
|
79
|
+
## Headers (Next.js)
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// next.config.js
|
|
83
|
+
const securityHeaders = [
|
|
84
|
+
{ key: 'X-Frame-Options', value: 'DENY' },
|
|
85
|
+
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
86
|
+
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
|
87
|
+
];
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Secrets in Code
|
|
91
|
+
|
|
92
|
+
- Never hardcode secrets
|
|
93
|
+
- Use `process.env` only on server
|
|
94
|
+
- Audit with `gitleaks` or similar
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Deployment Rules
|
|
2
|
+
|
|
3
|
+
## Platform: Vercel (or specified)
|
|
4
|
+
|
|
5
|
+
## Environment Setup
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Production: main branch → vercel.com/project
|
|
9
|
+
Staging: staging branch → staging.project.com
|
|
10
|
+
Preview: PR branches → pr-123.project.vercel.app
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Environment Variables
|
|
14
|
+
|
|
15
|
+
Set in Vercel Dashboard:
|
|
16
|
+
- `DATABASE_URL` - Production DB connection
|
|
17
|
+
- `NEXTAUTH_URL` - Full production URL
|
|
18
|
+
- `NEXTAUTH_SECRET` - Production secret
|
|
19
|
+
- `STRIPE_SECRET_KEY` - Live key (not test!)
|
|
20
|
+
- `STRIPE_WEBHOOK_SECRET` - Production webhook secret
|
|
21
|
+
|
|
22
|
+
## Pre-Deploy Checklist
|
|
23
|
+
|
|
24
|
+
- [ ] All tests pass (`npm test`)
|
|
25
|
+
- [ ] Build succeeds (`npm run build`)
|
|
26
|
+
- [ ] No TypeScript errors
|
|
27
|
+
- [ ] No console.log in production code
|
|
28
|
+
- [ ] Environment variables set
|
|
29
|
+
- [ ] Database migrations applied
|
|
30
|
+
|
|
31
|
+
## Database Migrations
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Generate migration
|
|
35
|
+
npx prisma migrate dev --name add_feature
|
|
36
|
+
|
|
37
|
+
# Apply to production (in CI/CD)
|
|
38
|
+
npx prisma migrate deploy
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## CI/CD Pipeline (GitHub Actions)
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
# .github/workflows/deploy.yml
|
|
45
|
+
name: Deploy
|
|
46
|
+
on:
|
|
47
|
+
push:
|
|
48
|
+
branches: [main]
|
|
49
|
+
|
|
50
|
+
jobs:
|
|
51
|
+
test:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: actions/setup-node@v4
|
|
56
|
+
with:
|
|
57
|
+
node-version: '20'
|
|
58
|
+
- run: npm ci
|
|
59
|
+
- run: npm test
|
|
60
|
+
- run: npm run build
|
|
61
|
+
|
|
62
|
+
deploy:
|
|
63
|
+
needs: test
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
steps:
|
|
66
|
+
- uses: amondnet/vercel-action@v25
|
|
67
|
+
with:
|
|
68
|
+
vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
|
69
|
+
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
|
|
70
|
+
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
|
|
71
|
+
vercel-args: '--prod'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Rollback Strategy
|
|
75
|
+
|
|
76
|
+
- Vercel: Instant rollback via dashboard
|
|
77
|
+
- Database: Keep migration rollback scripts
|
|
78
|
+
- Feature flags: Disable without deploy
|
|
79
|
+
|
|
80
|
+
## Monitoring Post-Deploy
|
|
81
|
+
|
|
82
|
+
- Check error rates (Sentry, Vercel)
|
|
83
|
+
- Check performance (Vercel Analytics)
|
|
84
|
+
- Test critical user flows manually
|
|
85
|
+
- Monitor webhooks (Stripe dashboard)
|
|
86
|
+
|
|
87
|
+
## Domain Setup
|
|
88
|
+
|
|
89
|
+
1. Add custom domain in Vercel
|
|
90
|
+
2. Update DNS (CNAME or A record)
|
|
91
|
+
3. Wait for SSL certificate
|
|
92
|
+
4. Update `NEXTAUTH_URL` env var
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Error Handling Rules
|
|
2
|
+
|
|
3
|
+
## Principle
|
|
4
|
+
|
|
5
|
+
Never swallow errors. Always log, always handle, always inform.
|
|
6
|
+
|
|
7
|
+
## API Error Format
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
interface ApiError {
|
|
11
|
+
error: {
|
|
12
|
+
code: string; // Machine-readable: 'VALIDATION_ERROR'
|
|
13
|
+
message: string; // Human-readable: 'Email is required'
|
|
14
|
+
details?: any; // Optional: field-level errors
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Error Codes
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const ErrorCodes = {
|
|
23
|
+
VALIDATION_ERROR: 400,
|
|
24
|
+
UNAUTHORIZED: 401,
|
|
25
|
+
FORBIDDEN: 403,
|
|
26
|
+
NOT_FOUND: 404,
|
|
27
|
+
CONFLICT: 409, // Duplicate resource
|
|
28
|
+
RATE_LIMITED: 429,
|
|
29
|
+
INTERNAL_ERROR: 500,
|
|
30
|
+
} as const;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API Route Pattern
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
export async function POST(req: Request) {
|
|
37
|
+
try {
|
|
38
|
+
const body = await req.json();
|
|
39
|
+
const data = schema.parse(body);
|
|
40
|
+
const result = await service.create(data);
|
|
41
|
+
return Response.json({ data: result }, { status: 201 });
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return handleApiError(error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function handleApiError(error: unknown) {
|
|
48
|
+
if (error instanceof ZodError) {
|
|
49
|
+
return Response.json({
|
|
50
|
+
error: { code: 'VALIDATION_ERROR', message: 'Invalid input', details: error.errors }
|
|
51
|
+
}, { status: 400 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (error instanceof NotFoundError) {
|
|
55
|
+
return Response.json({
|
|
56
|
+
error: { code: 'NOT_FOUND', message: error.message }
|
|
57
|
+
}, { status: 404 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Log unexpected errors
|
|
61
|
+
console.error('Unexpected error:', error);
|
|
62
|
+
|
|
63
|
+
return Response.json({
|
|
64
|
+
error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' }
|
|
65
|
+
}, { status: 500 });
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Client-Side Error Handling
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
async function createTask(data: TaskInput) {
|
|
73
|
+
const res = await fetch('/api/tasks', {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
body: JSON.stringify(data),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
const { error } = await res.json();
|
|
80
|
+
throw new ApiError(error.code, error.message);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return res.json();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// In component
|
|
87
|
+
try {
|
|
88
|
+
await createTask(data);
|
|
89
|
+
toast.success('Task created');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof ApiError) {
|
|
92
|
+
toast.error(error.message);
|
|
93
|
+
} else {
|
|
94
|
+
toast.error('Something went wrong');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Error Boundaries (React)
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
'use client';
|
|
103
|
+
|
|
104
|
+
export function ErrorBoundary({ error, reset }: { error: Error; reset: () => void }) {
|
|
105
|
+
return (
|
|
106
|
+
<div className="error-container">
|
|
107
|
+
<h2>Something went wrong</h2>
|
|
108
|
+
<button onClick={reset}>Try again</button>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Logging
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Use structured logging
|
|
118
|
+
console.error(JSON.stringify({
|
|
119
|
+
level: 'error',
|
|
120
|
+
message: error.message,
|
|
121
|
+
stack: error.stack,
|
|
122
|
+
userId: session?.user?.id,
|
|
123
|
+
requestId: req.headers.get('x-request-id'),
|
|
124
|
+
timestamp: new Date().toISOString(),
|
|
125
|
+
}));
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Sentry Integration
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// sentry.client.config.ts
|
|
132
|
+
Sentry.init({
|
|
133
|
+
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
134
|
+
tracesSampleRate: 0.1,
|
|
135
|
+
ignoreErrors: ['AbortError', 'Network request failed'],
|
|
136
|
+
});
|
|
137
|
+
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Performance Rules
|
|
2
|
+
|
|
3
|
+
## Targets
|
|
4
|
+
|
|
5
|
+
- Time to First Byte (TTFB): <200ms
|
|
6
|
+
- Largest Contentful Paint (LCP): <2.5s
|
|
7
|
+
- First Input Delay (FID): <100ms
|
|
8
|
+
- Cumulative Layout Shift (CLS): <0.1
|
|
9
|
+
- Lighthouse Score: >90
|
|
10
|
+
|
|
11
|
+
## Next.js Optimizations
|
|
12
|
+
|
|
13
|
+
### Images
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import Image from 'next/image';
|
|
17
|
+
|
|
18
|
+
<Image
|
|
19
|
+
src="/hero.jpg"
|
|
20
|
+
alt="Hero"
|
|
21
|
+
width={1200}
|
|
22
|
+
height={600}
|
|
23
|
+
priority // For above-the-fold images
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Fonts
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// app/layout.tsx
|
|
31
|
+
import { Inter } from 'next/font/google';
|
|
32
|
+
|
|
33
|
+
const inter = Inter({ subsets: ['latin'], display: 'swap' });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Dynamic Imports
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import dynamic from 'next/dynamic';
|
|
40
|
+
|
|
41
|
+
const HeavyChart = dynamic(() => import('@/components/Chart'), {
|
|
42
|
+
loading: () => <Skeleton />,
|
|
43
|
+
ssr: false, // Client-only if needed
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Database Performance
|
|
48
|
+
|
|
49
|
+
### Indexes
|
|
50
|
+
|
|
51
|
+
```prisma
|
|
52
|
+
model Task {
|
|
53
|
+
id String @id
|
|
54
|
+
userId String
|
|
55
|
+
status String
|
|
56
|
+
createdAt DateTime
|
|
57
|
+
|
|
58
|
+
@@index([userId, status]) // Composite index for common queries
|
|
59
|
+
@@index([createdAt]) // For sorting
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Query Optimization
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// BAD: N+1 query
|
|
67
|
+
const users = await prisma.user.findMany();
|
|
68
|
+
for (const user of users) {
|
|
69
|
+
const tasks = await prisma.task.findMany({ where: { userId: user.id } });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// GOOD: Include relation
|
|
73
|
+
const users = await prisma.user.findMany({
|
|
74
|
+
include: { tasks: true },
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Pagination
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const tasks = await prisma.task.findMany({
|
|
82
|
+
where: { userId },
|
|
83
|
+
take: 20,
|
|
84
|
+
skip: page * 20,
|
|
85
|
+
orderBy: { createdAt: 'desc' },
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Caching
|
|
90
|
+
|
|
91
|
+
### React Query
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const { data } = useQuery({
|
|
95
|
+
queryKey: ['tasks', userId],
|
|
96
|
+
queryFn: fetchTasks,
|
|
97
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API Response Caching
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// For public data
|
|
105
|
+
export async function GET() {
|
|
106
|
+
const data = await fetchData();
|
|
107
|
+
return Response.json(data, {
|
|
108
|
+
headers: { 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300' },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Bundle Size
|
|
114
|
+
|
|
115
|
+
- Use `next/bundle-analyzer`
|
|
116
|
+
- Tree-shake unused code
|
|
117
|
+
- Import only what you need: `import { format } from 'date-fns'`
|
|
118
|
+
|
|
119
|
+
## Monitoring
|
|
120
|
+
|
|
121
|
+
- Vercel Analytics (Web Vitals)
|
|
122
|
+
- Sentry Performance
|
|
123
|
+
- Custom timing with `performance.mark()`
|