The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Most html-to-image failures in React come from one of four places: the target element is not ready, an image or font cannot be embedded, SVG foreignObject rendering differs in the browser, or a cross-origin resource or oversized output blocks canvas export. Start by exporting a mounted element from a React ref and handling the promise; then isolate the failing resource or style rather than changing React state at random.
How html-to-image turns a React element into an image
html-to-image does not take a screenshot of the visible browser tab. It clones the selected DOM subtree, copies computed styles, embeds fonts and images, serializes the result as XML inside an SVG foreignObject, and may render that SVG on an off-screen canvas for PNG or pixel output. The project README describes the SVG feature as allowing “arbitrary HTML content inside of the <foreignObject> tag.” Each stage can fail independently, so first determine whether the target, resources, SVG rendering, or final canvas is the problem. See the html-to-image project README for its documented API and behavior.
Start with a mounted React ref and visible errors
Attach a ref to the exact element to export. Do not call the library while the ref is null, and do not discard the returned promise: a rejection is often the most useful clue.
import { useRef } from 'react';
import { toPng } from 'html-to-image';
export function Card() {
const cardRef = useRef(null);
async function downloadCard() {
const node = cardRef.current;
if (!node) return;
try {
const dataUrl = await toPng(node);
const link = document.createElement('a');
link.download = 'card.png';
link.href = dataUrl;
link.click();
} catch (error) {
console.error('Could not export card:', error);
}
}
return (
<>
Shareable card
Content to export
>
);
}
This follows the project’s documented ref-and-promise pattern. If the target contains content loaded asynchronously, trigger export only after that content has mounted and its images and fonts are ready. Compare the visible element with the output; a missing part of the page may never have been present in the cloned target at capture time.
#1 Best Overall
Fix blank or missing images
The library attempts to embed image sources, including CSS background images, before serialization. A resource can display in the ordinary page yet fail during export because its URL is unreachable from the capture context or browser security rules prevent it from being fetched or reused.
- Open the browser developer tools Network panel and inspect the image and background-image requests. Confirm the URLs, response status, redirects, and whether the request completes before export.
- Check the console for fetch, security, or serialization errors. Reduce the target to one image; if that still fails, investigate that image’s origin and delivery rather than React rendering.
- For an image that cannot be fetched, provide a data-URL fallback with
imagePlaceholder. This substitutes a placeholder; it does not repair access to the original resource. - Use
cacheBust: trueonly to test whether a stale cached resource is involved. It appends the current time as a query parameter to resource requests and is not a general cross-origin fix.
Do not treat “enable CORS” as a universal instruction. The server must return suitable access headers, and the resource must be used in a compatible way. A browser security restriction is not necessarily a React state bug.
Fix missing fonts and styles
Font embedding is a separate step from image embedding. The documented font process finds @font-face declarations, fetches font files, base64-encodes them, and adds processed CSS to the cloned node. Verify that the declaration and the font URLs are reachable, and check whether the intended font is actually applied to the live element.
Free tools Windows power users keep installed
One-click scans. No signup required.
- Use
preferredFontFormatwhen a font provider lists multiple formats and you want the embedding step to retain a preferred format. - For repeated exports that use the same font CSS, prepare it with
getFontEmbedCSS()and pass the result asfontEmbedCSSto subsequent captures. - Test stylesheets that rely on CSS
@importseparately. The project issue tracker has an open report titled “Parsing @import in CSS causes style loss”; that report is a reason to isolate imported styles, not proof that every imported stylesheet fails.
Also check computed styles on the target, not only the source stylesheet. CSS selectors that depend on ancestors outside the exported subtree may produce a different appearance when the node is cloned.
Diagnose browser-specific output
The export path relies on SVG foreignObject support and browser image handling. The project README names Chrome, Firefox, and Safari as tested and explicitly says Internet Explorer is unsupported; the version notes in that README are historical, not a current compatibility matrix. The npm package page also notes browser differences.
If output fails only in one browser, reproduce it with a small component in the actual browser, operating system, and version where it occurs. The issue tracker contains a report titled “html-to-image not working on Safari,” but an individual report does not establish that Safari is universally unsupported or that every Safari user is affected. Compare a minimal plain-text element with the full component, then add the styles and resources back a piece at a time.
Check canvas security and export dimensions
If the target contains a canvas, such as a chart or drawing surface, the project warns that a tainted canvas can prevent rendering. Isolate that canvas and inspect any cross-origin inputs it uses. This is a browser origin-security constraint, not necessarily a defect in React state or the chart library.
Recommended Free Tools
For clipping, unexpectedly small output, or memory pressure, distinguish element dimensions from canvas dimensions:
Rank #3
widthandheightapply dimensions to the node before rendering.canvasWidthandcanvasHeightscale the canvas and the elements inside it.pixelRatiocontrols the image pixel ratio and defaults to the device ratio.skipAutoScalebypasses automatic scaling for very large DOMs, but the README warns that very large output may lose image content.
Increase output dimensions in small steps and compare the result. Do not assume the largest requested capture is supported; data-URI limits vary, and dimensions can increase memory use substantially.
Isolate problematic CSS or markup
When a single visual effect breaks the export, remove features one at a time in a minimal reproduction. The issue tracker includes reports titled “repeating-linear-gradient acts like linear-gradient (CSS),” “Clip-path URLs with absolute same-document references break in exported images,” and “Node contains illegal XML comment node export fail.” Issue titles record reports, not confirmed universal limitations, so reproduce against the dependency version and browser you use.
- Use the
filteroption to exclude a node and its descendants, which can help identify whether a particular subtree is involved. - Use
styleto override styles on the cloned root when a root-level style causes an unwanted result. - Use
includeStylePropertiesto limit copied style properties where performance or style-copying scope matters.
These options help narrow or shape an export; none guarantees a fix for every malformed XML node or unsupported CSS feature.
Choose the output method and options that match the job
The library provides promise-based methods for different results. Select one deliberately rather than converting formats after capture without a reason.
Rank #4
| Method or option | What it does | Useful when |
|---|---|---|
toPng |
Returns a PNG data URL. | You need a lossless raster image or a browser download. |
toJpeg |
Returns a JPEG data URL; quality from 0 to 1 controls JPEG quality. |
You need a compressed photograph-like output. |
toSvg |
Returns an SVG data URL. | You need the serialized SVG representation. |
toBlob |
Returns image output as a Blob; type selects the image MIME type and PNG is the default. |
You want a Blob for upload or object-URL handling. |
toCanvas |
Returns a rendered canvas. | You need to draw further or inspect canvas pixels. |
toPixelData |
Returns pixel data. | You need pixel-level processing rather than a downloadable file. |
backgroundColor |
Sets the output background color. | The target is transparent but the desired format or viewer needs a solid backdrop. |
imagePlaceholder |
Provides a data URL for an image whose fetch fails. | A missing image should be represented by a fallback graphic. |
preferredFontFormat, fontEmbedCSS |
Control font-format selection or reuse prepared font CSS. | Fonts are missing or repeated exports need font embedding. |
For the full option definitions and method signatures, consult the project README. Treat output format and dimensions as part of the debugging variables: begin with a small PNG at ordinary dimensions, then vary one option at a time.
Common failures and fixes
| Symptom | Likely stage | First check |
|---|---|---|
| Blank output or rejected promise | Target, resource loading, or SVG/canvas render | Confirm the ref is non-null; log the caught error; try a text-only target. |
| Images absent but text visible | Image fetch or origin security | Inspect image requests and test one image by itself; use imagePlaceholder only as fallback. |
| Fallback font or altered text layout | Font embedding or CSS | Verify reachable @font-face URLs and test font embedding options. |
| Works in one browser but not another | foreignObject or browser-specific rendering |
Reproduce in the affected browser and reduce to a minimal element. |
| Fails when a chart is included | Tainted canvas | Isolate the chart canvas and investigate its cross-origin inputs. |
| Large output is clipped or incomplete | Scaling, data URI limits, or resource use | Reduce dimensions; compare width/height with canvas dimensions; test skipAutoScale cautiously. |
| One gradient, clip-path, or comment breaks output | CSS/XML serialization edge case | Remove that feature in a minimal reproduction and check the matching issue report. |
Or skip the browser setup
If your goal is a screenshot of a URL rather than exporting a React component’s DOM, a screenshot API avoids configuring this client-side rendering pipeline. ScreenshotNeo is a website screenshot API and MCP server; its clean-shot flow accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.
One GET request returns an image or PDF. The following cURL example saves a WebP image; replace the target URL and key with your own values. See the ScreenshotNeo documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. A URL screenshot API is not a substitute for html-to-image when you specifically need a React component or unsaved client-side state: it captures a URL, not an arbitrary in-memory DOM node.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Best Value
Reliability and cost considerations
For in-app exports, the client library avoids a remote screenshot request, but your result still depends on the mounted DOM, fetchable resources, browser support, and output size. For automated URL captures, an API can simplify browser setup, but evaluate what counts as a billable result, what failure information the response exposes, and whether URL capture matches your need. ScreenshotNeo’s stated pricing is monthly: Free, 1,000; Starter, $5 for 3,000; Growth, $15 for 15,000; Pro, $39 for 60,000; Scale, $99 for 250,000; Business, $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. These are ScreenshotNeo plan terms, not a performance comparison with the React library.
Frequently asked questions
Can html-to-image export a React component that is not in the DOM?
No. It accepts a DOM node. Render the component into the document first and pass the mounted node, typically through a ref.
Does cacheBust solve CORS problems?
No. It changes resource URLs by appending a current-time query parameter to test cache behavior; it does not grant cross-origin access.
Can I use ScreenshotNeo to capture a chart that exists only in React state?
Not as an arbitrary DOM-node export. ScreenshotNeo captures a URL, so the chart must be rendered on a page that the service can request.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

