Repository navigation
perf: Don't draw what's outside of the image, and render web pages in the playground - #932
Merged
Merged
Conversation
A `/screenshot` page with a URL input renders the page with Satori, like a screenshot of its viewport, through `/api/screenshot`. The page is fetched with its stylesheets and the fonts of its `@font-face` rules that Satori can read, and its HTML is parsed with parse5. Scripts don't run, so pages are rendered as their server sends them. Both are only available in development: the API route returns 404 and the page isn't found otherwise.
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Pages control the URLs of their stylesheets and fonts, so a page could make the development server request private services. Like the images that Satori fetches, every request and every redirect is now validated with Satori's SSRF checks, and must resolve to a public address, unless it's on the origin of the URL that was entered, e.g. a local server. Cached stylesheets and fonts are kept by that origin too.
Boxes, images, outlines and lines of text entirely outside of the image aren't drawn anymore, which makes the SVGs of long content much smaller, and faster to render. Rendered at 1280x800, the SVG of nextjs.org goes from 5.1MB to 0.4MB, and the one of svelte.dev from 32.8MB to 1.4MB. Shadows, outlines and filters are checked with how far they draw outside of boxes. Descendants of transformed elements are always drawn, and so are boxes that define the clip paths or masks of descendants. The nodes outside of the image are still reported to `onNodeDetected`.
shuding
added a commit
that referenced
this pull request
Oct 16, 2026
Two fixes for values that pages ship, found rendering [paco.me](https://paco.me) with the playground's screenshot page (#932). - **Values of `var()` stay apart from what's next to them.** Minifiers leave out spaces after functions, e.g. `grid-template-columns: 192px var(--content-width)192px` or `font: 500 var(--text-xs)/var(--leading-s)var(--font-display)`. Browsers substitute tokens, so the result is still two values, but Satori substituted text, which made `640px192px`. On paco.me, every `font` declaration, the main padding and the grid columns were invalid. - **`env()` is supported,** like on desktops: the safe area and keyboard insets are `0px`, and other variables, e.g. the title bar areas of installed apps, use their fallbacks, or make the declaration invalid without one. Declarations with it are valid until they're computed, like the ones with `var()`. On paco.me, the page margins use `max(24px, env(safe-area-inset-left))`. Both new tests fail without the fix.
The Geist Mono variable was only defined on the wrapper of pages, where the font of the body, which uses it, couldn't see it, so pages fell back to a serif font. The wrapper now sets the font itself.
|
🎉 This PR is included in version 0.60.2 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two changes: Satori doesn't draw what's entirely outside of the image anymore, which makes the SVGs of long content much smaller, and a development-only playground page renders any web page with Satori, like a screenshot of its viewport.
Skip what's outside of the image
Boxes, images, outlines and lines of text entirely outside of the image aren't drawn. Rendered at 1280x800 through the playground page, main vs this PR:
Satori's times include downloading images, and resvg is the time to draw the SVG as a PNG. Rendered with resvg, all of the pixels of nextjs.org, vercel.com, svelte.dev and example.com are the same as with main, and so are all the existing snapshots and fidelity scores.
grayscale(), draw in place, andblur()anddrop-shadow()add to the margin of descendants.text-shadow.onNodeDetected.The new tests compare the image with the top of the same content drawn on a taller image, where nothing is outside.
Playground: render web pages
A
/screenshotpage with a URL input, through/api/screenshot, which fetches the page and returns Satori's SVG.media, and URLs in CSS and of images are made absolute.@font-facerules are loaded for basic Latin, and the ones Satori can't parse, like variable fonts, are skipped. Geist is the fallback.Server-Timingheader has the time of each step, andX-Screenshotwhat was loaded and the style errors. The page shows them, and syncs the URL and size to its query, e.g./screenshot?url=https://vercel.com.NODE_ENVset toproductionandtest.Some results: nextjs.org and vercel.com are close to the pages, without the outlined "NEXT" graphic and the animated hero. On svelte.dev, the icons of the header are too large, and Hacker News breaks, as tables aren't supported. Satori also prints warnings for
top: auto, which is valid CSS.parse5is added to the playground's dependencies.