The Problem
My resume lives in a Google Doc so I can update it without touching code. The obvious move is linking straight to Google's export URL:
https://docs.google.com/document/d/<id>/export?format=pdfThat works, but Google's response comes back with:
Content-Disposition: attachment; filename="MartinIfeanyi.pdf"attachment means the browser downloads the file no matter what — even in a new tab, even with a native PDF viewer available. Someone who just wants to skim my resume gets a file dumped into their Downloads folder instead. Not the experience I wanted.
The Fix: Proxy It
The browser respects whatever Content-Disposition header the response has, and I control that header on my own routes. So instead of linking to Google directly, I built a small Route Handler that fetches the PDF server-side and re-serves the same bytes with inline instead:
export async function GET() {
const res = await fetch(SITE.resume, { next: { revalidate: 3600 } });
const pdf = await res.arrayBuffer();
return new NextResponse(Buffer.from(pdf), {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": 'inline; filename="Martin-Ifeanyi-Resume.pdf"',
"Cache-Control": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400",
},
});
}Now /api/resume opens straight in the browser's native PDF viewer. Same source doc, same one-click editing workflow, just a header swap in between.
The Follow-Up Bug: An Extra Blank Page
After shipping that, I noticed the exported PDF had 3 pages instead of 2 — page one was just my name, floating alone, with the real content starting on page two. Turned out to be a stray page break in the Google Doc itself, sitting right after the title.
I could go fix the Doc every time this happens, or I could make the proxy resilient to it. I went with the latter, using pdf-lib to detect and drop a leading throwaway page automatically:
async function dropLeadingBlankPage(bytes: ArrayBuffer) {
const doc = await PDFDocument.load(bytes);
if (doc.getPageCount() <= 2) return new Uint8Array(bytes);
const trimmed = await PDFDocument.create();
const pages = await trimmed.copyPages(doc, doc.getPageIndices().slice(1));
pages.forEach((page) => trimmed.addPage(page));
return trimmed.save();
}If the doc ever exports cleanly at 2 pages again, the condition never triggers and the PDF passes through untouched. Self-healing, no manual intervention required.
Result
One route handler, one small dependency, and a resume link that behaves the way a resume link should: click it, read it, close the tab. Download it if you actually want a copy — nothing forced.