The iframe snippet works on your laptop because the PDF is sitting next to the HTML. The moment the page travels, it's two files — and only one of them arrives. What actually survives the trip.
You built a page — a report, a proposal, a client summary — and there's a PDF that belongs with it. The spec sheet, the signed contract, the appendix nobody rebuilt in HTML. So you search for how to show a PDF inside a web page, find the snippet everyone finds, and drop it in:
<iframe src="report.pdf" width="100%" height="800"></iframe>
Refresh. There it is, rendering perfectly. Job done.
Then you send the page to someone, and they see a blank rectangle.
src="report.pdf" is a relative path. It doesn't mean "this PDF"; it means "whatever file happens to be sitting next to me called report.pdf." On your laptop, there is such a file, in the same folder, so the browser finds it instantly.
The instant the page travels, that sentence stops being true. You upload one .html file, or attach it to an email, or paste it into a host — and the HTML goes, and the PDF stays behind. The frame does exactly what you told it to: it looks for a neighbour, finds nobody, and renders empty. Nothing is broken. The page is just describing a room it no longer lives in.
This is the same shape as an AI share link that opens fine for you and not for them — the thing worked on the side where all the context was, and the context was the part that didn't travel.
Base64-inlining it. This is the reflex, and it's a good reflex — it's exactly how you make images travel inside a single HTML file. Encode the file, paste it into the src, done. It works for images because <img src="data:image/png;base64,…"> is permitted essentially everywhere.
It does not carry over to PDFs, because a PDF has to render in a frame, and browsers deliberately block navigating a frame to a data: URL — Chrome has enforced that for years as an anti-phishing measure. So the encoded PDF sits in your file, adds about a third to its size, and still shows a blank box. The trick that saves images is the wrong tool here.
Zipping the HTML and the PDF together. Reasonable instinct — a ZIP is the right container for a genuinely multi-file build, and the relative path would resolve if both files got served. The catch is that "served" is doing a lot of work in that sentence. Static hosts generally serve a curated set of web asset types — HTML, CSS, JS, images, fonts — and skip everything else, partly for security and partly because anything else isn't a web page.
miinideck works this way too, and it's worth being blunt about it: a .pdf inside an uploaded ZIP is skipped, not served. The upload succeeds, the page goes live, and the frame 404s — which is a worse failure than an outright rejection, because nothing warns you. Whatever host you're using, this is the thing to test before you send: upload the bundle, open the live URL yourself, and check the frame actually renders. Don't check the source; check the page.
While you're testing, one more thing to know about strict hosts, ours included: <embed> and <object> are the plugin-era elements, and they're the first two a tight security policy switches off. <iframe> is the modern equivalent and is far more widely allowed. If you're copying a snippet, copy the <iframe> one.
Testing whether your page survives the trip is the whole point — drop the HTML, open the link yourself, and see what your reader will see before they do. No card, no account.
If the PDF genuinely has to render inside the page, give the frame an address instead of a neighbour:
<iframe src="https://example.com/files/report.pdf" width="100%" height="800"></iframe>
Now the page carries no assumption about what's sitting beside it. Wherever the HTML ends up, the frame goes and fetches the PDF from a place that exists. This is the version that works on a hosted link, in a colleague's downloads folder, and on a phone.
Three things to get right. The address has to be the file itself — a URL that ends in the PDF. A private-link host's viewer page is a different thing: it's a web page wrapped around the file, and hosts that care about who sees a page usually refuse to be framed by someone else's site. Ours is one of them — embedding miinideck pages in other sites isn't supported right now — so a miinideck link belongs in the page as a link, not inside an iframe. The URL also has to be https:// — a page served over HTTPS won't load an http:// frame, and you'll get another blank box for a completely different reason. And the PDF needs to live somewhere that will still be there in six months, which usually means your own storage or a document host, not a temporary link from a chat thread.
Everything above answers the question as asked. It's worth asking a different one before you use any of it.
A PDF inside a web page is a document inside a document. The reader gets a scrollbar within a scrollbar, a fixed page size that doesn't reflow on a phone, text that's harder to select and search, and a zoom level that's never quite right. It's a viewer bolted into a page that was already a perfectly good viewer.
So the question is what the reader is meant to do with it:
Most of the time the first option is both less work and a better page. The embed is a habit carried over from a world where the PDF was the deliverable; when the page is the deliverable, the format question is worth answering deliberately rather than by reflex.
Client-side encryption tools turn your page into an encrypted file, so the protection and the artifact are the same object — every edit means re-running the tool and re-uploading. What that actually costs, the salt setting that decides whether your old share links survive, and when a hosted password is the better trade.
A static page has no backend and no secrets, which sounds like it rules out forms, payments and bookings. It doesn't. The line isn't 'frontend versus backend' — it's whether the credential you'd have to publish is designed to be published. How to tell, in one question, before you build.
The person who needs your dashboard usually has no seat in your BI tool — and buying one is the wrong fix. How to ship a live, interactive dashboard to a no-login viewer, and where a live connection beats a snapshot.
miinideck turns a single HTML file into an unguessable link with optional password and expiry. Default-private, never indexed.