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:
- Dynamic OG images — Social cards generated from user-created content
- PDF exports — Reports, invoices, or data visualizations from your app
- Server-side preview — Generate thumbnails before storing URLs
- AI agent integration — Agents need to see pages to make decisions
The common alternatives all have painful trade-offs:
- Puppeteer: 200MB+ bloat, serverless incompatibility, complex Docker builds
- Sharp + HTML: Only handles simple layouts, limited CSS support
- Self-hosted Chrome: Infrastructure overhead, scaling pain, memory management
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
| Factor | PageBolt API | Puppeteer |
|---|---|---|
| Setup time | ~2 minutes | 30+ minutes |
| Bundle / image bloat | None | +200MB |
| Works on Vercel / Netlify | Yes | No (lambda size limits) |
| Works in SvelteKit form actions | Yes — pure HTTP fetch | Complex, process lifecycle issues |
| Memory per request | <1MB | 50–150MB |
| Maintenance | Zero | Browser crashes, version drift |
Getting Started
- Get a free API key at pagebolt.dev/dashboard — 100 requests/month, no credit card
- Add
PAGEBOLT_API_KEYto.env.local - Copy the
+server.tsroutes above into your project - Call
POST /api/screenshotwith 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