Icons and social images
A favicon or an apple touch icon is a file in public/ and an icons entry in the layout's metadata.
A favicon or an apple touch icon is a file in public/ and an icons entry in the layout's metadata. A generated
image is an API route that returns an ImageResponse. It draws an element tree with satori (layout) and resvg
(rasterization). Both are optional peer dependencies of @shadow-js/dusk: install satori and @resvg/resvg-wasm
where the image is drawn.
import { h, ImageResponse } from "@shadow-js/dusk/og";
const sans = new Uint8Array(0);
const wasm = new Uint8Array(0);
export const GET = () =>
new ImageResponse(
h(
"div",
{ style: { display: "flex", width: "100%", height: "100%", background: "#0b0b0a", color: "#f1f0ec", padding: 72 } },
h("div", { style: { fontSize: 72 } }, "Example"),
),
{ fonts: [{ name: "Sans", data: sans, weight: 400 }], resvgWasm: wasm },
);
ImageResponse answers image/png at 1200 by 630 by default (width and height change it; each side is a whole
number up to 4096). It uses cache-control: public, max-age=3600, stale-while-revalidate=86400 unless a header is set.
The tree is checked when the response is built: a tag name satori cannot draw, a tree deeper than 200 levels or
larger than 5000 nodes is refused before any drawing. Text needs at least one font (TrueType, OpenType or WOFF;
satori does not read WOFF2). The resvg wasm is the index_bg.wasm of @resvg/resvg-wasm, passed as bytes or a module.
What a tree may hold. Satori fetches every image a tree names while it draws, so a URL that a request can influence would be a request from your server. The tree is therefore checked when the response is built:
- An
imgsrc, anhreforxlink:href, asrcSet, and any CSSurl()(in a style value or atwclass) must be adata:image/...URI (or a#fragmentinside an svg). Anything else is aTypeError. Fetch the image yourself, check it, and pass its bytes as adata:URI.allowRemoteImages: truelets http(s) URLs through for addresses written in code; never use it with a URL built from a path, a query, a header or a database row.file:,javascript:and protocol-relative values stay refused. A CSS escape (\75rl() andimage-set()are refused too. - Text is bounded: 100 000 characters in all, 10 000 in one style or attribute value, and
data:images of 4 MiB each and 8 MiB in all. The tree has at most 5000 nodes and 200 levels. A number must be finite, and a function, symbol or bigint in a prop is refused. resvgWasmis the bytes or the compiled module, never a URL (initWasmwould fetch a string, a URL or aResponse), and the wasm is the one you pass, not one named by the request. Before resvg reads the SVG satori wrote, everyhrefandurl()in it must be adata:image or a#fragment; the check guards a satori version or a custom renderer other than the one tested here.- The text a request controls (a title from
?title=) is safe in the tree: satori turns text into outlines, and a quote or a tag in a style value is escaped in the attribute it ends up in. The answer is always anosniffPNG, and a font'sdatamust be bytes.
The tree is built with h, or with jsx and jsxs from @shadow-js/dusk/og. ShadowJS JSX compiles to DOM
templates, so a .tsx file in a ShadowJS project cannot return an image tree; keep the image in a .ts file.
Non-Latin text: a font stack#
fonts takes several faces, and each glyph is drawn with the first face in the list that has it. A face named in an
element's fontFamily is the first choice for that element, and the rest of the list is the fallback, glyph by glyph. A
text with no fontFamily uses the faces in order. So a Latin face and an Arabic face together draw a title that mixes the
two, and without the Arabic face the Arabic letters are not drawn. The Arabic face must be a static TrueType or OpenType
file: satori reads a font's default instance, and a variable font whose fvar table does not parse is refused by the font
reader. Noto Sans Arabic or Noto Naskh Arabic, Regular weight, are suitable (SIL Open Font License).
A render error after the response starts ends the body with that error. The HTTP status has already been sent and cannot be changed.