Images & emoji
Load and cache images, and render emoji.
External images
import { } from "takumi-js";
const = < ="https://example.com/image.png" />;
const = new <string, <>>();
const = await (, {
: {
,
},
});Takumi fetches remote images referenced by src, background-image, mask-image, and list-style-image. An optional fetchCache shares downloaded bytes and pending requests across renders. Use Map<string, Promise<ArrayBuffer>> or an object with the same get, set, and delete contract. Values are promises, not raw buffers.
A plain Map never evicts. If each render uses a different URL, pass a bounded cache such as an LRU, or omit fetchCache.
Fetch limits
import { } from "takumi-js";
const = < ="https://example.com/image.png" />;
const = await (, {
: {
: 3000,
: 4 * 1024 * 1024,
: () => new (). === "example.com",
},
});These options apply to Takumi's image fetches, googleFonts, and fontFromUrl. A custom data loader manages its own requests.
| Option | Default | Effect |
|---|---|---|
timeout | 30000 ms | Deadline for each request, including retry delays. 0 or negative disables it. |
signal | none | Cancel a request and any pending retry. |
maxBytes | 32 MiB | Reject oversized bodies, using content-length and streamed bytes. |
allowUrl | allow all | Reject a URL before fetching it, including redirect destinations. |
fetch | globalThis.fetch | Supply a custom fetch implementation. |
GET and HEAD requests make up to three attempts for connection errors, transient timeouts, or HTTP 408, 429, 500, 502, 503, and 504. Retries wait 100 ms, then 200 ms. A Retry-After header can extend that delay up to one second; a longer value returns the error without retrying. Redirects use the same deadline, with a separate attempt limit per destination.
Caller cancellation and an expired deadline stop retries. Other HTTP errors and failures while reading a response body are not retried. A custom fetch must honor the supplied abort signal.
render() and ImageResponse fetch these URLs from your server. Set allowUrl whenever user input can influence the markup. On a cache miss, it checks each redirect destination, with at most five hops. An image byte-cache hit checks only the entry URL: cached values do not retain redirect history. Do not share images.fetchCache across different URL policies or trust boundaries. DNS is not inspected; use a custom fetch for resolved-address filtering.
Pre-fetched images
Provide images by key so the renderer doesn't have to fetch them itself. Each key can be used in any src field or in the background-image / mask-image CSS properties.
import { } from "takumi-js/response";
export function () {
return new (< />, {
: [
{
: "my-logo",
: () => ("/logo.png").(() => .()),
},
{
: "background",
: () => ("/background.png").(() => .()),
},
],
});
}
function () {
return (
<
={{
: "url(background)",
}}
>
< ="my-logo" />
</>
);
}Raw pixels
For pixels you compute yourself (charts, generative art), pass raw RGBA instead of encoding a PNG first; nothing is decoded. The Bitmap helper renders a row-major width * height * 4 byte buffer as an <img>:
import { } from "takumi-js";
import { } from "takumi-js/helpers/jsx";
const = new (64 * 64 * 4);
const = await (< ={64} ={64} ={} />, {
: 64,
: 64,
});Set premultiplied when the bytes are already alpha-premultiplied and the renderer takes them as-is. Outside JSX, an image node's src takes the same { width, height, data, premultiplied? } object.
Decode caching
Takumi caches each decoded image so repeated content is decoded once. Two independent layers exist:
| Layer | Stores | Keyed by | Set with |
|---|---|---|---|
| Byte cache | Fetched bytes | URL | images.fetchCache |
| Decode cache | Decoded pixels | Hash of image bytes | per-image cache / images.cache |
The decode cache lives in the renderer and is bounded: entries weigh in at their decoded size against a byte budget (16 MiB by default), and once it fills the cache evicts the least recently used. A high-cardinality workload (a different image every render) won't grow memory; it misses the cache and decodes each time. SVG sources and their rasterized pixmaps share the same budget, as do parsed stylesheets.
Set the budget when constructing the renderer; 0 disables caching:
const renderer = new Renderer({ cacheMaxBytes: 64 * 1024 * 1024 });The per-image cache mode takes two values:
auto(default): cache the decoded image within the byte budget.none: read the cache but never populate it. Use it for a one-off image you won't render again, so it can't displace images worth keeping.
import { } from "takumi-js/response";
export function () {
return new (< />, {
: [
{
: "banner",
: () => ("/banner.png").(() => .()),
: "none",
},
],
});
}To set the mode for every image in a render, including ones Takumi fetches itself, pass images as a group instead of an array. A per-source cache still wins. For a route where each request renders a different image, cache: "none" keeps one-off decodes out of the cache:
import { } from "takumi-js/response";
export function () {
return new (< />, {
: {
: "none",
: [{ : "logo", : () => ("/logo.png").(() => .()) }],
},
});
}
function () {
return < ="logo" />;
}Decode limits
Takumi rejects oversized images at decode time. The limits guard server renders against decode bombs: small files that declare huge dimensions to force multi-gigabyte allocations.
| Input | Limit |
|---|---|
| Image | 8192×8192 pixels |
| Animated GIF | 4× the image budget, summed over all frames |
Emoji
Dynamic fetching
ImageResponse accepts a satori-compatible emoji option. The providers are twemoji, blobmoji, noto, openmoji, fluent, and fluentFlat.
import { } from "takumi-js/response";
export function () {
return new (< ="flex justify-center items-center text-3xl">Hello 👋😁</>, {
: "twemoji",
});
}extractEmojis follows the Unicode presentation rules. A pictograph that defaults to text presentation, such as ‼ or ▶, stays text and renders with your fonts. Add U+FE0F after it to force the emoji image, or U+FE0E after an emoji to keep the text glyph.
Manual extraction
ImageResponse calls the extractEmojis helper for you. The helper splits emoji from text into image nodes pointing at a CDN. Run the steps yourself to fetch emoji bytes alongside other images.
import { } from "takumi-js/helpers/emoji";
import { } from "takumi-js/helpers/jsx";
import { } from "takumi-js/helpers";
import { } from "takumi-js/node";
let { } = await (< ="flex justify-center items-center text-3xl">Hello 👋😁</>);
= (, "twemoji");
const = await ({ });
const = new ();
const = await .(, {
,
});prepareImages returns image entries for remote URLs missing from sources. It accepts the same fetch cache and limits.
COLR / bitmap fonts
Takumi renders COLR (Color Layer) fonts, the format behind packs like Twemoji-COLR. A COLR file is much smaller than a bitmap emoji font like Noto Color Emoji. The renderer draws emoji from the loaded glyphs, with no network request.
Two steps:
- Register the font through
fonts. - Set
emoji: "from-font".
Variation selectors override font order. A codepoint followed by U+FE0F renders as a color emoji even when a text font appears earlier in the stack. A codepoint followed by U+FE0E renders as a monochrome glyph even when a color font appears first. Bare codepoints follow stack order, as in browsers.
Last updated on