DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

SvelteKit Screenshot API: Capture Pages from Your Svelte Backend

SvelteKit Screenshot API: Capture Pages from Your Svelte Backend

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're back to the same problem: Puppeteer adds 200MB+ of bloat, breaks in serverless, and complicates your deployment.

PageBolt changes that. One HTTPS request. No browser binaries. Works everywhere — Vercel, Netlify, self-hosted, and traditional servers.

Why SvelteKit Developers Need This

Your SvelteKit app might need screenshots for:

  • Dynamic OG images — Social cards generated from user-created content
  • PDF exports — Reports, invoices, or data visualizations from your app
  • Server-side preview — Generate previews before storing URLs
  • AI agent integration — Claude agents need to see pages to make decisions

Without a solution, you're stuck:

  • Puppeteer: 200MB+ bloat, serverless incompatibility, complex Docker builds
  • Sharp + HTML: Only handles simple layouts, CSS support is limited
  • Self-hosted Chrome: Infrastructure overhead, scaling pain, memory management

PageBolt solves this with one HTTPS call from your SvelteKit server routes.


Solution: PageBolt via SvelteKit Server Routes

SvelteKit's +server.ts routes are perfect for API endpoints. Create a reusable route that captures screenshots and returns them to the client.

Step 1: Create Screenshot 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 });
  }

  try {
    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,
      }),
    });

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

    const buffer = await response.arrayBuffer();

    return new Response(buffer, {
      headers: {
        'Content-Type': `image/${format}`,
        'Cache-Control': 'public, max-age=3600',
      },
    });
  } catch (error) {
    return json({ error: 'Internal server error' }, { status: 500 });
  }
};
Enter fullscreen mode Exit fullscreen mode

Step 2: Create PDF 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, margin = '1cm' } = await request.json();

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

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

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

    const buffer = await response.arrayBuffer();

    return new Response(buffer, {
      headers: {
        'Content-Type': 'application/pdf',
        'Content-Disposition': 'attachment; filename="document.pdf"',
      },
    });
  } catch (error) {
    return json({ error: 'Internal server error' }, { status: 500 });
  }
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Use Server Actions for Client-Triggered Captures

Create a server action in src/lib/server/capture.server.ts:

import { fail } from '@sveltejs/kit';

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

export async function captureScreenshot(url: string) {
  try {
    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,
        format: 'png',
      }),
    });

    if (!response.ok) {
      return fail(500, { error: 'Screenshot failed' });
    }

    const buffer = await response.arrayBuffer();
    const base64 = Buffer.from(buffer).toString('base64');

    return {
      success: true,
      image: `data:image/png;base64,${base64}`,
    };
  } catch (error) {
    return fail(500, { error: 'Internal server error' });
  }
}

export async function generatePdf(url: string) {
  try {
    const response = await fetch(`${PAGEBOLT_API}/pdf`, {
      method: 'POST',
      headers: {
        'x-api-key': API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        url,
        landscape: false,
        margin: '1cm',
      }),
    });

    if (!response.ok) {
      return fail(500, { error: 'PDF generation failed' });
    }

    const buffer = await response.arrayBuffer();
    const base64 = Buffer.from(buffer).toString('base64');

    return {
      success: true,
      pdf: `data:application/pdf;base64,${base64}`,
    };
  } catch (error) {
    return fail(500, { error: 'Internal server error' });
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Use in a Page Component

Create src/routes/capture/+page.svelte:

<script>
  import { captureScreenshot, generatePdf } from '$lib/server/capture.server';

  let url = '';
  let screenshot = '';
  let isPending = false;

  async function handleScreenshot() {
    isPending = true;
    try {
      const result = await captureScreenshot(url);
      if (result.success) {
        screenshot = result.image;
      }
    } finally {
      isPending = false;
    }
  }

  async function handlePdf() {
    isPending = true;
    try {
      const result = await generatePdf(url);
      if (result.success) {
        const link = document.createElement('a');
        link.href = result.pdf;
        link.download = 'document.pdf';
        link.click();
      }
    } finally {
      isPending = false;
    }
  }
</script>

<div class="container">
  <h1>Capture Screenshots & PDFs</h1>

  <input
    type="url"
    bind:value={url}
    placeholder="Enter URL"
    disabled={isPending}
  />

  <button on:click={handleScreenshot} disabled={isPending || !url}>
    {isPending ? 'Capturing...' : 'Screenshot'}
  </button>

  <button on:click={handlePdf} disabled={isPending || !url}>
    {isPending ? 'Generating...' : 'Download PDF'}
  </button>

  {#if screenshot}
    <img src={screenshot} alt="Captured screenshot" />
  {/if}
</div>

<style>
  .container {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }

  input {
    width: 100%;
    padding: 0.75rem;
    margin-bottom: 1rem;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  button {
    padding: 0.75rem 1.5rem;
    margin-right: 1rem;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  button:disabled {
    background: #ccc;
    cursor: not-allowed;
  }

  img {
    max-width: 100%;
    margin-top: 2rem;
    border: 1px solid #eee;
    border-radius: 4px;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Step 5: Add Environment Variable

Create .env.local:

PAGEBOLT_API_KEY=YOUR_API_KEY
Enter fullscreen mode Exit fullscreen mode

Real-World Example: AI Agent Visual Verification

Your SvelteKit app integrates Claude agents that need to verify form submissions:

export async function verifyFormSubmission(formUrl: string, userId: string) {
  // Capture proof of submission
  const result = await captureScreenshot(formUrl);

  if (result.success) {
    // Store screenshot in audit log
    await storeAuditProof(userId, result.image);

    // Send to Claude for verification
    const verification = 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-opus-4-5-20251101',
        max_tokens: 1024,
        messages: [
          {
            role: 'user',
            content: [
              {
                type: 'image',
                source: {
                  type: 'base64',
                  media_type: 'image/png',
                  data: result.image.split(',')[1],
                },
              },
              {
                type: 'text',
                text: 'Was this form successfully submitted? Respond with yes or no.',
              },
            ],
          },
        ],
      }),
    });

    return verification;
  }
}
Enter fullscreen mode Exit fullscreen mode

Comparison: PageBolt vs Self-Hosted Puppeteer

Feature PageBolt Puppeteer
Setup time 2 minutes 30+ minutes
Bundle size No bloat +200MB
Vercel/Netlify ✅ Works ❌ No
Maintenance 0 — managed High
Cost (baseline) $29/mo $100+/mo infrastructure
Server actions ✅ Native support ❌ Complex workarounds

Cost Analysis

PageBolt:

  • Free tier: 100 requests/month
  • Starter: $29/month (5,000 requests)
  • Pro: $99/month (50,000 requests)

Self-hosted Puppeteer:

  • Server instance: $20-50/month
  • Database: $10+/month
  • Monitoring: $15+/month
  • Developer time: 10+ hours setup + maintenance
  • Total: $45-75/month + significant time

PageBolt pays for itself in the first month, plus you get 99.9% uptime.


Getting Started

  1. Sign uppagebolt.dev (100 free requests/month, no credit card)
  2. Get API key — Copy from dashboard
  3. Copy the routes above — Paste into your SvelteKit project
  4. Add environment variable — Set PAGEBOLT_API_KEY
  5. Make your first requestPOST /api/screenshot with a URL

Your SvelteKit backend now captures screenshots, generates PDFs, and integrates with AI agents — no browser binaries, no infrastructure overhead.


Try it free — 100 requests/month, no credit card. Start now.

Top comments (0)