How I Built a Live Resume Download with Google Docs, Next.js, and Vercel
How I Built a Live Resume Download with Google Docs, Next.js, and Vercel
Keeping a resume PDF updated sounds simple until it becomes one more manual step after every small edit. I was previously keeping a static PDF inside my portfolio app. That worked, but it meant the resume on my site could become stale any time I updated the source document and forgot to replace the exported file.
I wanted a cleaner flow: edit the resume in Google Docs, then let my portfolio download the latest version as a PDF.
The goal
The user experience should stay exactly the same:
- A visitor clicks Download CV on my portfolio.
- The browser downloads a file named
Jayant_Resume.pdf. - No visitor login is required.
- The resume content comes from the latest Google Doc, not a committed static PDF.
The security requirement was just as important: Google credentials must stay server-side and never be exposed to the browser.
The architecture
The final flow is:
Visitor clicks Download CV
-> /api/resume
-> Next.js server route authenticates with Google
-> Google Drive API exports the Google Doc as PDF
-> Server returns Jayant_Resume.pdf
The browser only knows about /api/resume. It never sees the Google service account email, private key, access token, or the Drive API call.
Why Google Drive API, not Google Docs API?
This was the key detail. Google Docs API is useful for reading and editing document structure, but exporting a Google Doc to PDF is handled by the Google Drive API.
The route calls Drive's export endpoint with:
mimeType=application/pdf
That gives the app a fresh PDF version of the Google Doc.
Server-side auth
I used a Google service account with read-only Drive access. The resume Google Doc is shared with that service account as a viewer.
The app stores these values as server environment variables:
GOOGLE_RESUME_DOCUMENT_ID=
GOOGLE_SERVICE_ACCOUNT_EMAIL=
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY=
None of these use NEXT_PUBLIC_, so they are not included in the browser bundle.
The Next.js route signs a JWT on the server, exchanges it for a Google access token, exports the document as PDF, and returns the PDF response with:
Content-Type: application/pdf
Content-Disposition: attachment; filename="Jayant_Resume.pdf"
Public endpoint, private credentials
My portfolio is public, so the resume endpoint also needs to be public. I added /api/resume to the app's public proxy allowlist so visitors do not need authentication.
That does not make the Google document public. It only makes my server route public. The server still controls the Google credentials and returns only the generated PDF.
Deployment notes
The app runs on Vercel, so I added the same environment variables to Production, Preview, and Development. I also verified the preview and production endpoints by downloading the file and checking that the response was a valid one-page PDF.
I kept a short cache window so downloads are fast, while still allowing resume updates to show up quickly after the Google Doc changes.
What improved
This small change removed a repeated manual step from my workflow:
- No more exporting a PDF locally.
- No more replacing a static file in the repo.
- No more deploying only because the resume changed.
- The portfolio always points to the resume source of truth.
It is a simple pattern, but it is the kind of automation that keeps a personal site easier to maintain over time.
Related resources
- Visit the Jayant Goyal developer profile connected to the live resume flow.
- Explore the portfolio homepage where the resume download is exposed.
- Try the public developer tools collection built in the same Next.js app.