Cache Aggressively
Use built-in caching and avoid repeated API calls for the same user data.
Learn how to optimize astro-gravatar components for maximum performance, from caching strategies to lazy loading techniques.
astro-gravatar includes several performance optimizations out of the box:
Profile data is automatically cached during build time and server-side rendering:
---import { getProfile, getApiCacheStats } from 'astro-gravatar';
// Check cache statisticsconsole.log('API Cache:', getApiCacheStats());
// Profile fetched with automatic cachingconst profile = await getProfile('user@example.com', { apiKey: import.meta.env.GRAVATAR_API_KEY});
// Cache stats will show improved performance on subsequent requests---import { GravatarClient } from 'astro-gravatar';
const client = new GravatarClient({ apiKey: 'your-api-key', cache: { ttl: 300, // 5 minutes cache time maxSize: 1000, // Maximum cached items },});---import { clearApiCache, getProfile } from 'astro-gravatar';
// Clear cache if needed (useful for development)if (import.meta.env.DEV) { clearApiCache();}
// Fresh fetch after cache clearconst profile = await getProfile('user@example.com', { apiKey: import.meta.env.GRAVATAR_API_KEY});------import GravatarAvatar from 'astro-gravatar';---
<!-- Above the fold - load immediately --><GravatarAvatar email="visible@example.com" size={100} class="rounded-full"/>
<!-- Below the fold - lazy load --><GravatarAvatar email="hidden@example.com" size={100} lazy={true} class="rounded-full"/>Basic Lazy Loading:
---import GravatarAvatar from 'astro-gravatar';---
<!-- Above the fold - load immediately --><GravatarAvatar email="visible@example.com" size={60} class="rounded-full"/>
<!-- Below the fold - lazy load --><GravatarAvatar email="hidden@example.com" size={60} lazy={true} class="rounded-full"/>Complete Example with Scroll Area:
---import GravatarAvatar from 'astro-gravatar';---
<div style="display: flex; flex-direction: column; gap: 2rem; max-width: 600px; margin: 0 auto;"> <!-- Immediate loading avatar --> <div style="display: flex; align-items: center; gap: 1rem; padding: 1rem; background: var(--sl-color-bg-secondary); border-radius: 0.5rem;"> <GravatarAvatar email="visible@example.com" size={60} class="rounded-full" /> <div> <strong>Immediate Loading</strong> <p style="margin: 0.25rem 0 0 0; font-size: 0.875rem; color: var(--sl-color-text-secondary);"> This avatar loads immediately when the page loads </p> </div> </div>
<!-- Spacer to create scrolling --> <div style="height: 100vh; display: flex; align-items: center; justify-content: center; background: var(--sl-color-bg-accordion); border-radius: 0.5rem;"> <p style="color: var(--sl-color-text-secondary);">Scroll down to see lazy loading</p> </div>
<!-- Lazy loading avatar --> <div style="display: flex; align-items: center; gap: 1rem; padding: 1rem; background: var(--sl-color-bg-secondary); border-radius: 0.5rem;"> <GravatarAvatar email="hidden@example.com" size={60} lazy={true} class="rounded-full" /> <div> <strong>Lazy Loading</strong> <p style="margin: 0.25rem 0 0 0; font-size: 0.875rem; color: var(--sl-color-text-secondary);"> This avatar loads only when scrolled into view </p> </div> </div></div>Lazy Loading Features:
loading="lazy")For advanced lazy loading with custom trigger points:
---import GravatarAvatar from 'astro-gravatar';---<div id="avatar-container"></div>
<script> // Create lazy avatar loader with custom options class LazyAvatarLoader { constructor(container, email, options = {}) { this.container = container; this.email = email; this.options = { size: 80, className: 'rounded-full', rootMargin: '50px', // Start loading 50px before element enters viewport threshold: 0.1, // Start loading when 10% visible ...options }; this.observer = null; this.init(); }
init() { // Create placeholder this.createPlaceholder();
// Setup intersection observer this.observer = new IntersectionObserver( this.handleIntersection.bind(this), { rootMargin: this.options.rootMargin, threshold: this.options.threshold } );
// Start observing the container this.observer.observe(this.container); }
createPlaceholder() { const placeholder = document.createElement('div'); placeholder.className = 'avatar-placeholder'; placeholder.style.cssText = ` width: ${this.options.size}px; height: ${this.options.size}px; background: #f3f4f6; border-radius: ${this.options.className.includes('full') ? '50%' : '0.25rem'}; display: inline-flex; align-items: center; justify-content: center; color: #6b7280; font-size: 0.875rem; transition: opacity 0.3s ease; `; placeholder.innerHTML = 'Loading...'; this.container.appendChild(placeholder); }
handleIntersection(entries) { entries.forEach(entry => { if (entry.isIntersecting) { this.loadAvatar(); this.observer.unobserve(entry.target); } }); }
async loadAvatar() { try { // Remove placeholder const placeholder = this.container.querySelector('.avatar-placeholder'); if (placeholder) { placeholder.style.opacity = '0'; setTimeout(() => placeholder.remove(), 300); }
// Load and create GravatarAvatar const avatar = document.createElement('img'); const emailHash = await this.hashEmail(this.email);
avatar.src = \`https://0.gravatar.com/avatar/\${emailHash}?s=\${this.options.size}\`; avatar.width = this.options.size; avatar.height = this.options.size; avatar.className = \`gravatar-avatar \${this.options.className}\`; avatar.alt = \`Avatar for \${this.email}\`; avatar.style.cssText = 'opacity: 0; transition: opacity 0.3s ease;';
// Add loading animation avatar.onload = () => { avatar.style.opacity = '1'; };
this.container.appendChild(avatar);
// Cleanup this.observer?.disconnect(); } catch (error) { console.error('Failed to load avatar:', error); this.showError(); } }
showError() { const errorElement = document.createElement('div'); errorElement.className = 'avatar-error'; errorElement.style.cssText = \` width: \${this.options.size}px; height: \${this.options.size}px; background: #fef2f2; border: 1px solid #fecaca; border-radius: \${this.options.className.includes('full') ? '50%' : '0.25rem'}; display: flex; align-items: center; justify-content: center; color: #dc2626; font-size: 0.75rem; \`; errorElement.innerHTML = '⚠';
const placeholder = this.container.querySelector('.avatar-placeholder'); if (placeholder) { placeholder.remove(); } this.container.appendChild(errorElement); }
async hashEmail(email) { // Simple email hashing for the example const encoder = new TextEncoder(); const data = encoder.encode(email.toLowerCase().trim()); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; }
destroy() { if (this.observer) { this.observer.disconnect(); this.observer = null; } } }
// Usage example const container = document.getElementById('avatar-container'); const loader = new LazyAvatarLoader(container, 'user@example.com', { size: 120, className: 'rounded-full shadow-lg', rootMargin: '100px', threshold: 0.5 });
// Cleanup on page unload window.addEventListener('beforeunload', () => { loader.destroy(); });</script>---import GravatarAvatar from 'astro-gravatar';---
<GravatarAvatar email="user@example.com" size={100} class="rounded-full" // Automatically generates: // - 1x: 100px // - 1.5x: 150px // - 2x: 200px/>---import GravatarAvatar from 'astro-gravatar';---
<GravatarAvatar email="user@example.com" size={100} class="rounded-full" // Manual control for specific responsive breakpoints srcset="https://0.gravatar.com/avatar/hash?s=50 1x, https://0.gravatar.com/avatar/hash?s=75 1.5x, https://0.gravatar.com/avatar/hash?s=100 2x" sizes="(max-width: 600px) 50px, 100px"/>---import { getProfiles } from 'astro-gravatar';
const emails = [ 'user1@example.com', 'user2@example.com', 'user3@example.com', 'user4@example.com'];
// Fetch all profiles in parallelconst profiles = await getProfiles(emails, { apiKey: import.meta.env.GRAVATAR_API_KEY});---
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem;"> {profiles.map((profile, index) => ( <div key={index} style="padding: 1rem; background: var(--sl-color-bg-secondary); border-radius: 0.5rem;"> <GravatarProfileCard email={profile.email} layout="card" avatarSize={60} showName template="compact" /> </div> ))}</div>---import { getProfile, clearApiCache } from 'astro-gravatar';
// Cache key generationconst getCacheKey = (email, options) => { return `${email}:${options.size || 'default'}:${options.layout || 'card'}`;};
// Enhanced caching functionasync function getCachedProfile(email, options = {}) { const cacheKey = getCacheKey(email, options); const cache = new Map();
// Check cache first if (cache.has(cacheKey)) { const cached = cache.get(cacheKey); if (Date.now() - cached.timestamp < 5 * 60 * 1000) { // 5 minutes return cached.data; } }
// Fetch fresh data const profile = await getProfile(email, { apiKey: import.meta.env.GRAVATAR_API_KEY, ...options });
// Store in cache cache.set(cacheKey, { data: profile, timestamp: Date.now() });
return profile;}
const profile = await getCachedProfile('user@example.com', { layout: 'card', avatarSize: 100});---
<GravatarProfileCard email={profile.email} layout="card" avatarSize={100} showName showBio/>astro-gravatar supports tree-shaking. Import only what you need:
// Good: Import only what you useimport GravatarAvatar from 'astro-gravatar';import { getProfile } from 'astro-gravatar';
// Avoid: Import everythingimport * as Gravatar from 'astro-gravatar';For performance-critical applications, consider component splitting:
---import GravatarAvatar from 'astro-gravatar';---<GravatarAvatar email="user@example.com" size={60} />// utils/avatar.astro (split into separate file)export const ProfileAvatar = ({ email, size = 80, ...props }) => ( <GravatarAvatar email={email} size={size} {...props} />);---import GravatarAvatar from 'astro-gravatar';---<div style="display: flex; gap: 1rem; align-items: center; margin: 2rem 0;"> {/* Skeleton placeholder */} <div class="avatar-skeleton" style="width: 60px; height: 60px; border-radius: 50%; background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%); animation: skeleton-loading 1.5s infinite;"></div>
{/* Real avatar with error handling */} <GravatarAvatar email="user@example.com" size={60} class="rounded-full" onError={(e) => { e.target.style.display = 'none'; e.target.parentElement.querySelector('.avatar-skeleton').style.display = 'block'; }} /></div>
### CSS Example
Create a separate CSS file for skeleton loading:
```css/* src/styles/skeleton.css */@keyframes skeleton-loading { 0% { background-position: -200% 0; } 100% { background-position: calc(200% + 1px) 0; }}
.avatar-skeleton { display: none; /* Hidden when real avatar loads */}Then import it in your Astro component:
---import '../styles/skeleton.css';import GravatarAvatar from 'astro-gravatar';------import GravatarAvatar from 'astro-gravatar';
const emails = ['user1@example.com', 'user2@example.com'];---<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(60px, 1fr)); gap: 1rem; margin: 2rem 0;"> {emails.map(email => ( <GravatarAvatar key={email} email={email} size={60} default="identicon" class="rounded-full" onError={(e) => { // Fallback to default avatar e.target.style.display = 'none'; const fallback = document.createElement('div'); fallback.className = 'avatar-fallback rounded-full'; fallback.style.width = '60px'; fallback.style.height = '60px'; fallback.style.background = `linear-gradient(135deg, #667eea 0%, #764ba2 100%)`; fallback.style.display = 'flex'; fallback.style.alignItems = 'center'; fallback.style.justifyContent = 'center'; fallback.style.color = 'white'; fallback.style.fontWeight = 'bold'; fallback.textContent = email[0].toUpperCase(); e.target.parentNode.insertBefore(fallback, e.target); }} /> ))}</div>
### CSS Example for Fallbacks
```css/* src/styles/avatar-fallback.css */.avatar-fallback { font-size: 1.5rem;}---import '../styles/avatar-fallback.css';import GravatarAvatar from 'astro-gravatar';------import { getApiCacheStats } from 'astro-gravatar';---<div style="padding: 1rem; background: var(--sl-color-bg-secondary); border-radius: 0.5rem; margin: 2rem 0;"> <h3>API Cache Statistics</h3> <pre style="background: var(--sl-color-bg-code); padding: 1rem; border-radius: 0.25rem; overflow-x: auto;">{JSON.stringify(getApiCacheStats(), null, 2)} </pre></div>---// Track component performanceimport { performance } from 'node:perf';
function measureComponent(componentName, renderFn) { const start = performance.now();
const result = renderFn();
const end = performance.now(); const duration = end - start;
console.log(`${componentName} rendered in ${duration.toFixed(2)}ms`);
if (duration > 100) { console.warn(`${componentName} is slow to render (>100ms)`); }
return result;}
const avatar = measureComponent('GravatarAvatar', () => GravatarAvatar.render({ email: 'user@example.com', size: 80 }));---Cache Aggressively
Use built-in caching and avoid repeated API calls for the same user data.
Lazy Load Below Fold
Enable lazy loading for avatars that aren’t immediately visible.
Use Appropriate Sizes
Don’t use unnecessarily large avatar sizes. Stick to 32-200px for optimal performance.
Batch Operations
Fetch multiple profiles in parallel rather than sequentially.