600552c858
Replace the manual "read .claude/agents/*.md" workflow with native Claude Code features for a more efficient, scalable development experience: - **Skills** (.claude/skills/): 7 auto-discovered slash commands (/requirements, /architecture, /frontend, /backend, /qa, /deploy, /help) with forked sub-agents for heavy tasks and inline execution for interactive ones - **Rules** (.claude/rules/): 4 modular rule files (general, frontend, backend, security) auto-applied based on file context - **Sub-Agents** (.claude/agents/): Lightweight configs for frontend-dev, backend-dev, and qa-engineer with model, tool, and turn limit settings - **Context Engineering**: Layered context loading, context isolation via forked skills, built-in context recovery after compaction, and "always read, never guess" rules to prevent hallucinated code references - **CLAUDE.md**: Auto-loaded project context replacing PROJECT_CONTEXT.md - **Feature tracking**: features/INDEX.md as persistent state across sessions - **Production guides**: docs/production/ for error tracking, security, performance, database optimization, and rate limiting - **Init Mode**: /requirements detects empty PRD and bootstraps full project setup (PRD + all feature specs) from a single project description Removed: 6 monolithic agent files, PROJECT_CONTEXT.md, HOW_TO_USE_AGENTS.md, TEMPLATE_CHANGELOG.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2.3 KiB
2.3 KiB
Database Optimization
1. Indexing
Create indexes on columns used in WHERE, ORDER BY, or JOIN clauses:
-- Without index: ~500ms at 100k rows
SELECT * FROM tasks WHERE user_id = 'abc123' ORDER BY created_at DESC;
-- After creating index: <10ms
CREATE INDEX idx_tasks_user_id_created ON tasks(user_id, created_at DESC);
Rule of thumb: If a column appears in WHERE or ORDER BY and the table will have >1000 rows, add an index.
Always include indexes in your migration SQL alongside CREATE TABLE.
2. Avoid N+1 Queries
The most common performance problem with ORMs and query builders:
// Bad: N+1 (1 query for users + N queries for tasks)
const { data: users } = await supabase.from('users').select('*')
for (const user of users) {
const { data: tasks } = await supabase
.from('tasks')
.select('*')
.eq('user_id', user.id)
}
// Good: Single query with join (1 query total)
const { data } = await supabase
.from('users')
.select('*, tasks(*)')
3. Always Limit Results
Never return unbounded results from the database:
// Bad: Returns ALL rows
const { data } = await supabase.from('tasks').select('*')
// Good: Returns max 50 rows
const { data } = await supabase.from('tasks').select('*').limit(50)
// Better: Paginated
const { data } = await supabase
.from('tasks')
.select('*')
.range(0, 49) // First 50 rows
4. Caching Strategy
For data that changes rarely (dashboard stats, config, categories):
import { unstable_cache } from 'next/cache'
export const getCategories = unstable_cache(
async () => {
const { data } = await supabase.from('categories').select('*')
return data
},
['categories'], // Cache key
{ revalidate: 3600 } // Refresh every hour
)
When to cache:
- Data that changes less than once per hour
- Expensive aggregation queries
- Data shared across all users (not user-specific)
When NOT to cache:
- User-specific data that changes frequently
- Real-time data (use Supabase Realtime instead)
5. Select Only What You Need
// Bad: Fetches all columns
const { data } = await supabase.from('users').select('*')
// Good: Fetches only needed columns
const { data } = await supabase.from('users').select('id, name, avatar_url')