lissy93/web-check · warning · Error

No body element found on the page

Error message

No body element found on the page

What it means

During a Puppeteer screenshot run (dark-mode emulation, domcontentloaded wait), the page is evaluated and document.querySelector('body') returns null, so the in-page script throws. HTML documents always have a body node synthesised by the parser, so this only fires when the navigated document is not HTML (e.g. XML, plain text in some contexts) or the page frame is in an unexpected state after navigation.

Source

Thrown at api/screenshot.js:62

// Fallback to puppeteer when the direct Chromium binary call fails
const puppeteerScreenshot = async (targetUrl) => {
  let browser = null;
  try {
    browser = await puppeteer.launch({
      args: [...chromium.args, '--no-sandbox'],
      defaultViewport: { width: 800, height: 600 },
      executablePath: process.env.CHROME_PATH || (await chromium.executablePath()),
      headless: true,
      acceptInsecureCerts: true,
      ignoreDefaultArgs: ['--disable-extensions'],
    });
    const page = await browser.newPage();
    await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
    page.setDefaultNavigationTimeout(8000);
    await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
    await page.evaluate(() => {
      if (!document.querySelector('body')) {
        throw new Error('No body element found on the page');
      }
    });
    const buffer = await page.screenshot();
    return buffer.toString('base64');
  } finally {
    if (browser) await browser.close().catch(() => {});
  }
};

const screenshotHandler = async (targetUrl) => {
  if (!targetUrl) throw new Error('URL is missing from queryStringParameters');
  try {
    new URL(targetUrl);
  } catch {
    throw new Error('URL provided is invalid');
  }

  log.debug(`request received: ${targetUrl}`);

View on GitHub (pinned to af1a97759f)

Solutions

  1. Verify the target actually serves text/html (curl -I <url>) and fix the URL
  2. Have the caller check Content-Type before requesting a screenshot
  3. If you control this code, treat a missing body as a skip/soft-failure rather than a hard error, as the handler already does for Chromium-missing errors

Example fix

// before
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.screenshot(); // throws 'No body element found on the page' for XML docs

// after
const res = await page.goto(url, { waitUntil: 'domcontentloaded' });
if (!(res.headers()['content-type'] || '').includes('html')) {
  return { skipped: `non-HTML content-type: ${res.headers()['content-type']}` };
}
await page.screenshot();
Defensive patterns

Strategy: fallback

Validate before calling

const res = await fetch(target, { method: 'HEAD', redirect: 'follow' });
const ct = res.headers.get('content-type') || '';
if (!ct.includes('html')) skipScreenshot('non-HTML target');

Type guard

null

Try / catch

try { return { image: await screenshot(url) }; }
catch (e) {
  if (e.message === 'No body element found on the page') return { skipped: 'target is not an HTML page' };
  throw e;
}

Prevention

When it happens

Trigger: Screenshotting a URL that serves application/xml, an RSS/Atom feed, or a non-HTML resource; a redirect chain landing on a non-HTML document; about:blank-ish or prematurely aborted loads where DOMContentloaded fired on a skeleton document.

Common situations: Feeds and API endpoints passed to a screenshot API, XML sitemap URLs, endpoints that sniff content-type differently for headless Chromium's User-Agent.

Related errors


AI-assisted analysis of lissy93/web-check@af1a97759f (2026-08-27). Data as JSON: /api/errors/23b887f0a547d4e7. Report an issue: GitHub.