Production-Ready HTML to PDF Microservice
Serverless β’ Scalable β’ Lightning Fast
π Quick Start β’ π Documentation β’ π― Deploy β’ π¬ Support
WebAssets PDF Generator is a production-grade, Dockerized microservice that converts HTML pages to high-quality PDFs using Chrome/Chromium rendering engine. Built for reliability and performance, it handles everything from simple invoices to complex, multi-page reports with pixel-perfect accuracy.
- π¨ Pixel-Perfect Rendering - Uses real Chrome engine for accurate PDF generation
- β‘ Blazing Fast - Optimized Docker image with <1 minute cold starts
- π Secure by Default - API key authentication and isolated execution
- π Deploy Anywhere - Run on Render, Koyeb, Railway, AWS, or any Docker platform
- π± Responsive Support - Generates PDFs from mobile-optimized pages
- π― Zero Configuration - Works out of the box with sensible defaults
- π¦ Concurrent Job Management - Limits simultaneous PDF generations (max 2 on free tier)
- π Fair Job Queuing - First-come-first-served processing prevents crashes
- β±οΈ Queue Wait Tracking - Monitor how long jobs wait before processing
- πΎ Job History - Stores last 100 jobs with full metrics
- π Automatic Retries - Graceful failure handling with detailed error logs
- π Real-time Queue Status - See jobs waiting, processing, and completed
- π Daily Analytics - Success rates, average duration, file sizes
- π½ Memory Tracking - Per-job memory usage monitoring
- π Performance Metrics - Detailed render times and queue wait statistics
β οΈ Error Logging - Comprehensive error tracking per job- π Auto-refresh - Dashboard updates every 5 seconds
| Industry | Application |
|---|---|
| π’ SaaS Platforms | Generate invoices, reports, contracts |
| Create booking confirmations, itineraries, vouchers | |
| π₯ Healthcare | Export patient records, prescriptions, lab reports |
| π Analytics Dashboards | Convert charts and graphs to printable PDFs |
| π§ Email Marketing | Archive campaigns as PDF backups |
| π Education | Generate certificates, transcripts, course materials |
- Docker installed on your system
- Node.js 18+ (for local development)
# Clone the repository
git clone https://github.com/Tactition/webassets-pdf-generator.git
cd webassets-pdf-generator
# Build the Docker image
docker build -t pdf-generator .
# Run the container
docker run -p 3000:3000 \
-e PDF_SECRET_KEY=your_secret_key_here \
pdf-generatorVisit http://localhost:3000 to see the dashboard!
# Install dependencies
npm install
# Set environment variable
export PDF_SECRET_KEY=your_secret_key_here
# Start the server
npm startEndpoint: POST /generate
Request:
{
"targetUrl": "https://example.com/invoice?id=123",
"secretKey": "your_secret_key_here",
"filename": "invoice_123.pdf"
}Response: Job queued (immediate response)
{
"status": "queued",
"jobId": "abc-123-def-456",
"position": 3,
"estimatedWait": "~12 seconds",
"statusUrl": "/status/abc-123-def-456",
"downloadUrl": "/download/abc-123-def-456"
}Endpoint: GET /status/:jobId
Response (Queued):
{
"status": "queued",
"message": "Job is in queue or processing"
}Response (Completed):
{
"status": "completed",
"jobId": "abc-123-def-456",
"filename": "invoice_123.pdf",
"duration": 3.24,
"fileSize": 245678,
"memoryUsed": 128
}Response (Failed):
{
"status": "failed",
"error": "net::ERR_CONNECTION_REFUSED at http://..."
}Endpoint: GET /download/:jobId
Response: Binary PDF file (available for 5 minutes after completion)
Status Codes:
200- PDF ready, downloading404- Job not found or PDF expired202- Job still processing, try again later
# Step 1: Submit job
JOB_RESPONSE=$(curl -X POST https://your-service.com/generate \
-H "Content-Type: application/json" \
-d '{
"targetUrl": "https://example.com/invoice",
"secretKey": "your_secret_key_here",
"filename": "invoice.pdf"
}')
# Extract job ID
JOB_ID=$(echo $JOB_RESPONSE | jq -r '.jobId')
# Step 2: Poll for completion
while true; do
STATUS=$(curl -s https://your-service.com/status/$JOB_ID | jq -r '.status')
if [ "$STATUS" == "completed" ]; then
break
elif [ "$STATUS" == "failed" ]; then
echo "Job failed"
exit 1
fi
sleep 1
done
# Step 3: Download PDF
curl https://your-service.com/download/$JOB_ID --output invoice.pdfEndpoint: GET /health
Response:
{
"status": "healthy",
"uptime": 12345,
"queue": {
"size": 3,
"pending": 2
}
}The service includes a production-grade monitoring dashboard built with Tailwind CSS:
- π Queue Size - Jobs waiting to be processed
- βοΈ Active Jobs - Currently processing (X/2)
- β Success Rate - Percentage of successful completions
- β Failed Today - Count of failed jobs
- β±οΈ Avg Duration - Average PDF generation time
- π¦ Avg File Size - Average output PDF size
- β Completed Jobs - Total successful today
- πΎ Current Memory - Real-time memory usage
Each job displays:
- β° Timestamp
- β /β Status (Success/Failed)
- π Filename
- β±οΈ Duration
- π¦ File Size
- π½ Memory Used
- β³ Queue Wait Time
β οΈ Error Message (if failed)
Dashboard auto-refreshes every 5 seconds to show live updates.
- Fork this repository
- Go to render.com β New Web Service
- Connect your GitHub repository
- Render auto-detects the
Dockerfile - Set environment variable:
PDF_SECRET_KEY - Click Create Web Service
β
Done! Your service will be live at https://your-app.onrender.com
- Push this repo to GitHub
- Go to koyeb.com β Create App
- Select GitHub β Choose your repository
- Build method: Docker
- Port:
3000 - Set environment:
PDF_SECRET_KEY - Deploy!
- Visit railway.app
- Click New Project β Deploy from GitHub
- Railway auto-detects Dockerfile
- Add environment variable:
PDF_SECRET_KEY - Get your URL from the dashboard
| Variable | Required | Default | Description |
|---|---|---|---|
PDF_SECRET_KEY |
β Yes | - | Authentication key for API requests |
PORT |
β No | 3000 |
Server port |
The queue system is configured in server.js with these defaults:
| Setting | Value | Description |
|---|---|---|
| Concurrency | 2 |
Max simultaneous PDF generations |
| Timeout | 60000ms |
Max time per job (60 seconds) |
| Job Retention | 5 minutes |
How long completed PDFs are cached |
| History Size | 100 jobs |
Max jobs stored in memory |
To modify queue settings, edit server.js:
const pdfQueue = new PQueue({
concurrency: 2, // Increase for more powerful servers
timeout: 60000, // Adjust timeout as needed
throwOnTimeout: true
});
β οΈ Note: On free-tier hosting, keep concurrency at 2 to prevent memory exhaustion.
Customize PDF generation by modifying server.js:
const pdfBuffer = await page.pdf({
format: 'A4', // A4, Letter, Legal, A3
printBackground: true, // Include CSS backgrounds
margin: { // Page margins
top: '10mm',
right: '10mm',
bottom: '10mm',
left: '10mm'
},
displayHeaderFooter: true,
headerTemplate: '<div>Custom Header</div>',
footerTemplate: '<div>Page <span class="pageNumber"></span></div>'
});$pdf_url = 'https://your-service.com/generate';
$data = json_encode([
'targetUrl' => 'https://example.com/invoice',
'secretKey' => 'your_secret_key_here',
'filename' => 'invoice.pdf'
]);
$ch = curl_init($pdf_url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$pdf_binary = curl_exec($ch);
curl_close($ch);
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="invoice.pdf"');
echo $pdf_binary;import requests
response = requests.post('https://your-service.com/generate', json={
'targetUrl': 'https://example.com/page',
'secretKey': 'your_secret_key_here',
'filename': 'output.pdf'
})
with open('output.pdf', 'wb') as f:
f.write(response.content)const axios = require('axios');
const fs = require('fs');
const response = await axios.post('https://your-service.com/generate', {
targetUrl: 'https://example.com/page',
secretKey: 'your_secret_key_here',
filename: 'output.pdf'
}, {
responseType: 'arraybuffer'
});
fs.writeFileSync('output.pdf', response.data);This service uses the official Puppeteer Docker image which includes:
- β Pre-installed Chromium browser
- β All required system dependencies
- β Optimized for <1 minute builds
- β Regular security updates
Image Size: ~200MB (compressed)
Build Time: ~60 seconds
Cold Start: ~5-10 seconds
- Use Strong Secret Keys: Generate random 32+ character keys
- Enable HTTPS: Always use SSL for production deployments
- Rate Limiting: Implement rate limiting on your reverse proxy
- Whitelist IPs: Restrict access to known IP addresses if possible
- Monitor Logs: Watch for suspicious activity in dashboard
| Metric | Free Tier | Paid Tier |
|---|---|---|
| Max Concurrent Jobs | 2 simultaneous | 4-8 simultaneous |
| Average Generation Time | 2-5 seconds | 2-4 seconds |
| Queue Wait Time | ~4s per queued job | ~2s per queued job |
| Memory Usage | ~120MB per job | ~120MB per job |
| Max PDF Size | Unlimited | Unlimited |
| Jobs Processed/Hour | ~720 jobs | ~1800 jobs |
| Crash Resilience | β 100% (queue prevents overload) | β 100% |
β
With Queue: Unlimited requests = All Successfully Processed
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Puppeteer Team - For the amazing headless Chrome library
- Docker Community - For simplified deployment workflows
- Open Source Contributors - For inspiration and support
Zahid is a self-taught software developer and entrepreneur passionate about building scalable, production-ready solutions. Through WebAssets, he creates tools that empower businesses to automate workflows and enhance productivity.
"Building software that solves real problems, one line of code at a time."
WebAssets is a software solutions company specializing in:
- π§ Custom SaaS development
- βοΈ Cloud-native microservices
- π€ Workflow automation tools
- π Business intelligence dashboards
Get in Touch:
π§ Email: webassets.tech@gmail.com
π± Phone: +91 788 980 4942
π Instagram: @webassets.tech
π₯ YouTube: @WebAssetsTech
πΌ LinkedIn: Zahid
π§΅ Threads: @webassets.tech
Job stuck in queue forever:
- Check dashboard to see if service is processing jobs
- Restart the service (jobs in queue will be lost)
- Check Koyeb/Render logs for errors
"PDF not found or expired" error:
- PDFs are cached for 5 minutes after completion
- Download immediately after job completes
- If expired, submit a new generation request
High queue wait times:
- Normal on free tier with 2 concurrent jobs
- Upgrade to paid tier for higher concurrency
- Or consider self-hosting with more resources
Memory exceeded errors:
- Reduce concurrency in server.js (from 2 to 1)
- Limit complexity of HTML pages being converted
- Upgrade to hosting with more RAM
Jobs failing silently:
- Check
/status/:jobIdfor error messages - Verify targetUrl is publicly accessible
- Check Chrome console errors in logs
Need help? Have questions?
- π Documentation: Read the full docs
- π Bug Reports: Open an issue
- π‘ Feature Requests: Submit your ideas
- π§ Email: webassets.tech@gmail.com