How to Fix Cut-Off Right-to-Left Arabic Content in Puppeteer Screenshots
Find out whether Puppeteer’s capture boundary, page layout, or missing Arabic fonts are cutting off RTL content, then apply the fix that fits.
When Arabic text is cut off in a Puppeteer screenshot, first identify where the cut happens: at the image boundary, inside a page element, or in the glyphs themselves. Those point to different causes: screenshot options such as clip, CSS layout and overflow, or Arabic font availability and loading. The fix depends on which boundary is responsible.
This guide uses current Puppeteer screenshot API concepts. Match option behavior to your installed Puppeteer version: older issue discussions describe historical Chromium behavior and are not universal instructions for current releases.
1. Reproduce the same capture
Before changing settings, make the failing result reproducible. Record the target URL, Puppeteer version, browser executable or bundled Chromium version, viewport width and height, device scale factor, and whether the run is local or hosted. Use those same values for each comparison.
Save the failing screenshot and inspect its pixel dimensions. Note whether the missing content ends exactly at an image edge, at a clipping rectangle, or within a box that is otherwise visible. This distinction helps separate capture cropping from page layout and font rendering.
2. Check Puppeteer’s screenshot boundary
page.screenshot() captures a page, while elementHandle.screenshot() captures an element. The fullPage option requests a full-page capture. clip defines a capture rectangle, and captureBeyondViewport controls capture beyond the viewport. Check the reference for your installed version before relying on defaults or a workaround. See the ScreenshotOptions API reference and Puppeteer’s screenshot guide.
const screenshot = await page.screenshot({
path: 'page.png',
fullPage: true,
// Omit clip unless you intentionally want a cropped rectangle.
// Check captureBeyondViewport behavior for your installed version.
});
If you intentionally use clip, confirm that its x, y, width, and height contain the entire desired area. A correct full-page setting cannot restore content excluded by an explicit crop.
There is a historical version detail worth keeping in context: Puppeteer maintainer Mathias Bynens described a Chromium change that made page.screenshot clip elements to the viewport as of Puppeteer v2.0.0. A later comment in that discussion suggested --blink-settings=mainFrameClipsContent=false. Treat that flag as a historical workaround to investigate only when it matches your browser version and evidence, not as a default fix for current Puppeteer. See the Puppeteer screenshot clipping discussion.
3. Separate page layout from screenshot cropping
If the screenshot boundary is correct but text is cut inside the page, inspect the page itself. Arabic direction does not by itself prevent clipping: a narrow fixed-width container, hidden overflow, or viewport-dependent sizing can crop content regardless of text direction.
const layout = await page.evaluate(() => {
const targets = [...document.querySelectorAll('html, body, [dir="rtl"]')];
return {
viewport: { width: innerWidth, height: innerHeight },
document: {
width: document.documentElement.scrollWidth,
height: document.documentElement.scrollHeight,
clientWidth: document.documentElement.clientWidth,
clientHeight: document.documentElement.clientHeight
},
targets: targets.map((el) => {
const rect = el.getBoundingClientRect();
const style = getComputedStyle(el);
return {
tag: el.tagName,
dir: el.getAttribute('dir'),
className: typeof el.className === 'string' ? el.className : '',
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
overflowX: style.overflowX,
overflowY: style.overflowY,
width: style.width,
maxWidth: style.maxWidth,
fontFamily: style.fontFamily,
direction: style.direction
};
})
};
});
console.log(layout);
Use the output to find scrollable overflow and boxes whose dimensions are smaller than their content. Inspect the specific element containing the clipped text as well as its ancestors. Look for overflow: hidden or clip, fixed heights, restrictive widths, and line wrapping rules. If needed, compare with a diagnostic style that temporarily sets the relevant container to overflow: visible and height: auto; do not keep that override unless it is correct for the page.
Check CSS using vw and vh, along with fixed and sticky positioning. Full-page capture and viewport-dependent layout can interact, and changing the viewport may change the layout rather than merely expand the capture. Historical issues document such cases; they do not establish how every current version behaves. See the Puppeteer full-page and viewport sizing issue.
4. Check RTL markup and Arabic font loading
Set direction semantically on the page or relevant content container. For a page that is primarily Arabic, use <html lang="ar" dir="rtl">. For mixed-language content, apply dir="rtl" only to the Arabic region so surrounding left-to-right content keeps its intended direction. Direction sets text flow; it does not provide glyphs or fix a clipped container.
When Arabic letters appear as missing boxes, unexpected fallback glyphs, or a different width than local output, compare the computed font family and available fonts in the local and hosted runtime. Wait for page fonts before capturing, and verify the chosen font actually includes the required Arabic glyphs.
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.evaluate(async () => {
if (document.fonts) await document.fonts.ready;
});
const fontCheck = await page.evaluate(() => {
const el = document.querySelector('[dir="rtl"]');
if (!el) return { found: false };
const style = getComputedStyle(el);
return {
found: true,
fontFamily: style.fontFamily,
direction: style.direction,
text: el.textContent
};
});
console.log(fontCheck);
await page.screenshot({ path: 'arabic.png', fullPage: true });
Use the actual affected selector instead of the generic [dir="rtl"] when the page has more than one RTL region. Compare the font files and browser environment between local and hosted execution. A Puppeteer issue includes one report of Arabic glyphs falling back to monospace on Google Cloud Functions while local output looked correct; that report suggests an environment-specific possibility, not a universal platform defect. See the Arabic glyph rendering issue report.
5. Runnable Puppeteer diagnostic script
This CommonJS example opens a page, waits for network idle and web fonts, records key environment and layout information, and writes a full-page image. Replace the URL and selector with the affected page and Arabic container. Install Puppeteer in your project with npm install puppeteer, then save this as capture.cjs and run node capture.cjs.
const puppeteer = require('puppeteer');
(async () => {
const url = 'https://example.com';
const rtlSelector = '[dir="rtl"]';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({
width: 1440,
height: 1000,
deviceScaleFactor: 1
});
await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 60000
});
await page.evaluate(async () => {
if (document.fonts) await document.fonts.ready;
});
const diagnosis = await page.evaluate((selector) => {
const root = document.documentElement;
const target = document.querySelector(selector);
if (!target) {
return {
found: false,
viewport: { width: innerWidth, height: innerHeight },
document: { width: root.scrollWidth, height: root.scrollHeight }
};
}
const rect = target.getBoundingClientRect();
const style = getComputedStyle(target);
return {
found: true,
viewport: { width: innerWidth, height: innerHeight },
document: {
width: root.scrollWidth,
height: root.scrollHeight,
clientWidth: root.clientWidth,
clientHeight: root.clientHeight
},
target: {
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
direction: style.direction,
fontFamily: style.fontFamily,
overflowX: style.overflowX,
overflowY: style.overflowY,
width: style.width,
height: style.height
}
};
}, rtlSelector);
console.log(JSON.stringify({
puppeteerVersion: require('puppeteer/package.json').version,
browserVersion: await browser.version(),
diagnosis
}, null, 2));
await page.screenshot({
path: 'arabic-full-page.png',
fullPage: true
});
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The script records the package and browser versions because results can vary with the Puppeteer/Chromium build. It does not claim to detect every clipping cause; use the measurements to choose between capture options, CSS, and runtime font investigation.
6. Fix based on what the evidence shows
| What you see | Likely branch | Next action |
|---|---|---|
| Image ends at viewport or clip edge; page content continues | Capture boundary | Review fullPage, remove or correct clip, and check captureBeyondViewport for your version. |
| Text is cut inside a visible page box | CSS layout | Inspect the target and ancestors for fixed dimensions, overflow clipping, width constraints, and viewport units. |
| Arabic glyphs differ or render as fallback only in hosted output | Font/runtime | Wait for font readiness and compare available font files, computed family, and browser runtime between environments. |
| Only element screenshots fail | Element capture | Confirm the selected element and its bounds. Puppeteer may scroll an off-screen element into view before taking its screenshot; compare with a page screenshot and inspect element-specific clipping. |
For element capture, Puppeteer documents that ElementHandle.screenshot() attempts to scroll a hidden element into view. Large element capture and full-page behavior have also appeared in older issue discussions, so check the installed version and reproduce before adopting a workaround. See the ElementHandle.screenshot API reference and the historical clipping discussion.
7. Troubleshooting common failures
| Symptom | Cause to check | Fix |
|---|---|---|
| Full-page capture still misses the bottom or side | An explicit clip, version-specific capture behavior, or content outside the document dimensions |
Remove clip for a full-page diagnostic, log the document dimensions, and verify the installed API reference. |
| Only the Arabic line is truncated within its card | Fixed card height or width, hidden overflow, wrapping rule, or font metrics that differ in the runtime | Inspect the card and ancestors, test a dynamically sized container, and verify the actual Arabic-capable font is loaded. |
| Local screenshot works; hosted screenshot does not | Different browser build, installed fonts, font loading timing, viewport, or device scale factor | Log both versions and viewport settings, await document.fonts.ready, then compare the font inventory and computed styles. |
| Changing viewport fixes one area but breaks another | Responsive rules or vw/vh-based layout changed with the viewport |
Use the intended viewport as the baseline and fix the layout or capture boundary indicated by measurements. |
| Historical Blink flag has no effect | The flag may not apply to the Chromium/Puppeteer version in use | Remove it from the diagnosis path unless a controlled comparison on that exact runtime shows a result; fix current capture settings or page layout instead. |
| Font wait never completes or navigation times out | Network idle may not occur on pages with persistent requests, or a font request may be stalled | Use an appropriate navigation condition for the site, inspect failed font requests, and set a deliberate timeout. Do not mistake a timeout for a screenshot crop. |
8. Performance, reliability, and cost
Full-page images can be much taller and larger than viewport screenshots. Capture only the viewport or target element when that is all the consumer needs; use full-page mode when the missing content is genuinely below or beyond the viewport. Large captures consume more memory and take longer to encode and transfer, so avoid repeating them during diagnosis when a smaller representative capture will answer the question.
For repeatable results, pin the Puppeteer version, use the matching browser build, keep viewport and device scale factor fixed, wait for the relevant content and fonts, and record errors from navigation and resource loading. A font or page request failure can produce a valid image with incorrect content, so a successful screenshot call alone does not prove the page rendered as intended.
Self-hosted Puppeteer cost depends on the compute, storage, and operational work needed to run Chromium; the cited sources do not provide a general cost figure. Measure your own capture size, runtime, retries, and concurrency needs rather than applying an unsupported benchmark.
9. Skip the browser setup with ScreenshotNeo
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call API can return PNG, JPEG, WebP, or PDF, which is useful when you want a rendered capture without maintaining a Puppeteer browser process. For this RTL issue, still check that the page itself renders Arabic correctly; an API cannot repair a site’s CSS or supply a font that the page does not load.
Example request, using the required API pattern with the affected page URL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for the API details. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. Sign up for free and get 1,000 screenshots a month with no card.
FAQ
Should I set dir="rtl" on the whole document?
Only when the document is primarily right-to-left. For a mixed-language page, scope RTL direction to the Arabic content region.
Does fullPage: true fix Arabic rendering?
No. It changes the requested capture extent; it does not correct CSS clipping or missing glyphs.
Is the old Blink flag a recommended permanent fix?
No general recommendation follows from the historical discussion. Verify its behavior against the exact Chromium build before considering it.


