HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogUser Avatars in Web Apps: Initials, Gravatar, and Dynamic Generation
Table of Contents▾
  • The Three-Layer Avatar Pattern
  • Layer 1: User Uploads
  • Layer 2: Gravatar
  • Layer 3: SVG Initials Avatar
  • React Avatar Component — All Three Layers
  • Caching and Performance
  • Conclusion
tutorials#avatars#gravatar#svg

User Avatars in Web Apps: Initials, Gravatar, and Dynamic Generation

A profile picture makes an app feel personal. Learn how to implement a robust avatar system with user uploads, Gravatar fallbacks, and SVG initials avatars as the final fallback.

Trong Ngo
March 3, 2026
3 min read

The Three-Layer Avatar Pattern

A robust avatar system has three layers, tried in order:

  1. User-uploaded photo — highest priority, most personal
  2. Gravatar — fetched from the user's email hash; many users already have one
  3. Generated initials avatar — always works, consistent branding

This pattern means zero "broken image" states — the generated avatar is always the fallback of last resort.

Layer 1: User Uploads

// Store in Supabase Storage or S3, return a signed URL
async function uploadAvatar(userId: string, file: File): Promise<string> {
  const ext = file.name.split(".").pop()
  const path = `avatars/${userId}.${ext}`

  const { error } = await supabase.storage
    .from("public")
    .upload(path, file, { upsert: true, contentType: file.type })

  if (error) throw error

  const { data } = supabase.storage.from("public").getPublicUrl(path)
  return data.publicUrl
}

Layer 2: Gravatar

Gravatar links an email address to a profile photo. If the user has a Gravatar, it loads automatically.

import { createHash } from "crypto"  // Node.js; use SubtleCrypto in browser

function gravatarUrl(email: string, size = 80): string {
  const hash = createHash("md5").update(email.toLowerCase().trim()).digest("hex")
  return `https://www.gravatar.com/avatar/${hash}?s=${size}&d=404`
}
// d=404 returns a 404 if the user has no Gravatar (we catch this in the img's onError)

// Browser version using SubtleCrypto
async function gravatarUrlBrowser(email: string, size = 80): Promise<string> {
  const encoder = new TextEncoder()
  const data = encoder.encode(email.toLowerCase().trim())
  const hashBuffer = await crypto.subtle.digest("MD5", data)  // MD5 not available in SubtleCrypto!
  // Use a tiny md5 library for browser:
  // import md5 from "blueimp-md5"
  // return `https://www.gravatar.com/avatar/${md5(email.toLowerCase().trim())}?s=${size}&d=404`
}

Note: SubtleCrypto does not support MD5 (only SHA-256+). For browser Gravatar hashing, use a small MD5 library like blueimp-md5 (1.4KB).

Layer 3: SVG Initials Avatar

function initialsAvatar(name: string, size = 80): string {
  const words = name.trim().split(/\s+/)
  const initials = words.length >= 2
    ? (words[0][0] + words[words.length - 1][0]).toUpperCase()
    : name.slice(0, 2).toUpperCase()

  // Deterministic color from name (consistent across renders)
  const hue = Array.from(name).reduce((acc, c) => acc + c.charCodeAt(0), 0) % 360
  const bg = `hsl(${hue}, 55%, 45%)`

  const svg = [
    `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}">`,
    `<circle cx="${size/2}" cy="${size/2}" r="${size/2}" fill="${bg}"/>`,
    `<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle"`,
    `  font-family="system-ui,-apple-system,sans-serif" font-size="${size * 0.38}"`,
    `  font-weight="600" fill="white">${initials}</text>`,
    `</svg>`
  ].join("")

  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
}

React Avatar Component — All Three Layers

import { useState } from "react"
import md5 from "blueimp-md5"

interface AvatarProps {
  name: string
  email?: string
  photoUrl?: string
  size?: number
  className?: string
}

export function Avatar({ name, email, photoUrl, size = 40, className = "" }: AvatarProps) {
  const [src, setSrc] = useState<string>(
    photoUrl                               // Layer 1: uploaded photo
    ?? (email
      ? `https://www.gravatar.com/avatar/${md5(email.toLowerCase().trim())}?s=${size}&d=404`
      : initialsAvatar(name, size))        // Layer 2: Gravatar, Layer 3: initials
  )

  function handleError() {
    // Gravatar 404 → fall to initials
    setSrc(initialsAvatar(name, size))
  }

  return (
    <img
      src={src}
      onError={handleError}
      alt={name}
      width={size}
      height={size}
      className={`rounded-full object-cover ${className}`}
      style={{ width: size, height: size }}
    />
  )
}

Caching and Performance

// Cache generated SVGs to avoid re-computation on every render
const avatarCache = new Map<string, string>()

function cachedInitialsAvatar(name: string, size: number): string {
  const key = `${name}:${size}`
  if (!avatarCache.has(key)) {
    avatarCache.set(key, initialsAvatar(name, size))
  }
  return avatarCache.get(key)!
}

Preview and download custom initials avatars with HeoLab's Avatar Generator.

Conclusion

The three-layer pattern — user photo → Gravatar → generated initials — ensures every user always has a meaningful avatar while respecting those who have already set up a Gravatar. The SVG initials approach is deterministic (same name always produces the same color), works offline, adds zero bytes to your bundle, and renders crisply at any size.

Try These Tools

Initials Avatar Generator

Generate letter-based SVG avatars from names. Custom colors, shapes, sizes, and bulk generation.

Placeholder Image Generator

Generate SVG placeholder images with custom dimensions, colors, and text. Copy as data URL, HTML, or CSS.

Related Articles

Placeholder Images in Web Development: From Lorem Picsum to SVG Data URIs

3 min read

SVG Optimization: Making Your Icons and Illustrations Faster

3 min read

Next.js App Router: The Complete Guide for 2025

5 min read

Back to Blog

Table of Contents

  • The Three-Layer Avatar Pattern
  • Layer 1: User Uploads
  • Layer 2: Gravatar
  • Layer 3: SVG Initials Avatar
  • React Avatar Component — All Three Layers
  • Caching and Performance
  • Conclusion

Related Articles

Placeholder Images in Web Development: From Lorem Picsum to SVG Data URIs

3 min read

SVG Optimization: Making Your Icons and Illustrations Faster

3 min read

Next.js App Router: The Complete Guide for 2025

5 min read