Back to Blog
Deep Dive

Vercel's 10s Timeout vs PDF Generation: The Math Doesn't Work

2026-01-27·4 min read
serverlessvercellambdatimeout

You deploy to Vercel. Everything works. Then someone tries to generate a PDF and the function times out.

You bump the timeout to 60 seconds. It still times out. You upgrade to Pro for 300 seconds. Now it works—sometimes.

The problem isn't your code. It's the architecture.

Serverless Limits Are Real

Platform Default Timeout Max Timeout Max Memory
Vercel (Hobby) 10s 10s 1024MB
Vercel (Pro) 15s 300s 3008MB
AWS Lambda 3s 15min 10GB
Cloudflare Workers 30ms CPU 30s 128MB

Cloudflare Workers can't even run Chromium. The 128MB memory limit makes it impossible.

What PDF Generation Actually Costs

Let's trace a typical PDF render:

text
1. Cold start Chromium      → 2-4 seconds
2. Parse HTML/CSS           → 200-500ms
3. Load external resources  → 0-10 seconds (fonts, images)
4. Render to PDF            → 500ms-3s
5. Upload/return result     → 200ms-1s

Best case: 3 seconds. Worst case with external resources: 15+ seconds.

And that's for a single page. Multi-page documents scale linearly.

The Cold Start Problem

Serverless functions spin down when idle. When a request comes in, the runtime has to:

  1. Allocate a container
  2. Load your code
  3. Initialize dependencies
  4. Launch Chromium

Step 4 is the killer. Chromium isn't a lightweight process. It needs to:

  • Decompress binaries
  • Initialize V8
  • Set up IPC channels
  • Allocate rendering buffers

That's 2-4 seconds before your code even runs.

typescript
// This import alone triggers a cold start penalty
import puppeteer from 'puppeteer'

export default async function handler(req, res) {
  // By the time we get here, 3 seconds are gone
  const browser = await puppeteer.launch()
  // ...
}

Workarounds (And Why They Fail)

1. Keep Functions Warm

You can ping your function every minute to prevent cold starts.

typescript
// cron job hits this endpoint
export async function GET() {
  // Just keep the function warm
  return new Response('ok')
}

Problems:

  • Costs money (invocations aren't free)
  • Doesn't guarantee your specific instance stays warm
  • Multiple instances = multiple cold starts anyway

2. Use Provisioned Concurrency (AWS)

AWS lets you pay to keep Lambda instances warm.

Problems:

  • Expensive ($0.000004/GB-second × 24/7)
  • Still limited by memory/timeout
  • You're now paying for idle compute

3. Use a Dedicated Server

Spin up an EC2/Droplet just for PDF generation.

Problems:

  • Now you're managing infrastructure
  • Scaling is manual
  • You've abandoned the serverless model

4. Edge Functions

Some developers try edge functions for lower latency.

Problems:

  • Edge runtimes are even more restricted
  • No native binaries (no Chromium)
  • Limited memory (usually 128MB)

The Real Cost Calculation

Let's do the math for a SaaS generating 10,000 PDFs/month.

Self-hosted on Vercel Pro:

  • Pro plan: $20/month
  • Function invocations: ~$5/month
  • Execution time (5s avg × 10k): ~$15/month
  • Total: ~$40/month
  • Hidden cost: Your debugging time

Self-hosted on AWS Lambda:

  • Lambda costs: ~$10/month
  • Cold start latency: User frustration
  • Memory tuning: Your weekends
  • Total: ~$10/month + sanity

API-based (NeatPDF):

  • 10k PDFs: $20/month
  • Cold starts: Zero (always warm)
  • Maintenance: Zero
  • Total: $20/month

The API costs more than raw Lambda. But Lambda doesn't include your engineering time debugging memory leaks at 2am.

When Serverless Works

Serverless PDF generation can work if:

  1. Documents are simple (no images, system fonts only)
  2. Volume is low (<100/day)
  3. Latency doesn't matter (async/background jobs)
  4. You're okay with occasional timeouts

If that's you, here's a minimal setup:

typescript
// pages/api/pdf.ts
import puppeteer from 'puppeteer-core'
import chromium from '@sparticuz/chromium'

export const config = {
  maxDuration: 60, // Vercel Pro required
}

export default async function handler(req, res) {
  const browser = await puppeteer.launch({
    args: chromium.args,
    executablePath: await chromium.executablePath(),
    headless: chromium.headless,
  })

  const page = await browser.newPage()
  await page.setContent(req.body.html)
  const pdf = await page.pdf({ format: 'A4' })

  await browser.close()

  res.setHeader('Content-Type', 'application/pdf')
  res.send(pdf)
}

Note: @sparticuz/chromium is a stripped-down Chromium build for Lambda. It's still 50MB compressed.

When to Use an API

If any of these are true, stop fighting serverless:

  • Documents have images or custom fonts
  • Users wait for the PDF (sync generation)
  • Volume exceeds 1000/month
  • You've already hit timeout issues

The NeatPDF Approach

We run dedicated Chromium pools. Always warm. No cold starts.

typescript
import { NeatPDF } from '@neatpdf/sdk'

const client = new NeatPDF({ apiKey: process.env.NEATPDF_API_KEY })

// This returns in 1-3 seconds, consistently
const pdf = await client.generate({
  html: invoiceHtml,
  format: 'A4',
})

No timeout configuration. No memory tuning. No @sparticuz/chromium hacks.

Your serverless function just makes an HTTP call. That fits in any timeout.

Get started →

Ready to generate PDFs?

Zero data retention. GDPR compliant.

Start Free TrialZero data retention · GDPR