# A React and Next.js avatar component with a real fallback

> Build an <Avatar> that shows a user's real picture from a username, email, or domain, and falls back to initials when there isn't one.

Published 2026-10-19 · https://avatarsapi.com/blog/react-nextjs-avatar-component

Most avatar components fake it: a colored circle with initials, because finding the real picture is someone else's problem. This one shows the real picture when it exists and falls back to initials when it does not, with no backend.

## The component

```tsx
import { useState } from "react"

type AvatarProps = {
  /** e.g. "github/torvalds", "email/jane@stripe.com", "domain/stripe.com" */
  lookup: string
  name: string
  size?: number
}

export function Avatar({ lookup, name, size = 40 }: AvatarProps) {
  const [failed, setFailed] = useState(false)
  const initials = name
    .split(/\s+/)
    .map((part) => part[0] ?? "")
    .join("")
    .slice(0, 2)
    .toUpperCase()

  if (failed) {
    return (
      <span
        aria-label={name}
        style={{
          width: size,
          height: size,
          borderRadius: "50%",
          display: "inline-grid",
          placeItems: "center",
          background: "#e9e4d8",
          fontSize: size * 0.4,
          fontWeight: 600
        }}
      >
        {initials}
      </span>
    )
  }

  return (
    <img
      src={`https://avatarsapi.com/${lookup}?size=${size * 2}`}
      alt={name}
      width={size}
      height={size}
      loading="lazy"
      style={{ borderRadius: "50%", objectFit: "cover" }}
      onError={() => setFailed(true)}
    />
  )
}
```

Usage:

```tsx
<Avatar lookup="github/torvalds" name="Linus Torvalds" />
<Avatar lookup="email/jane@stripe.com" name="Jane Doe" />
<Avatar lookup="domain/stripe.com" name="Stripe" size={24} />
```

Why this works: avatarapi returns a real 404 when nobody has a picture for that Handle, instead of a default silhouette with a 200. So `onError` fires exactly when there is no real image. Requesting `size * 2` keeps it sharp on high-density screens.

## Next.js `<Image>`

If you prefer `next/image`, allow the host in `next.config.js`:

```js
module.exports = {
  images: {
    remotePatterns: [{ protocol: "https", hostname: "avatarsapi.com" }]
  }
}
```

Then swap `<img>` for `<Image>` and keep the same `onError`. Consider `unoptimized`: avatarapi already resizes (`?size=`) and caches at the edge, so routing the image through Next's optimizer adds a second fetch and a second cache without making it smaller.

## Using a Project key

On a free or paid Project, prefix the path with your publishable key. `pk_` keys are safe in client code:

```tsx
src={`https://avatarsapi.com/p/${process.env.NEXT_PUBLIC_AVATARS_KEY}/${lookup}?size=${size * 2}`}
```

Free use, anonymous or on a free Project, requires a visible "Avatars by avatarsapi.com" link where the images appear; Starter removes that. Details are in the [docs](https://avatarsapi.com/docs#plans).

## Related

- [Generated avatar services](/blog/avatar-placeholder-generators) if you want something prettier than initials.
- [All platforms](/platforms/) and their Handle formats.
