Recipes

Battle-tested patterns. Copy-paste, adjust seeds, ship.

Compose a stable seed from a user object

The single biggest mistake is passing a display name as the seed. Names collide. Use the helper:

import { Navii } from '@usenavii/core';

const s = Navii.seed({
  id: user.id,            // wins if present
  email: user.email,      // fallback
  name: user.name,        // last resort
  createdAt: user.createdAt, // composed with name if id+email missing
});

createAvatar(s);

Why it matters: the seed rule says same seed = same avatar. If two users share a seed they share an avatar. Navii.seed picks the most-unique field automatically.

Photo fallback (Navii when no photoUrl)

Easiest pattern: ?? to fall back to a Navii URL when the user hasn't uploaded:

function Avatar({ user }) {
  const navii = `https://navii-api.uxderrick.com/avatar/${encodeURIComponent(user.id)}?size=64&tileBg=auto`;
  return (
    <img
      src={user.photoUrl ?? navii}
      alt={user.name}
      width={64}
      height={64}
    />
  );
}

If you want the upload to load first and Navii as onError recovery:

<img
  src={user.photoUrl}
  onError={(e) => { e.currentTarget.src = naviiUrl; }}
  alt={user.name}
/>

Server-side rendering (Next.js, Remix, Astro)

The simplest SSR path is the hosted endpoint — zero engine in your bundle, works in every renderer:

// server or client component — no difference
<img src={`https://navii-api.uxderrick.com/avatar/${user.id}?size=64`} />

If you want zero extra HTTP requests (inlining the SVG in your HTML stream), use @usenavii/core on the server and pipe the SVG string into your template. Since the engine is deterministic and pure, server output matches client output — no hydration mismatch.

Don't fire 100 /avatar/* requests. Use /group — one SVG, one request, no rate limit:

const ids = team.map(u => u.id).join(',');
<img src={`https://navii-api.uxderrick.com/group?seeds=${ids}&size=48&overlap=0.3`} alt="team" />

For a multi-row grid use /cast.svg:

<img src={`https://navii-api.uxderrick.com/cast.svg?seeds=${ids}&cols=6&size=80`} />

React Native

No dedicated package needed. Use core + react-native-svg:

import { createAvatar } from '@usenavii/core';
import { SvgXml } from 'react-native-svg';

export function Navii({ seed, size = 64 }) {
  const svg = createAvatar(seed, { size });
  return <SvgXml xml={svg} width={size} height={size} />;
}

Determinism still holds — same SVG in RN as in the browser, byte-identical to the hosted API.

Fixed brand mascot (no seed)

When you want a specific look — logo, empty-state, 404 page — skip the seed and build directly:

import { Navii } from '@usenavii/core';

const heroSvg = Navii.build({
  body: 'tall',
  eyes: 'star',
  mouth: 'grin',
  palette: 'violet',
  topper: 'crown',
}, { size: 256, animated: true });

Or via URL: /build/render?body=tall&eyes=star&palette=violet&topper=crown. Use the builder UI to design visually + copy the params.

Caching your own copies

Responses ship Cache-Control: public, max-age=31536000, immutable. Any layer between you and the API (CDN, browser, service worker) respects that. No extra config needed.

If you do want offline copies:

const svg = await fetch(`https://navii-api.uxderrick.com/avatar/${userId}`).then(r => r.text());
await fs.writeFile(`avatars/${userId}.svg`, svg);

Bytes are deterministic — re-fetching the same seed produces the same content.

Anti-patterns

  • Passing Date.now() as the seed. Avatar changes every render. Determinism is the contract.
  • Passing a display name. Two "Alice"s look identical. Use a stable id.
  • Fetching /avatar/* in a loop without caching. Hit /group or /cast.svg instead.
  • Stripping Cache-Control in your proxy. You'd hammer the origin for no reason.
  • Rasterizing client-side. Use .png URLs if you need PNG; resvg on the server is faster than canvas in the browser.