Server-Side PDF Generation
Date: 2026-03-25 PR: #169 Issues: Closes #152, #80
What changed
PDF resume downloads now generate server-side instead of in the browser. The /api/resume/download-pdf API route renders the PDF via @react-pdf/renderer on the Node.js server and streams the result back as a file download. The browser never loads the PDF rendering library.
This eliminates unsafe-eval from the Content Security Policy. The CSP script-src directive no longer includes 'unsafe-eval' on any page.
Changes
- New API route
/api/resume/download-pdf— fetches resume data, renders PDF viarenderToBuffer, returns withContent-Dispositionheader PDFDownloadButtoncallsfetch()+saveAs()instead of running@react-pdf/rendererclient-sideResumeDocumentextracted topdf-document.tsxfor server-side import without browser dependenciesUNSAFE_EVALremoved from next.config.ts, Dockerfile, cloudbuild.yaml, deploy.yml, docker-compose.yml, package.json, and all documentation- In-memory PDF cache with 5-minute TTL and stampede guard (coalesces concurrent cold-cache requests)
- Cache invalidated on resume PUT, DELETE, set-default, restore-original, and tailor
Content-Dispositionfilename sanitized to prevent header injectionfetchBackend()utility extracted toshared/utils/backend-fetch.ts— replaces boilerplate across 28 API routes, includes 10sAbortSignal.timeoutby defaultsanitizeFilename()utility in same module- Fixed pre-existing
Tabs.test.tsxfailure (test expected rendered children in hidden panel, but component uses lazy-mount)
Post-merge manual steps
- Delete the
UNSAFE_EVALGitHub variable from repo Settings > Secrets and variables > Actions > Variables - Close issue #80