Handling Blob URLs in JavaScript: A Better Way to Preview and Download PDFs
Working with the Blob API in JavaScript is often a pain for developers, especially when trying to provide a user-friendly experience for file previews. A common problem is that when opening a PDF in a new tab using a blob URL, the browser displays a randomly generated string instead of a meaningful filename. This makes the user experience frustrating to say the least. In this article, we’ll explore why this happens and present a solution that improves both previewing and downloading while overcoming the limitations of blob URLs.
What is a Blob URL?
A Blob (Binary Large Object) in JavaScript represents raw binary data. The Blob API allows developers to create object URLs for temporary access to these files. When a blob is created, a unique blob URL is generated, such as:
blob:http://localhost:3000/85cad96e-e44e-a1f9-db97a96ed3fe
This URL is only valid within the current browser session and cannot be shared or accessed outside of it. When a PDF is opened using a blob URL, the browser does not have a meaningful filename to display, which can lead to a poor user experience.
Why is Previewing Blobs Difficult?
There are several key issues with blob URLs:
- Lack of meaningful filenames: The generated URL does not include the original file name.
- Non-shareable URLs: Blob URLs are only valid during the session and cannot be copied and opened later.
- Download naming issues: If users attempt to download the file using the browser’s default download button, the filename will appear as a random hash instead of the expected name.
- Limited control over UI customization: When opening the blob directly, customization options for controls, buttons, or branding are limited.
Backend Approach vs. Client-Side Approach
Of course, we can handle this on the backend by serving a file with its own URL and the correct headers. This would allow the browser to handle the file naturally, displaying the correct filename and making the file accessible via a shareable URL. However, in most modern Single Page Applications (SPAs), we generate the PDF on the client side using libraries like PDF.js. Because of this, we need a better option for previewing PDFs when a backend-generated URL is not available.
Opening a Blob URL in a New Tab looks like this
The browser will treat it as a normal PDF file with the only difference being that the filename and the URL are not something readable because the blob API does not provide a way to give it a name.
A Better Solution: Embedding the Blob in a New Tab with a Custom UI
Instead of directly opening the blob URL, we can open a new tab containing an iframe that displays the file. This allows us to:
- Set a custom filename in the page title and history.
- Provide a custom download button that correctly names the file.
- Enhance the UI/UX with custom styling and functionality.
JavaScript Implementation
Here’s how you can achieve this:
function previewFile(response, filename, type = 'application/pdf') {
const file = new Blob([response.data], { type });
const fileURL = URL.createObjectURL(file);
// Open a new tab with the preview
const newTab = window.open();
if (newTab) {
newTab.document.write(`
<html>
<head>
<title>${filename}</title>
<style>
body { margin: 0; padding: 0; }
.download-btn {
position: fixed;
bottom: 20px;
right: 20px;
background-color: #007bff;
color: white;
border: none;
padding: 10px 15px;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
}
.download-btn:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<iframe src="${fileURL}" style="width:100vw; height:100vh; border:none;"></iframe>
<button class="download-btn" onclick="downloadFile()">Download</button>
<script>
function downloadFile() {
const a = document.createElement('a');
a.href = "${fileURL}";
a.download = "${filename}";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
// Change the URL displayed in the address bar
window.history.pushState("", "${filename}", "/preview/${filename}");
</script>
</body>
</html>
`);
newTab.document.close();
} else {
// Gracefully handle the case if the browser stops the modal
alert("Pop-up blocked! Please allow pop-ups for this site.");
}
}
Explanation of the Solution
Creates a Blob URL:
- Converts the response data into a Blob and generates a URL for it.
Opens a new tab:
- Instead of opening the blob directly, a new blank tab is created.
Injects an iframe with the file preview:
- The iframe takes up the full width and height, ensuring a seamless preview experience.
Adds a custom download button:
- Clicking the button triggers a download with the correct filename.
Updates the browser’s URL:
- Uses
window.history.pushState
to set a user-friendly URL in the address bar.
Pros and Cons of This Approach
Pros:
✅ Meaningful URL in the address bar
✅ Correct filename when downloading via the custom button
✅ Full control over the UI with HTML, CSS, and JavaScript
✅ You can even place your button over the default button for better UX
✅ Works in most modern browsers
Cons:
❌ The new URL is visual only — bookmarking or refreshing the page won’t reload the document.
❌ If users download using the browser’s default button, the filename will still be a blob hash.
❌ Browser compatibility for window.history.pushState
should be checked.
Conclusion
By embedding the blob inside an iframe within a new tab, we can significantly improve the user experience when previewing and downloading PDFs. While it doesn’t solve all the issues with blob URLs, it provides a much cleaner and more intuitive interface. Hopefully, future browser updates will introduce better native support for handling blob URLs with custom filenames, but until then, this solution offers a practical workaround.
Have you found other ways to improve blob handling in JavaScript? Share your thoughts in the comments!