Screenshot API for Angular: Screenshots, PDFs, and OG Images Without Puppeteer
If you need to generate PDFs from Angular templates, create screenshot-based reports, or produce Open Graph images server-side, the usual answer is "spin up a Puppeteer instance." That path leads to Docker images that balloon to 1.5 GB, memory leaks in long-running processes, and an ongoing maintenance burden every time Chrome updates.
A screenshot API sidesteps all of that. You POST a URL or HTML, get back a binary. No headless Chrome on your server. This guide covers how to wire that up correctly in an Angular project — and why "correctly" means keeping your API key on the server, not in the browser.
The API Key Problem: Why Not Call Directly from Angular?
Angular apps run in the browser. Any API key you put in an Angular service ends up readable by anyone who opens DevTools. The right pattern is a thin backend proxy: Angular calls your own API endpoint, your server calls PageBolt with the secret key, returns the result.
This is a one-time setup and it applies to any third-party API that uses a secret key.
Project Setup
You need two pieces: an Angular service that calls your backend, and a Node.js/Express backend that calls PageBolt.
Install the backend dependencies in your Express project:
npm install express axios dotenv
Add your PageBolt API key to .env:
PAGEBOLT_API_KEY=your_api_key_here
In your Angular app, define the backend URL in environment files:
// src/environments/environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:3000'
};
// src/environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://your-api.yourapp.com'
};
The Angular Service
// src/app/services/pagebolt.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
@Injectable({ providedIn: 'root' })
export class PageboltService {
private baseUrl = environment.apiUrl;
constructor(private http: HttpClient) {}
downloadPdf(url: string): Observable<Blob> {
return this.http.post(
`${this.baseUrl}/capture/pdf`,
{ url },
{ responseType: 'blob' }
);
}
takeScreenshot(url: string): Observable<Blob> {
return this.http.post(
`${this.baseUrl}/capture/screenshot`,
{ url },
{ responseType: 'blob' }
);
}
}
Register HttpClientModule (or provideHttpClient() in Angular 17+) in your app module or bootstrap config.
The Express Backend Proxy
// server/index.ts
import express from 'express';
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(express.json());
const PAGEBOLT_API = 'https://pagebolt.dev/api/v1';
const API_KEY = process.env.PAGEBOLT_API_KEY!;
app.post('/capture/pdf', async (req, res) => {
const { url, html } = req.body;
try {
const response = await axios.post(
`${PAGEBOLT_API}/pdf`,
{ url, html, pdfOptions: { format: 'A4' } },
{
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
responseType: 'arraybuffer'
}
);
res.set('Content-Type', 'application/pdf');
res.send(response.data);
} catch (err) {
res.status(500).json({ error: 'PDF generation failed' });
}
});
app.post('/capture/screenshot', async (req, res) => {
const { url } = req.body;
try {
const response = await axios.post(
`${PAGEBOLT_API}/screenshot`,
{ url, fullPage: true, blockBanners: true, blockAds: true },
{
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
responseType: 'arraybuffer'
}
);
res.set('Content-Type', 'image/png');
res.send(response.data);
} catch (err) {
res.status(500).json({ error: 'Screenshot failed' });
}
});
app.listen(3000);
Use Case 1: PDF Report Download Button
A common Angular pattern — user clicks "Download Report", Angular calls the backend, gets a PDF blob, triggers a browser download:
// In your component
export class ReportComponent {
constructor(private pagebolt: PageboltService) {}
downloadReport() {
const reportUrl = `https://yourapp.com/reports/${this.reportId}`;
this.pagebolt.downloadPdf(reportUrl).subscribe(blob => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `report-${this.reportId}.pdf`;
link.click();
URL.revokeObjectURL(url);
});
}
}
<button (click)="downloadReport()">Download PDF</button>
You can also pass raw HTML instead of a URL — useful if you want to render an Angular template server-side and convert it:
// Backend: POST /capture/pdf with { html: '<html>...</html>' }
const response = await axios.post(`${PAGEBOLT_API}/pdf`, {
html: `<html><body><h1>Q1 Report</h1><p>Revenue: $${revenue}</p></body></html>`,
pdfOptions: { format: 'A4', margin: { top: '40px', bottom: '40px' } }
}, { headers: { 'x-api-key': API_KEY }, responseType: 'arraybuffer' });
Use Case 2: OG Image Generation Server-Side
Social preview images should be generated on your server (or in a serverless function), not triggered by Angular. When a blog post or product page is created, call PageBolt to render a template and store the result:
// server/og.ts — called on content creation, not on page load
async function generateOgImage(title: string, slug: string): Promise<Buffer> {
const templateUrl = `https://yourapp.com/og-template?title=${encodeURIComponent(title)}`;
const response = await axios.post(
`${PAGEBOLT_API}/screenshot`,
{ url: templateUrl, viewportWidth: 1200, viewportHeight: 630, format: 'jpeg', quality: 90 },
{ headers: { 'x-api-key': API_KEY }, responseType: 'arraybuffer' }
);
return Buffer.from(response.data);
}
The /og-template route is a simple Angular route that renders a full-bleed card layout — no nav, no footer, just the visual you want.
Use Case 3: Visual Regression Testing in CI
Add a post-deploy screenshot step to your GitHub Actions workflow:
# .github/workflows/visual-test.yml
- name: Capture post-deploy screenshot
run: |
curl -s -X POST https://pagebolt.dev/api/v1/screenshot \
-H "x-api-key: ${{ secrets.PAGEBOLT_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"url": "https://staging.yourapp.com", "fullPage": true, "blockBanners": true}' \
--output post-deploy.png
- name: Upload screenshot artifact
uses: actions/upload-artifact@v3
with:
name: visual-snapshot
path: post-deploy.png
Pair it with pixelmatch or resemblejs to diff before/after deploys. The screenshots go through a real Chrome instance, so what you see is what a user actually sees — including fonts, layout shifts, and lazy-loaded images.
Error Handling in the Angular Service
Add basic error handling so the UI can respond gracefully:
import { catchError, throwError } from 'rxjs';
downloadPdf(url: string): Observable<Blob> {
return this.http.post(`${this.baseUrl}/capture/pdf`, { url }, { responseType: 'blob' }).pipe(
catchError(err => {
console.error('PDF generation failed', err);
return throwError(() => new Error('Could not generate PDF. Try again.'));
})
);
}
Free Tier
PageBolt's free tier includes 100 requests per month — no credit card required. That's enough to prototype the full integration, test the PDF and screenshot endpoints, and validate CI snapshots before committing to a paid plan.
Get started: pagebolt.dev
Top comments (0)