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.
Loading lots of avatars at once (team list, feed)
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/groupor/cast.svginstead. - Stripping
Cache-Controlin your proxy. You'd hammer the origin for no reason. - Rasterizing client-side. Use
.pngURLs if you need PNG; resvg on the server is faster than canvas in the browser.