Cloud

PDF Rendering Error: Why a Client's PDF Came Out Blank (And We Couldn't Reproduce It)

Junaid Zafar
Senior Software Engineer
October 19, 2026
Blog Main Image

Table of Content

Few things are more confusing in software development than a bug you cannot reproduce. You open the same file, on the same link, and everything looks perfect, yet the client is staring at a completely blank page. That's exactly what happened while working on a security assessment platform. A platform that's grown through several rounds of product development as it's matured to serve enterprise clients. It generates PDF reports for clients after every scan.

What looked at first like a broken PDF generator turned out to be something far more subtle. It is hidden in the file delivery layer instead of the rendering pipeline. This blog walks through the entire software debugging process: the wrong assumptions, the browser difference, and the fix that finally made the reports work.

Quick summary (TL;DR): A client's downloaded PDF was blank. The file itself was fine; the problem was that reports were stored gzip-compressed in S3. Also, Chrome silently decompressed them on download while Firefox did not. The fix was serving files through CloudFront instead of direct S3 links.

The Client Saw a Blank PDF, and I Couldn't Reproduce It

A client downloaded a security assessment report and found nothing inside it. Every page looked blank. That immediately pointed us toward the report-generation pipeline: scans run, findings get collected, a headless browser renders the report, and the file goes to Amazon S3. So many places to fail, a PDF rendering error seemed like the obvious explanation.

It wasn't. The PDF was valid, the generator worked, the upload worked. The real problem arose later, while the browser was receiving the file. It turned a frustrating production issues ticket into a lesson in identifying the root cause instead of chasing the obvious symptom.

Why We Blamed PDF Generation First

PDF generation was the most complicated part of the system, so that's where our Node.js developers started debugging production issues. It is an instinct, but not always right, and a good reminder to define the problem before chasing a fix.

We pulled the exact file from S3 and opened it locally. It worked; every page was there, findings intact. We regenerated the report to rule out a fluke; that copy was fine too. We sent the client a fresh link. Still blank on their end.

At that point, the evidence stopped matching my theory. This wasn't a PDF generation/rendering failure. The file itself was healthy. The problem lived somewhere between storage and the client's screen.

Test the File Before You Touch the Code

When a customer reports a blank PDF, ask one simple question first: is the stored file actually blank? Don't rely on the customer's browser alone; download the original object and open it locally. This separates two very different problems: a bad PDF was created, or a good PDF was delivered incorrectly. That single check is the corrective action that saves hours of wasted debugging.

In our case, the stored file immediately ruled out generation as the cause.

The Real Cause Was Gzip Delivery

Reports weren't stored in S3 as plain PDFs. To save storage and bandwidth, large reports were gzip-compressed and stored as .pdf.gz, with metadata describing how to decode them. HTTP supports this: the Content-Encoding header tells a client how a response was encoded, and browsers send Accept-Encoding to say what they can handle.

The break was in the delivery path. Users got a presigned S3 URL, a temporary, direct link to the object. Chrome and Edge quietly decoded the response and rendered a normal PDF. Firefox didn't do the same on a direct S3 link; its viewer received raw gzipped bytes instead of a real PDF, and rendered nothing.

Why a Gzipped PDF Can Look Blank

A PDF viewer expects PDF data, not gzip data. If the delivery layer doesn't decode the response correctly first, the viewer gets something it can't interpret. Symptoms can include blank pages, a blank tab, a file that won't load, or different results across browsers. A blank PDF viewer doesn't automatically mean generation failed; this is where real root cause analysis software thinking matters: trace the data path instead of guessing.

How We Fixed It

Three options existed:

  1. Stop compressing reports: simple, but loses real storage savings.
  2. Decompress manually in the app: more control, more surface area, and genuinely time-consuming against everything else on the roadmap.
  3. Change how files are delivered, without touching storage.

We went with option three. Instead of raw presigned S3 URLs, the app now returns a CloudFront URL when configured, and CloudFront decompresses consistently for every browser. Chrome keeps working as before, Firefox now gets a proper PDF, and S3 storage costs don't change.

The code change was a small swap of one URL type for another. We also logged a warning in the fallback path so a future developer isn't left guessing if CloudFront isn't configured. That warning is the only reason this incident has a documented root cause at all, the kind of safeguard that deserves the same discipline as your unit tests.

Headers Worth Checking First

Before touching the generator, check these on the actual response:

Header What it tells you
Content-Type Should read application/pdf
Content-Encoding Flags compression, e.g. gzip
Accept-Encoding What the browser says it supports
Content-Disposition Inline vs. download, affects viewer handoff.

Check all four together; along with any CDN or proxy in between, good debugging tools and consistent debugging techniques earn their keep exactly here.

The Real QA Gap Was Browser Coverage

Report generation had solid test cases. Report delivery had almost none, and those are different claims. This was the clearest QA testing gap in the whole pipeline: one passing browser isn't proof a browser-facing feature works.

A tighter testing process should cover opening the source file from storage, downloading through the production URL, and testing in Chrome, Firefox, and Edge separately, plus comparing headers and testing both large and small reports. Folding this into automated testing, with testing automated on every deploy, would have caught this earlier.

Was the Client Wrong Because It Worked for Us?

No, and this is where client communication software issues get awkward. Assuming the customer "did something wrong" sends the investigation the wrong way and hurts the user experience. When the same link behaves differently for two people, treat their environment as data, not a suspect.  A pattern similar to what we've seen when fixing delivery bottlenecks in other production systems, where code that looks correct in isolation still fails once real traffic and real browsers touch it.  Good customer support, backed by honest customer feedback, starts by taking that difference seriously.

What Changed About How I Debug

The mistake started with the most complicated component instead of the smallest boundary where behavior actually changed. None of the suspicious systems' headless rendering, templates, uploads caused this. The failure was in the most boring layer: delivery.

The chain once untangled: blank pages → stored PDF is valid → regenerated PDF is valid → same URL behaves differently across browsers → investigate delivery and encoding → find compressed S3 delivery over a presigned URL → move behind CloudFront → retest across browsers.

That's how to fix a production bug in practice: a disciplined development process of ruling things out one boundary at a time, the same way quality assurance teams work, and increasingly the kind of pattern AI powered and machine learning-assisted log analysis is starting to catch earlier. Our team members now treat this sequence as a standard checklist for any file-delivery bug, not a one-off fix.

Final Takeaway

Not every production issues case lives where you expect it to. We spent hours suspecting the most complex part of the system when the fault was in the simplest, easiest-to-overlook layer: how the file reached the browser. The compression that saved storage costs quietly broke compatibility for one browser, and it took a change of testing environment even to see it.

When a bug only reproduces for a client and not for you, stop trusting your own environment as the baseline. Test across browsers, check the full delivery path, and leave a trail so the next person doesn't start from zero. If your product delivers files, reports, invoices, or exports, it's worth auditing that path today, before a client finds the gap for you.

At Amrood Labs, this is the kind of production debugging and delivery-pipeline work we do for teams every day. If you want a second pair of eyes on your own file delivery path, get in touch with Amrood Labs; we're happy to help you find the gap before your users do.

Frequently Asked Questions

Why is my PDF blank when downloaded from a browser?

Usually the file is served compressed, and the browser doesn't decompress it before the PDF viewer opens it. Chrome and Edge often auto-decompress; Firefox may not, especially on direct cloud-storage links.

Why does a PDF work in Chrome but not Firefox?

Browsers handle Content-Encoding differently on direct storage links. Chrome tends to auto-decompress gzip responses; Firefox can render the raw compressed bytes instead, producing blank pages.

Can S3 presigned URLs cause PDF display problems?

 The presigned URL is just temporary access. The object's metadata and the resulting response type, encoding, and disposition determine how the browser actually handles it.

What's the fix for gzip-compressed files breaking PDF viewers?

Serve files through a layer that reliably decompresses for the client, like a CDN, instead of linking directly to compressed storage objects.

How can teams prevent this bug in the future?

Test file delivery across multiple browsers, log warnings at known failure points, and document the root cause so it isn't rediscovered from scratch.

Scroll to Top Icon