Files
ai-coding-starter-kit/docs/production/database-optimization.md
T
“alexvisualmakers” 600552c858 feat: Migrate from agent markdown files to Skills, Rules, and Sub-Agents
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>
2026-02-13 10:15:27 +01:00

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')