Takumi

Performance & Optimization

Reuse Takumi renderers, tune image and glyph caches, and measure cold and repeated renders.

Reuse the renderer

Reuse the renderer
import {  } from "takumi-js";

const  = await (< ="p-8 text-4xl">Hello Takumi</>, {
  : 1200,
  : 630,
});

render and ImageResponse reuse a managed renderer. No cache setup is needed. When using Renderer directly, create it outside the request handler so registered fonts and decoded resources survive between renders.

Know what is cached

WorkLifetimeConfiguration
Decoded images, SVG rasters, parsed stylesheetsRenderercacheMaxBytes, 64 MiB by default
Glyph outlines and rasterized masksLoaded backend modulesetGlyphCacheMaxBytes, 8 MiB by default
Parsed Tailwind classesLoaded backend moduleAutomatic, bounded internally
Resolved Tailwind declarationsRenderAutomatic, separated by viewport width, font size, and pixel ratio
Google Fonts CSSFetch implementationAutomatic, bounded internally
Downloaded image bytesCaller-provided cacheOptional images.fetchCache

Cache budgets account for retained entries, not total process memory. Fonts, active renders, output buffers, and allocator overhead use memory too. See image caching and Google Fonts for resource-specific behavior.

Measure cold and repeated renders

Measure the first render separately from repeated renders. Font downloads and decoding affect startup; layout, painting, and encoding still happen on a warm renderer.

Use representative text, images, output formats, and concurrent requests. Compare latency and memory before changing a budget. Increasing a cache helps only when it keeps work that later renders reuse.

Resource cache

Resource cache
import {  } from "takumi-js/node";

const  = new ({ : 64 * 1024 * 1024 }); 

Raise the budget when frequently reused images or stylesheets are being evicted. For images used only once, cache: "none" avoids displacing reusable entries. An unbounded images.fetchCache is separate from this budget.

Glyph cache

Glyph cache
import {  } from "takumi-js";

(64 * 1024 * 1024); 

Call this before the first render. Outlines and masks share the budget across renderers in the same backend module. Large glyph sets or text sizes can benefit from more space, but benchmark your content first.

Keep memory flat on Linux servers

Each render allocates a few megabytes for its canvas and encoder buffers. On Linux, glibc keeps freed memory in its arenas, so a long-running process holds more than the renderer retains.

Fixed mmap threshold
GLIBC_TUNABLES=glibc.malloc.mmap_threshold=262144 node server.js

glibc raises its mmap threshold after a large block is freed, which moves later canvases into the arenas. A fixed threshold keeps buffers over 256 KiB in their own mappings, and glibc unmaps each one when it is freed.

SettingRSS over 1,275 rendersRender time
Default189–235 MBBaseline
GLIBC_TUNABLES=glibc.malloc.mmap_threshold=262144114–130 MB5–7% slower
MALLOC_ARENA_MAX=2165–211 MB7% slower with 4 renders at once

Both settings apply to the whole process, so measure your own server first. The figures come from Node 24 on Linux arm64, rendering 65 real OG templates four at a time. Alpine and other musl images use a different allocator.

Keep network requests out of repeated work

Bundle fonts for predictable startup without a font service. WOFF2 saves transfer and storage space but needs decompression; TTF avoids that step. A reused renderer decodes a registered file once. See local fonts and CI.

For repeated remote images, use a bounded fetch cache. The renderer's decode cache does not replace a download cache.

Reduce painting work

Each filtered node needs an offscreen layer. Put filters on one node when they should apply to the same composed content. Moving filters from children to a parent can change the output, so compare the result.

Render large canvases

A canvas holds at most 64 megapixels. That is 8192 × 8192, or 4096 × 16384.

A 4K render
import {  } from "takumi-js";

const  = await (< ="p-8 text-4xl">Hello Takumi</>, {
  : 3840,
  : 2160,
});

A larger viewport fails with Invalid viewport dimensions: a canvas holds 1 to 64 megapixels. Each pixel costs 4 bytes, so a full 64 megapixel render holds 256 MiB before encoding.

Painting cost follows the area a node covers, not the canvas. A backdrop-filter blurs only the pixels under its node. A clip mask costs only the clipped box.

Last updated on

On this page