DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to Screenshot Single-Page Applications (React, Vue, Angular) with an API

How to Screenshot Single-Page Applications (React, Vue, Angular) with an API

You're trying to screenshot a React app. Puppeteer launches. The page renders. You get a blank screenshot because the lazy-loaded content hasn't loaded yet.

This is the SPA screenshot problem.

SPAs are everywhere (React, Vue, Angular, Svelte). But they break traditional screenshot tools because the content is rendered by JavaScript, not HTML.

The Problem: Puppeteer Can't Wait Long Enough

Typical Puppeteer approach to screenshot an SPA:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/dashboard');

  // Wait for initial render
  await page.waitForTimeout(3000);

  // But lazy-loaded content still isn't visible
  await page.screenshot({ path: 'screenshot.png' });
  await browser.close();
})();
Enter fullscreen mode Exit fullscreen mode

Issues:

  • You don't know how long to wait. Is 3 seconds enough? 10?
  • Lazy-loaded images below the fold won't load if you don't scroll
  • Scroll triggers new lazy-load events
  • Cookie consent banners appear and cover content
  • No single solution for all SPAs

The Solution: PageBolt with fullPage + blockBanners

One API call. Let PageBolt handle JS rendering and lazy loading.

const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${apiKey}` },
  body: JSON.stringify({
    url: 'https://example.com/dashboard',
    fullPage: true,
    blockBanners: true
  })
});

const buffer = await response.arrayBuffer();
fs.writeFileSync('screenshot.png', Buffer.from(buffer));
Enter fullscreen mode Exit fullscreen mode

That's it. fullPage: true captures the entire rendered page (including lazy-loaded content). blockBanners: true hides consent popups.

Why fullPage Matters for SPAs

SPAs often have:

  • Hero section (viewport)
  • Lazy-loaded content below
  • More lazy-loaded sections as you scroll
  • Dynamic data that loads on interaction

fullPage: true tells PageBolt:

  1. Render the page fully in the browser
  2. Scroll through the entire page
  3. Wait for lazy-load events to fire
  4. Capture everything in one PNG

No guessing about wait times. No manual scrolling logic. Just: "give me the full page."

Full Example: React Dashboard

Screenshot a React dashboard with lazy-loaded charts:

const fs = require('fs');
const apiKey = process.env.PAGEBOLT_API_KEY;

async function screenshotReactApp(url, filename) {
  const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: url,
      fullPage: true,
      blockBanners: true,  // Hide cookie consent
      format: 'png',
      width: 1280,
      height: 720
    })
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }

  const buffer = await response.arrayBuffer();
  fs.writeFileSync(filename, Buffer.from(buffer));
  console.log(`✓ Screenshot saved: ${filename}`);
}

// Usage
screenshotReactApp('https://myapp.vercel.app/dashboard', 'dashboard.png')
  .catch(err => console.error(err.message));
Enter fullscreen mode Exit fullscreen mode

Vue.js Example

Screenshot a Vue 3 SPA with dynamic routes:

async function screenshotVueApp(appUrl) {
  const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` },
    body: JSON.stringify({
      url: appUrl,
      fullPage: true,
      blockBanners: true,
      blockAds: true,  // Also block ads if present
      format: 'png'
    })
  });

  return await response.arrayBuffer();
}

// Screenshot different Vue routes
const routes = [
  'https://myapp.example.com/',
  'https://myapp.example.com/products',
  'https://myapp.example.com/checkout'
];

for (const route of routes) {
  const buffer = await screenshotVueApp(route);
  const filename = route.split('/').pop() || 'home';
  fs.writeFileSync(`${filename}.png`, Buffer.from(buffer));
}
Enter fullscreen mode Exit fullscreen mode

Angular Example

Screenshot an Angular app with async data loading:

async function screenshotAngularApp(url) {
  // Angular apps render with JS — fullPage waits for lazy loading
  const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` },
    body: JSON.stringify({
      url: url,
      fullPage: true,           // Captures dynamically rendered content
      blockBanners: true,       // Hide consent popups
      blockAds: true,           // Remove ad clutter
      width: 1920,              // Desktop screenshot
      height: 1080
    })
  });

  return Buffer.from(await response.arrayBuffer());
}
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

E-commerce Product Pages — Screenshot product detail pages (React/Vue/Angular) with reviews, images, and related products all loaded:

async function captureProductPage(productId) {
  const url = `https://ecommerce.example.com/product/${productId}`;

  const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` },
    body: JSON.stringify({
      url: url,
      fullPage: true,
      blockBanners: true,
      blockAds: true  // Remove ads that cover product info
    })
  });

  return Buffer.from(await response.arrayBuffer());
}
Enter fullscreen mode Exit fullscreen mode

Dashboard Monitoring — Capture full SPA dashboards for reporting:

async function dashboardSnapshot(dashboardUrl) {
  // Lazy-loaded charts, tables, and metrics all render with fullPage
  const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` },
    body: JSON.stringify({
      url: dashboardUrl,
      fullPage: true,      // Get below-the-fold content
      blockBanners: true,  // Hide modals/alerts
      format: 'png'
    })
  });

  return Buffer.from(await response.arrayBuffer());
}
Enter fullscreen mode Exit fullscreen mode

Testing SPA Rendering — Verify that JS-rendered content appears correctly:

async function testSPARendering(testUrl) {
  const screenshot = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${apiKey}` },
    body: JSON.stringify({
      url: testUrl,
      fullPage: true,
      blockBanners: true,
      format: 'png'
    })
  });

  // Use for visual regression testing
  // Compare with baseline to catch rendering bugs
}
Enter fullscreen mode Exit fullscreen mode

Key Differences from Puppeteer

Feature Puppeteer PageBolt API
Lazy loading Manual wait logic Automatic with fullPage: true
Cookie banners Manual dismiss blockBanners: true
Setup Install 300MB dependency One API call
Full page Requires scroll logic Built-in fullPage: true
Works serverless No Yes

Pricing

Plan Requests/Month Cost Use Case
Free 100 $0 Testing SPA screenshots
Starter 5,000 $29 Small SPA monitoring
Growth 25,000 $79 Production SPA dashboards
Scale 100,000 $199 Enterprise SPA monitoring

Summary

Screenshots of SPAs (React, Vue, Angular):

  • fullPage: true captures lazy-loaded content
  • blockBanners: true removes consent popups
  • ✅ No manual wait logic
  • ✅ Works with dynamic routing
  • ✅ 100ms response time

Get started: Try PageBolt free — 100 requests/month, no credit card required →

Top comments (0)