Improving Pull Request Contributions Chart Performance with Supabase Avatar Caching
In a recent update to our project, I implemented a Supabase-based avatar caching strategy aimed at enhancing the performance of our Pull Request Contributions chart. This change significantly reduces load times and eliminates redundant GitHub API calls for avatars. Here’s a detailed breakdown of the technical implementation, architecture decisions, and the impact of these changes.

The Pull Request Contributions Chart
The Pull Request Contributions chart is a key visualization component in our contributor.info analytics platform. This interactive scatterplot displays pull request activity across repositories, with each point representing a contributor’s PR activity. The chart plots contributors based on metrics like PR count, lines of code changed, or review activity, allowing teams to identify key contributors, collaboration patterns, and contribution trends over time.
Each data point on the scatterplot displays the contributor’s GitHub avatar on hover or selection, providing immediate visual identification. With potentially hundreds of contributors displayed simultaneously, efficiently loading and displaying these avatars became a critical performance consideration.
Purpose and Context
The primary objective of this update was to tackle performance bottlenecks associated with fetching contributor avatars from GitHub’s API. Previously, each request to load avatars in the Pull Request Contributions scatterplot contributed to delayed rendering times and server load. When visualizing repositories with hundreds of contributors, the chart would make individual API calls for each avatar, causing noticeable lag during initial load and interactions. By introducing a caching layer, I aimed to optimize these load times, ultimately enhancing the user experience when exploring contribution patterns.
Technical Implementation Details
Database Migration for Avatar Caching
I began by adding a database migration to our Supabase setup to support avatar caching with a Time-to-Live (TTL) of seven days. This approach ensures that avatar data is periodically refreshed, providing a balance between performance and data accuracy.
-- supabase/migrations/20250821000000_add_avatar_caching.sql
CREATE TABLE avatar_cache (
github_id TEXT PRIMARY KEY,
avatar_url TEXT NOT NULL,
cache_expires_at TIMESTAMP NOT NULL DEFAULT NOW() + INTERVAL '7 days'
);
CREATE INDEX idx_contributors_avatar_cache ON avatar_cache(github_id, cache_expires_at);
Supabase Avatar Cache Service
I developed a comprehensive Supabase avatar cache service capable of handling batch operations. This service is responsible for inserting, updating, and fetching cached avatars efficiently.
// src/lib/supabase-avatar-cache.ts
export async function getCachedAvatars(githubIds: string[]): Promise<Map<string, string>> {
const { data, error } = await supabase
.from('avatar_cache')
.select('github_id, avatar_url')
.in('github_id', githubIds)
.is('cache_expires_at', '>', new Date());
if (error) throw error;
return new Map(data.map((entry) => [entry.github_id, entry.avatar_url]));
}
Cache Hierarchy and Fallbacks
I structured a multi-layer cache hierarchy, prioritizing memory cache, followed by Supabase, and finally localStorage. This strategy ensures that the fastest available data source is used first, with fallbacks to prevent missing avatars.
Progressive Enhancement and Asynchronous Loading
The chart now follows a progressive enhancement model. The scatterplot is displayed immediately, and avatars are loaded asynchronously. This design decision aligns with our project’s invisible UX principles, providing instant feedback while loading additional data in the background.
Error Handling and Identicon Fallbacks
To further enhance reliability, I incorporated robust error handling and GitHub identicon fallbacks. This ensures that even if avatar fetching fails, a default representation is used, maintaining visual consistency.
Key Code Changes
Progressive Loading for Charts
I introduced a ProgressiveChart
wrapper component to facilitate progressive loading, complete with skeleton placeholders and smooth CSS transitions.
// src/components/ui/charts/ProgressiveChart.tsx
export function ProgressiveChart({ data }) {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
// Simulate async loading
setTimeout(() => setLoaded(true), 1000);
}, []);
return (
<div className={`progressive-chart ${loaded ? 'loaded' : 'loading'}`}>
{loaded ? <ActualChart data={data} /> : <Skeleton />}
</div>
);
}
Optimized Avatar Component Usage
I modified several components to utilize an OptimizedAvatar
component, ensuring that all avatar displays benefit from the new caching mechanism.
// src/components/features/activity/activity-item.tsx
import { OptimizedAvatar } from '../../ui/Avatar';
function ActivityItem({ contributor }) {
return (
<div>
<OptimizedAvatar githubId={contributor.githubId} />
{/* Other component logic */}
</div>
);
}
Impact and Benefits
The introduction of Supabase avatar caching has resulted in a 70-80% reduction in avatar load times, leading to a smoother and more responsive user experience. By eliminating redundant GitHub API calls, we have also reduced server load and improved the scalability of our application.
Lessons Learned
- Caching Strategy: Implementing a multi-layer caching strategy can significantly enhance performance, but it requires careful consideration of data consistency and cache expiration policies.
- Progressive Enhancement: Providing immediate visual feedback with progressive loading techniques greatly improves perceived performance, even if actual data loading is delayed.
- Error Handling: Robust error handling and fallback mechanisms are crucial for maintaining application reliability in the face of network issues or service disruptions.
This update not only optimizes performance but also aligns with our goals of delivering a seamless and efficient user experience. As a software engineer, this project reinforced the importance of thoughtful architectural planning and the impact of strategic performance enhancements.
Discuss on Bluesky
Recent replies
So much this! I’ve had AI mention things that I am just not that familiar and felt confident enough to explore. I tend to use it as a pair programmer where I tell it what we are doing. In these instances it’s like the roles are reversed and it is guiding me.
View on Bluesky →