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 });
}
};
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 });
}
};
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' });
}
}
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>
Step 5: Add Environment Variable
Create .env.local:
PAGEBOLT_API_KEY=YOUR_API_KEY
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;
}
}
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
- Sign up — pagebolt.dev (100 free requests/month, no credit card)
- Get API key — Copy from dashboard
- Copy the routes above — Paste into your SvelteKit project
-
Add environment variable — Set
PAGEBOLT_API_KEY -
Make your first request —
POST /api/screenshotwith 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)