Skip to content

Fix PDF export on Vercel via serverless Chromium fallback#11

Merged
JakeLeoDev merged 2 commits into
mainfrom
fix/pdf-export-serverless-chromium
Apr 24, 2026
Merged

Fix PDF export on Vercel via serverless Chromium fallback#11
JakeLeoDev merged 2 commits into
mainfrom
fix/pdf-export-serverless-chromium

Conversation

@lukasfehling

@lukasfehling lukasfehling commented Apr 20, 2026

Copy link
Copy Markdown
Collaborator

Closes #9.
Closes #7 as duplicate — both issues describe the same root cause (Puppeteer's bundled Chromium not working on Vercel). This PR fixes it in-process instead of outsourcing rendering.

Summary

  • Auto-detect serverless runtime (VERCEL / AWS_LAMBDA_FUNCTION_NAME) and fall back to puppeteer-core + @sparticuz/chromium; non-serverless paths (Node / Docker / VPS / Railway / Fly.io) keep the full puppeteer bundle unchanged.
  • Keeps the infrastructure single-service — no external PDF renderer required.
  • maxDuration = 60 on the PDF route (Vercel Pro / Fluid Compute; Hobby's 10 s is too short).
  • Cleaner internal-URL resolution: INTERNAL_APP_URLVERCEL_URLNEXT_PUBLIC_APP_URLlocalhost, and TLS is only stripped for real localhost hostnames (not for public Vercel URLs).

Root cause (covers #9 and #7)

puppeteer bundles ~170 MB of Chromium. Vercel's function size limit is 50 MB unzipped, so the binary was missing at runtime and every PDF request failed with Serverfehler — bitte versuchen Sie es später erneut.

Both are resolved by swapping in @sparticuz/chromium when the process detects a serverless runtime — no external service needed.

Why this approach (vs. an external renderer as proposed in #7)

Discussed with @JakeLeoDev in-thread — the project's value proposition is a slim stack (Next.js + Supabase). Spinning up a Gotenberg/Browserless service just for PDF is infrastructure bloat at Proposit's target usage volume. @sparticuz/chromium stays single-service, fixes Vercel, and keeps the render target as the app's own /proposals/[id]/pdf page — so PDF output still matches the on-screen preview byte-for-byte.

The external-renderer path from #7 remains reasonable for high-volume / multi-tenant-scale deployments and could be added later as an opt-in (PDF_RENDERER_URL) without disturbing the default. If / when that need materialises, a new issue should be filed with concrete scale numbers.

What changed

  • lib/pdf/launch.ts (new) — launchBrowser(), resolveInternalAppUrl(), isServerlessRuntime().
  • app/api/proposals/[id]/pdf/route.ts — uses the launcher + maxDuration = 60.
  • next.config.tsserverExternalPackages: ['puppeteer', 'puppeteer-core', '@sparticuz/chromium'].
  • lib/pdf/__tests__/launch.test.ts (new) — 11 unit tests covering env precedence + runtime branch selection.
  • context/pdf-export-testing.md (new) — manual smoke-test checklist per rendering tier.
  • .env.example (new) — was referenced in CLAUDE.md but missing from the repo; .gitignore amended with !.env.example.
  • README.md / CLAUDE.md — deployment + convention notes.
  • Brings in the minimal vitest setup (config + scripts) that the new unit tests need.

Test plan

@JakeLeoDev — bitte um Testing. Die Tiers sind in context/pdf-export-testing.md dokumentiert. Kurzform:

  • pnpm install && pnpm test lib/pdf — 11 unit tests should pass
  • pnpm build — should succeed without Chromium/bundler warnings
  • Tier 2 (local / VPS path): pnpm dev, open an existing proposal, Export PDF → downloads in ~3-8 s, matches on-screen preview
  • Tier 1 (Vercel path): deploy the branch to a Vercel Preview (Pro plan or Fluid Compute), open a non-trivial proposal, Export PDF → downloads within ~20-40 s without Serverfehler
  • Verify no [PDF] requestfailed / [PDF] response >=400 in Vercel function logs during the successful export
  • Spot-check PDF visuals: fonts, images, collection tables not cut mid-row, light mode enforced

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

The full puppeteer package bundles ~170 MB of Chromium, which exceeds
Vercel's 50 MB function size limit and caused PDF export to fail with
"Serverfehler" (#9). The launcher now auto-detects serverless runtimes
(VERCEL / AWS_LAMBDA_FUNCTION_NAME) and falls back to puppeteer-core +
@sparticuz/chromium, while Node hosts / Docker / VPS deployments keep
the full puppeteer path unchanged. Infra stays single-service; an
external renderer (see #7) is not needed.

Also extracts the render-target URL resolution and adds unit tests +
a tiered manual smoke-test checklist for future regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
proposit Ready Ready Preview, Comment Apr 24, 2026 3:24pm

These were staged during the initial fix commit but ended up missing from
the committed package.json while present in pnpm-lock.yaml, causing CI to
fail with ERR_PNPM_OUTDATED_LOCKFILE. Restores parity so `pnpm install
--frozen-lockfile` works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JakeLeoDev JakeLeoDev merged commit f742244 into main Apr 24, 2026
3 checks passed
@JakeLeoDev JakeLeoDev deleted the fix/pdf-export-serverless-chromium branch April 24, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PDF Export doesn't work Add possibility to outsource pdf generation

2 participants