跪拜 Guibai
← All articles
Frontend · Vue.js

Building a Reusable File Preview Platform in Vue 3

By JunjunZ ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Most admin panels, content systems, and collaboration tools eventually need file preview. Without a layered architecture, each new format or storage backend forces changes across every page that shows an attachment. This design keeps business code ignorant of rendering details, so swapping file services or adding formats never touches the UI.

Summary

File preview starts simple but quickly accumulates complexity: PDFs, Word docs, spreadsheets, images, logs, and archives each demand different browser capabilities, while Blob URLs, CORS, authentication, and memory management create hidden failure modes. A five-layer architecture separates business pages from rendering logic, routing every file through a unified state model that accepts both URLs and Blobs.

Format identification uses a priority chain—caller-specified type, server MIME, then file extension—rather than trusting extensions alone. Each format gets its own renderer component, loaded asynchronously so that viewing an image never pulls in Office dependencies. Text and archive fetches use AbortController to cancel stale requests when users switch files rapidly.

Cross-origin access moves from Vite dev proxies to production gateways that enforce domain whitelists and prevent SSRF. Third-party Office preview becomes an optional fallback, not a default, with sensitive files routed through server-side PDF conversion instead. The state machine treats loading, error, unsupported, and retry as first-class states, making analytics straightforward and user experience consistent across every format.

Takeaways
Business pages call preview.open() with a URL or Blob and never know which renderer handles the file.
Blob URLs created via URL.createObjectURL() must be revoked on a delay to avoid flicker during close animations, and new open() calls must cancel pending cleanup timers.
Format resolution checks explicit caller type first, then server MIME, then file extension—never trust extensions alone.
Each format gets its own Vue renderer component, loaded asynchronously so heavy Office libraries aren't downloaded for image or text previews.
Text and archive fetches use AbortController with a guard that only the latest request updates UI state, preventing stale responses from overwriting current content.
Production CORS must come from file service headers or a backend gateway with domain whitelisting, redirect limits, and SSRF protection—not from a Vite dev proxy.
Third-party Office preview services require the file URL to be publicly reachable; sensitive files should instead be converted to PDF server-side after authentication.
Preview state includes idle, loading, ready, error, and unsupported as distinct statuses, with renderers only emitting ready or error events upward.
A standalone preview page with query parameters (url, fileName, fileType, title, hideDownload) lets iframes and external systems reuse the same platform.
hideDownload=1 is a UI toggle, not access control—any browser with a valid URL can still request the file directly.
Conclusions

Most file preview implementations start as a quick iframe and grow into a tangle of special cases; the five-layer split here is a deliberate inversion that makes the preview platform the stable center and business pages thin clients.

Delaying Blob URL revocation until after a close animation completes is a small detail that prevents a visible flash, but it also introduces a timer that must be cancelled on the next open() call—a race condition easy to miss in testing.

Treating error and unsupported as first-class states rather than edge cases means analytics can distinguish network failures from format gaps from permission denials, which is far more useful than a generic 'preview failed' log.

The insistence that hideDownload is not security is worth underlining: too many teams ship a hidden download button and believe they've implemented access control, when the file URL is still valid and inspectable in dev tools.

Concepts & terms
Blob URL
A temporary browser-local URL created via URL.createObjectURL() that points to a Blob object in memory. Must be manually released with URL.revokeObjectURL() to prevent memory leaks.
AbortController
A browser API that lets you cancel an in-flight fetch() or other async operation by calling abort() on its signal. Essential for preventing stale responses when users navigate quickly.
SSRF (Server-Side Request Forgery)
An attack where an attacker tricks a server into making requests to internal or unintended external resources. A file proxy that accepts arbitrary URLs without domain restrictions is a common SSRF vector.
CORS (Cross-Origin Resource Sharing)
A browser security mechanism that blocks web pages from making requests to a different domain unless the target server explicitly allows it via response headers.
Composable
In Vue 3, a function that encapsulates reactive state and logic using the Composition API, typically returning refs, reactive objects, and methods for a component to consume.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗