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();
})();
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));
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:
- Render the page fully in the browser
- Scroll through the entire page
- Wait for lazy-load events to fire
- 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));
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));
}
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());
}
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());
}
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());
}
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
}
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: truecaptures lazy-loaded content - ✅
blockBanners: trueremoves 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)