Back to Blog
Guide February 28, 2026 · 6 min read

How to capture screenshots of every page in a Next.js or Remix app without Puppeteer

Screenshot your full Next.js/Remix app from serverless functions or edge compute without managing Chromium. Works on Vercel, Netlify, and AWS Lambda.

You've built a Next.js or Remix app. Now you need to screenshot every page — for OG images, visual regression tests, or automated reports.

But Puppeteer requires Chromium, which weighs 300MB+ and won't run in Vercel edge functions, Lambda, or other serverless environments. You're stuck either running a separate server just for screenshots or skipping the feature entirely.

PageBolt solves this. It's an API — no local browser required. Call it from your serverless function, edge runtime, or anywhere in your app.

The problem with Puppeteer on Vercel

  1. Cold start timeout — Chromium takes 10-30 seconds to start. Edge functions timeout after 30 seconds total.
  2. Bundle size — Puppeteer + Chromium is 300MB+. Vercel functions have a 250MB limit.
  3. No local filesystem — Serverless functions can't write screenshots to disk, only to S3 or memory.
  4. Complexity — You need a separate service or a self-hosted server just for screenshots.

PageBolt is the serverless-native alternative.

Solution: PageBolt API

Call the PageBolt screenshot endpoint from your Next.js API route. It handles the browser, returns the image, done.

Next.js example

// pages/api/screenshot.js
import { NextResponse } from 'next/server';

const PAGEBOLT_API_KEY = process.env.PAGEBOLT_API_KEY;

export async function POST(request) {
  const { url } = await request.json();

  const response = await fetch('https://pagebolt.dev/api/v1/screenshot', {
    method: 'POST',
    headers: {
      'x-api-key': PAGEBOLT_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: url,
      width: 1200,
      height: 630,
      format: 'png',
    }),
  });

  const data = await response.json();

  // Return the screenshot as base64
  return NextResponse.json({
    screenshot: data.screenshot,
  });
}

Remix example

// routes/api/screenshot.jsx
import { json } from '@remix-run/node';

const PAGEBOLT_API_KEY = process.env.PAGEBOLT_API_KEY;

export async function action({ request }) {
  if (request.method !== 'POST') {
    return json({ error: 'Method not allowed' }, { status: 405 });
  }

  const { url } = await request.json();

  const response = await fetch('https://pagebolt.dev/api/v1/screenshot', {
    method: 'POST',
    headers: {
      'x-api-key': PAGEBOLT_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url: url,
      width: 1200,
      height: 630,
      format: 'png',
    }),
  });

  const data = await response.json();

  return json({ screenshot: data.screenshot });
}

Batch screenshot all pages

If you need to screenshot every page in your app, generate a sitemap first, then batch the requests.

// lib/screenshot-all-pages.js
async function screenshotAllPages() {
  // 1. Fetch your sitemap
  const sitemapResponse = await fetch('https://yourapp.com/sitemap.xml');
  const sitemapXml = await sitemapResponse.text();

  // 2. Parse URLs
  const urls = sitemapXml.match(/<loc>(.*?)<\/loc>/g).map(url =>
    url.replace('<loc>', '').replace('</loc>', '')
  );

  // 3. Screenshot each URL (with concurrency limit)
  const screenshots = [];
  const concurrency = 5;

  for (let i = 0; i < urls.length; i += concurrency) {
    const batch = urls.slice(i, i + concurrency);
    const results = await Promise.all(
      batch.map(url =>
        fetch('https://pagebolt.dev/api/v1/screenshot', {
          method: 'POST',
          headers: {
            'x-api-key': process.env.PAGEBOLT_API_KEY,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            url: url,
            width: 1200,
            height: 630,
            format: 'png',
          }),
        }).then(r => r.json())
      )
    );

    screenshots.push(...results);
    console.log(`Screenshotted ${i + batch.length}/${urls.length} pages`);
  }

  return screenshots;
}

Use cases

OG images on demand — Screenshot each product page, blog post, or user profile and use it as the OG image. No static generation, no design tool needed.

// pages/blog/[slug].jsx
import PageBolt from '@pagebolt/sdk';

export async function getServerSideProps({ params }) {
  const pagebolt = new PageBolt(process.env.PAGEBOLT_API_KEY);

  const screenshot = await pagebolt.screenshot({
    url: `https://yourapp.com/api/og?slug=${params.slug}`,
    width: 1200,
    height: 630,
  });

  return {
    props: { ogImage: screenshot },
  };
}

Visual regression testing in CI — Screenshot your staging app, compare to production baseline.

// scripts/visual-regression.js (run on every deploy)
const baseline = await fetch('/screenshots/baseline.json');
const current = await screenshotAllPages();

const diffs = baseline.pages.map((page, i) => {
  return pixelmatch(baseline[i].data, current[i].data, ...);
});

if (diffs.some(d => d > threshold)) {
  console.error('Visual regression detected!');
  process.exit(1);
}

Automated sitemaps with previews — Generate an HTML page listing all your app's routes with screenshot previews (great for internal dashboards).

Benefits over Puppeteer

Feature Puppeteer PageBolt
Runs in Vercel edge
Bundle size 300MB+ 0 (API call)
Cold start 10-30s <2s
Authentication Requires cookies/session management Built-in session support
Parallel requests Limited by CPU Unlimited
Cost Your infrastructure Pay per screenshot

Getting started

  1. Sign up at pagebolt.dev (free tier: 100 screenshots/month)
  2. Add PAGEBOLT_API_KEY to your .env.local
  3. Copy one of the examples above
  4. Screenshot all your pages

Try it free: 100 requests/month, no credit card required. Get started →


Need more control? Check out the full API docs → or reach out →

Get Started Free

100 requests/month, no credit card

One API key. Zero browser orchestration overhead in your agent's context window.

Get Your Free API Key →