DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

Screenshot API for Angular: Screenshots, PDFs, and OG Images Without Puppeteer

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
Enter fullscreen mode Exit fullscreen mode

Add your PageBolt API key to .env:

PAGEBOLT_API_KEY=your_api_key_here
Enter fullscreen mode Exit fullscreen mode

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'
};
Enter fullscreen mode Exit fullscreen mode

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' }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode
<button (click)="downloadReport()">Download PDF</button>
Enter fullscreen mode Exit fullscreen mode

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' });
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.'));
    })
  );
}
Enter fullscreen mode Exit fullscreen mode

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)