/

Product

Major performance gains in the latest release

An upward arrow illuminated against a glowing orange background, symbolizing movement or direction.

Kenji Watanabe

·

Performance improvements rarely make headlines, but they make the difference between software that feels fast and software that feels frustrating. Our latest release cuts average load times by 40%.

Measuring what matters

We instrumented the app to track real usage: page load times, time to interactive, database performance, and API response times.

The data revealed our biggest bottleneck: dashboard loading for projects with extensive history. Teams with 50+ milestones were seeing 8-12 second load_time values.

Database optimization

The project dashboard was making 47 separate queries—a classic N+1 problem that grew worse as projects accumulated data.

-- Before: Multiple queries per milestone
SELECT * FROM milestones WHERE project_id = ?;
SELECT * FROM tasks WHERE milestone_id = ?; -- Repeated N times

-- After: Single optimized query
SELECT m.*, t.* FROM milestones m 
LEFT JOIN tasks t ON m.id = t.milestone_id 
WHERE m.project_id = ?;
-- Before: Multiple queries per milestone
SELECT * FROM milestones WHERE project_id = ?;
SELECT * FROM tasks WHERE milestone_id = ?; -- Repeated N times

-- After: Single optimized query
SELECT m.*, t.* FROM milestones m 
LEFT JOIN tasks t ON m.id = t.milestone_id 
WHERE m.project_id = ?;
-- Before: Multiple queries per milestone
SELECT * FROM milestones WHERE project_id = ?;
SELECT * FROM tasks WHERE milestone_id = ?; -- Repeated N times

-- After: Single optimized query
SELECT m.*, t.* FROM milestones m 
LEFT JOIN tasks t ON m.id = t.milestone_id 
WHERE m.project_id = ?;

This change alone reduced dashboard load times by 60% for large projects.

Frontend optimization

We implemented lazy loading, intelligent caching, and bundle splitting:

// Dynamic imports for features
const ProjectTimeline = lazy(() => 
  import('./components/ProjectTimeline')
);

// Smart caching with invalidation
const useProjectData = (projectId) => {
  return useQuery(['project', projectId], {
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
};
// Dynamic imports for features
const ProjectTimeline = lazy(() => 
  import('./components/ProjectTimeline')
);

// Smart caching with invalidation
const useProjectData = (projectId) => {
  return useQuery(['project', projectId], {
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
};
// Dynamic imports for features
const ProjectTimeline = lazy(() => 
  import('./components/ProjectTimeline')
);

// Smart caching with invalidation
const useProjectData = (projectId) => {
  return useQuery(['project', projectId], {
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
};

API optimization

We implemented field selection to reduce payload sizes:

// Before: Full project object (15KB)
GET /api/projects/123

// After: Selective fields (3KB)  
GET /api/projects/123?fields=name,status,dueDate
// Before: Full project object (15KB)
GET /api/projects/123

// After: Selective fields (3KB)  
GET /api/projects/123?fields=name,status,dueDate
// Before: Full project object (15KB)
GET /api/projects/123

// After: Selective fields (3KB)  
GET /api/projects/123?fields=name,status,dueDate

Plus response compression:

// Compress API responses
app.use(compression({
  level: 6,
  threshold: 1024,
}));
// Compress API responses
app.use(compression({
  level: 6,
  threshold: 1024,
}));
// Compress API responses
app.use(compression({
  level: 6,
  threshold: 1024,
}));

Infrastructure changes

We added Redis caching for frequent queries:

// Cache project summaries
const summary = await redis.get(`project:${id}:summary`);
if (!summary) {
  const fresh = await generateSummary(id);
  await redis.setex(`project:${id}:summary`, 300, fresh);
  return fresh;
}
// Cache project summaries
const summary = await redis.get(`project:${id}:summary`);
if (!summary) {
  const fresh = await generateSummary(id);
  await redis.setex(`project:${id}:summary`, 300, fresh);
  return fresh;
}
// Cache project summaries
const summary = await redis.get(`project:${id}:summary`);
if (!summary) {
  const fresh = await generateSummary(id);
  await redis.setex(`project:${id}:summary`, 300, fresh);
  return fresh;
}

Real world impact

Performance improvements translate to measurable behavior changes:

  • 23% increase in daily usage

  • 31% reduction in page abandonment

  • 15% faster project completion times

  • 89% satisfaction rating for responsiveness (up from 72%)

What we learned

Performance improvements compound. Faster queries enable more responsive interfaces, which encourage frequent usage, revealing additional optimization opportunities.

Users don't consciously notice fast software, but they definitely notice slow software.

Fast software isn't nice to have—it's essential for tools teams use throughout their day.

Kenji Watanabe

·

Create a free website with Framer, the website builder loved by startups, designers and agencies.