Tutorial Mar 25, 2026

SvelteKit Screenshot API: Capture Pages from Your Svelte Backend

Screenshot and PDF capture via +server.ts routes and server actions. No Puppeteer, no browser binaries — works on Vercel, Netlify, and everywhere SvelteKit deploys.

SvelteKit has become the go-to framework for developers who want simplicity without sacrificing power. Its file-based routing, server-side rendering, and native TypeScript support make it perfect for fullstack applications.

But when you need server-side screenshots or PDF generation, you hit the same wall: Puppeteer adds 200MB+ of bloat, breaks in serverless, and complicates your deployment pipeline.

One HTTPS call fixes this. No browser binaries. Works everywhere — Vercel, Netlify, self-hosted, and traditional servers.

Why SvelteKit Developers Need This

Your SvelteKit app might need screenshots for:

The common alternatives all have painful trade-offs:

Step 1: Screenshot API Route

Create src/routes/api/screenshot/+server.ts:

import { json, type RequestHandler } from '@sveltejs/kit';

const PAGEBOLT_API = 'https://pagebolt.dev/api/v1';
const API_KEY = process.env.PAGEBOLT_API_KEY;

export const POST: RequestHandler = async ({ request }) => {
  const { url, format = 'png', width = 1280, height = 720 } = await request.json();

  if (!url) {
    return json({ error: 'URL is required' }, { status: 400 });
  }

  const response = await fetch(`${PAGEBOLT_API}/screenshot`, {
    method: 'POST',
    headers: {
      'x-api-key': API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url, width, height, format, blockBanners: true }),
  });

  if (!response.ok) {
    return json({ error: 'Screenshot failed' }, { status: 502 });
  }

  const buffer = await response.arrayBuffer();

  return new Response(buffer, {
    headers: {
      'Content-Type': `image/${format}`,
      'Cache-Control': 'public, max-age=3600',
    },
  });
};

Step 2: PDF API Route

Create src/routes/api/pdf/+server.ts:

import { json, type RequestHandler } from '@sveltejs/kit';

const PAGEBOLT_API = 'https://pagebolt.dev/api/v1';
const API_KEY = process.env.PAGEBOLT_API_KEY;

export const POST: RequestHandler = async ({ request }) => {
  const { url, landscape = false } = await request.json();

  if (!url) {
    return json({ error: 'URL is required' }, { status: 400 });
  }

  const response = await fetch(`${PAGEBOLT_API}/pdf`, {
    method: 'POST',
    headers: {
      'x-api-key': API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url, landscape, printBackground: true }),
  });

  if (!response.ok) {
    return json({ error: 'PDF generation failed' }, { status: 502 });
  }

  const buffer = await response.arrayBuffer();

  return new Response(buffer, {
    headers: {
      'Content-Type': 'application/pdf',
      'Content-Disposition': 'attachment; filename="document.pdf"',
    },
  });
};

Step 3: Server Helper for Actions

Create a server-side utility in src/lib/server/capture.ts for use in form actions and load functions:

const PAGEBOLT_API = 'https://pagebolt.dev/api/v1';
const API_KEY = process.env.PAGEBOLT_API_KEY;

export async function captureScreenshot(url: string): Promise<string | null> {
  const response = await fetch(`${PAGEBOLT_API}/screenshot`, {
    method: 'POST',
    headers: {
      'x-api-key': API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url, width: 1280, height: 720, blockBanners: true }),
  });

  if (!response.ok) return null;

  const buffer = await response.arrayBuffer();
  const base64 = Buffer.from(buffer).toString('base64');
  return `data:image/png;base64,${base64}`;
}

export async function generatePdf(url: string): Promise<Buffer | null> {
  const response = await fetch(`${PAGEBOLT_API}/pdf`, {
    method: 'POST',
    headers: {
      'x-api-key': API_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url, printBackground: true }),
  });

  if (!response.ok) return null;
  return Buffer.from(await response.arrayBuffer());
}

Step 4: Use in a Page with Form Actions

Create src/routes/capture/+page.server.ts:

import type { Actions } from './$types';
import { captureScreenshot } from '$lib/server/capture';

export const actions: Actions = {
  screenshot: async ({ request }) => {
    const data = await request.formData();
    const url = data.get('url') as string;

    if (!url) return { error: 'URL required' };

    const image = await captureScreenshot(url);
    if (!image) return { error: 'Capture failed' };

    return { success: true, image };
  },
};

And the component at src/routes/capture/+page.svelte:

<script lang="ts">
  import { enhance } from '$app/forms';
  export let form;
</script>

<form method="POST" action="?/screenshot" use:enhance>
  <input type="url" name="url" placeholder="https://example.com" required />
  <button type="submit">Capture Screenshot</button>
</form>

{#if form?.error}
  <p class="error">{form.error}</p>
{/if}

{#if form?.image}
  <img src={form.image} alt="Page screenshot" />
{/if}

Real-World Example: AI Visual Verification

Capture a page as evidence and pass it to Claude for verification:

import { captureScreenshot } from '$lib/server/capture';

export async function verifyFormSubmission(formUrl: string, userId: string) {
  const image = await captureScreenshot(formUrl);
  if (!image) return { verified: false };

  // Store in audit log
  await storeAuditProof(userId, image);

  // Ask Claude to verify
  const response = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'anthropic-version': '2023-06-01',
      'content-type': 'application/json',
      'x-api-key': process.env.ANTHROPIC_API_KEY!,
    },
    body: JSON.stringify({
      model: 'claude-sonnet-4-6',
      max_tokens: 64,
      messages: [{
        role: 'user',
        content: [
          {
            type: 'image',
            source: {
              type: 'base64',
              media_type: 'image/png',
              data: image.split(',')[1],
            },
          },
          { type: 'text', text: 'Was this form successfully submitted? Answer yes or no.' },
        ],
      }],
    }),
  });

  const result = await response.json();
  return { verified: result.content[0].text.toLowerCase().startsWith('yes') };
}

Environment Setup

Add to .env.local (SvelteKit loads this automatically):

PAGEBOLT_API_KEY=your_key_here

On Vercel or Netlify, add PAGEBOLT_API_KEY as an environment variable in your project settings. No other changes needed.

Comparison: PageBolt vs Puppeteer in SvelteKit

FactorPageBolt APIPuppeteer
Setup time~2 minutes30+ minutes
Bundle / image bloatNone+200MB
Works on Vercel / NetlifyYesNo (lambda size limits)
Works in SvelteKit form actionsYes — pure HTTP fetchComplex, process lifecycle issues
Memory per request<1MB50–150MB
MaintenanceZeroBrowser crashes, version drift

Getting Started

  1. Get a free API key at pagebolt.dev/dashboard — 100 requests/month, no credit card
  2. Add PAGEBOLT_API_KEY to .env.local
  3. Copy the +server.ts routes above into your project
  4. Call POST /api/screenshot with a JSON body: {"url": "https://example.com"}

Add web capture to your SvelteKit app today

PageBolt handles the browser. Your SvelteKit routes stay lean, serverless-compatible, and browser-free.

Get your free API key