File Preview & Document Viewer
If you're building a B2B SaaS in 2026 that lets users upload files — contracts, design files, spreadsheets, PDFs, images, videos — they expect to see the file inside your product, not download → open in another app → return. The naive approach: link to the raw file URL; let the browser figure it out. The structured approach: server-render thumbnails, use specialized viewers for each format (PDF.js for PDFs, browser for images, video player for video), handle large files with progressive loading, secure access with signed URLs, support download / annotation / share. Get this right and your product feels like Notion / Slack / Linear. Get it wrong and users leave.
1. Decide what to preview — by format
Map file formats to preview strategy.
Images (JPG, PNG, WebP, GIF, SVG, HEIC):
- Browser-native rendering for JPG/PNG/WebP
- HEIC: convert to JPEG server-side (or use heic2any client-side)
- SVG: sanitize before rendering (XSS risk)
- GIF: animate; respect prefers-reduced-motion
PDFs:
- PDF.js (Mozilla) — most-used; ~300KB
- Server-side render to images (faster initial load)
- Or: thumbnail + click to open full viewer
- Pages: paginate if >20 pages
Office documents (DOCX, XLSX, PPTX):
- No browser-native rendering
- Options:
1. Convert server-side to PDF, then PDF.js
2. Microsoft Office Online viewer iframe (free, requires public URL)
3. Google Docs viewer iframe (deprecated)
4. Aspose / Syncfusion (paid; high-quality)
5. Convert to images (server-side via LibreOffice + pdf2image)
Video (MP4, MOV, WebM):
- HTML5 video element
- HLS for adaptive streaming (Vimeo / Mux / Cloudflare Stream)
- Thumbnail at first frame
- Custom player (Plyr, Video.js)
Audio (MP3, WAV, M4A):
- HTML5 audio element
- Waveform visualization (Wavesurfer.js)
- Custom player
Code / text files (.txt, .json, .yaml, .md, .py, .ts, etc.):
- Render as text with syntax highlighting (Prism / Shiki)
- Or: GitHub-style code-viewer
- Markdown: render with sanitization (see markdown-rendering-sanitization)
Spreadsheets (CSV, XLSX):
- Convert to HTML table (server-side; size-limited)
- Or: render with SheetJS / Handsontable
- Or: convert to PDF first
Archives (ZIP, RAR, TAR):
- Show file listing (don't extract for preview)
- Click into specific file → preview that file
Other (specialty formats):
- DICOM (medical) → Cornerstone.js
- DWG (CAD) → Forge / Autodesk
- 3D models → Three.js / model-viewer
For [USE CASE], output:
1. Supported formats
2. Per-format preview strategy
3. Fallback for unsupported (download icon)
4. File-size limits per format
5. Thumbnail generation strategy
The "convert everything to PDF" pattern: simplest path. LibreOffice headless server-side converts DOCX/XLSX/PPTX → PDF; then PDF.js renders. Works for 95% of office docs.
2. Thumbnail generation — server-side
Showing a thumbnail in file lists is essential. Generate server-side at upload.
Generate file thumbnails server-side.
Triggered: at file upload (background job)
Per format:
Images:
- ImageMagick / Sharp (Node)
- Resize to multiple sizes (thumbnail 200px, medium 800px, large 1600px)
- Strip EXIF (privacy)
- Convert to WebP (smaller)
PDFs:
- pdf2image (Python) or pdf-img-convert
- Render first page to PNG/JPEG
- Save thumbnail variants
Office:
- LibreOffice headless: docx → pdf → image
- Slow (5-15 sec); use background job
Video:
- FFmpeg: extract frame at 0:01 or middle
- Multiple sizes
- Animated thumbnail (GIF of first 3 sec) — popular but heavy
Storage:
- Original file: S3 / R2 / Blob storage
- Thumbnails: same bucket, separate key prefix (thumbnails/{file_id}/{size}.webp)
- Or: image CDN (Cloudinary, imgix, Vercel Image)
Cache:
- Long cache (immutable; key by file_id + size)
- CDN-delivered
Errors:
- Failed conversion → placeholder icon
- Log to error monitoring (Sentry)
- Retry async
Background job:
- Queue (Vercel Queues, BullMQ, Inngest)
- Status: pending → processing → done / failed
- UI shows spinner during processing
Output:
1. Thumbnail-generation pipeline
2. Per-format tools
3. Storage + naming convention
4. Background-job framework
5. Error / retry handling
The async-thumbnail-generation rule: don't block upload response on thumbnail. Upload returns immediately; thumbnails appear when ready (with loading state). Otherwise upload feels slow.
3. PDF viewer — PDF.js or server-rendered
Implement PDF preview.
Option A: PDF.js (client-side)
- Library: pdfjs-dist (Mozilla; ~300KB)
- Renders PDF in canvas in browser
- Pros: full interactivity (zoom, search, copy-text)
- Cons: bundle size; client CPU; large PDFs slow
Option B: Server-rendered images
- Convert PDF pages to images server-side
- Display as image gallery
- Pros: fast initial load; small bundle
- Cons: no text-search; no interactivity; bigger storage
Option C: Hybrid
- Thumbnail or first-page image for preview tile
- Click to open: load PDF.js for full interactivity
- Best of both worlds
Option D: Embed via iframe (Office Online / Google Drive viewer)
- Pros: zero implementation
- Cons: requires public URL or auth; brand mismatch; deprecated for Google
Recommendation: Hybrid (Option C)
PDF.js setup:
import { getDocument } from 'pdfjs-dist';
const pdf = await getDocument(url).promise;
const page = await pdf.getPage(1);
const viewport = page.getViewport({ scale: 1.5 });
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({ canvasContext: context, viewport }).promise;
Considerations:
- Worker (PDF.js requires Web Worker)
- CDN: load PDF.js from CDN to avoid bundle
- Memory: large PDFs (>100MB) crash mobile browsers
- Pagination: render visible pages only
Output:
1. Recommended approach (Hybrid)
2. PDF.js setup
3. Canvas-based render
4. Pagination + zoom
5. Mobile considerations
The 2026 React stack: react-pdf (wraps PDF.js); react-pdf-viewer (more features); pdfjs-dist directly. react-pdf is most-popular for React-based viewers.
4. Image preview — gallery patterns
Most product UIs need image preview with zoom + navigation.
Implement image preview / gallery.
Patterns:
Lightbox / modal:
- Click thumbnail → full-screen modal
- Arrow keys to navigate
- Pinch-zoom on mobile
- Swipe to dismiss
- Library: yet-another-react-lightbox, fancybox, photoswipe
Inline expand:
- Click thumbnail → inline larger
- Less intrusive
Side panel (Linear / GitHub):
- Click → opens side panel with metadata + preview
- Allows quick navigation between files
Required features:
- Zoom (pinch + +/- buttons)
- Pan / drag
- Rotate (sometimes useful)
- Navigation (next/prev)
- Download
- Share / copy URL
- Close (X + Esc)
- Loading skeleton
Performance:
- Progressive image loading (blur-up / LQIP)
- WebP / AVIF where supported
- Responsive images (srcset)
- Lazy load below fold
Mobile:
- Touch gestures (pinch zoom, swipe)
- Full-screen on tap
- Bottom action bar
Anti-patterns:
- Lightbox that blocks scroll (frustrating)
- No keyboard nav
- Slow load on large images
- No loading state
Output:
1. Pattern recommendation
2. Library choice
3. Required features
4. Performance optimization
5. Mobile considerations
The Linear-style side panel is popular in 2026 product UI for collaborative tools. Less context-switching than modal.
5. Office documents — server-side conversion
Office formats are no-go in browser. Convert.
Preview Office documents (DOCX, XLSX, PPTX).
Recommended pipeline:
1. User uploads .docx
2. Background job: LibreOffice headless converts → PDF
3. PDF.js renders preview
LibreOffice headless command:
soffice --headless --convert-to pdf input.docx --outdir /output
Wrapped in:
- Docker image (jbarlow83/ocrmypdf or libreoffice/online)
- Lambda layer (limited)
- Or: Cloud Run / Fly.io serverless
Alternative: Microsoft Graph API
- /print endpoint converts to PDF
- Requires M365 license + permissions
- Cleaner output (Microsoft's native rendering)
Alternative: Google Drive API
- Upload to Drive, export as PDF
- Requires Google account
- Quotas
Alternative: Specialized SaaS
- ConvertAPI ($0.01-0.05/conversion)
- Aspose Cloud
- CloudConvert
- Useful for low-volume; expensive at scale
Cache:
- Once converted, cache PDF version
- Re-use on subsequent previews
- Invalidate when source changes
Performance:
- Conversion: 2-30 sec depending on size
- Show "Generating preview..." status
- Email user when ready (for large docs)
Output:
1. Conversion pipeline
2. Service / library choice
3. Caching strategy
4. Failure handling
5. User-facing status updates
The LibreOffice path: most-used. Free; reliable; supports DOCX/XLSX/PPTX/ODF/RTF. Slow (~10s/doc); use background job + cache.
6. Video player — HLS for streaming
Implement video preview.
For short clips (<5 min):
- HTML5 <video> with progressive download
- MP4 H.264 (universal)
- Good for product demos, customer support clips
For longer / streaming:
- HLS adaptive streaming
- Convert MP4 → HLS (FFmpeg) — multi-bitrate
- Players: HLS.js, Video.js, Plyr, Mux Player
Hosting:
- Self-host on S3 + CloudFront (cheap; complex)
- Mux (managed; transcoding + streaming + analytics)
- Cloudflare Stream (managed; simple)
- Vimeo / Wistia (creator-focused)
- YouTube (free; ads)
Player features:
- Play / pause
- Seek bar with thumbnails
- Volume
- Fullscreen
- Captions / subtitles (WebVTT)
- Speed control
- Quality selection
- Picture-in-picture
Thumbnail / preview:
- Poster image (first frame)
- Storyboard / sprite sheet for hover-scrub
Accessibility:
- Captions / subtitles required for compliance
- Keyboard control (Space play; arrows seek)
- Focus management
Mobile:
- Don't autoplay with sound (browsers block)
- Native fullscreen on iOS
- Touch controls
For [USE CASE], output:
1. Hosting decision (self vs Mux vs Cloudflare Stream)
2. Player library
3. Captions strategy
4. Accessibility checklist
5. Mobile UX
The "use Mux or Cloudflare Stream" rule for B2B SaaS: rolling your own video infrastructure (transcoding, CDN, players) takes weeks. Mux/Cloudflare cost $5-50/mo for typical SaaS use; saves months.
7. Secure access — signed URLs
User-uploaded files often have sensitive content. Secure access.
Secure file access.
Options:
Public + obscure URLs:
- File at /files/{random-uuid}/document.pdf
- Pro: simple
- Con: anyone with URL has access
- Use only for non-sensitive
Signed URLs (recommended):
- Generate URL with expiration (1 hour typical)
- Cloudflare R2 / AWS S3 / Vercel Blob support natively
- User accesses → server validates auth → generates signed URL → redirects/streams
- After expiry: re-generate
Server-streamed:
- Client requests via your API
- API authenticates user
- API streams file from storage (or generates signed URL)
- Pros: maximum control; no expired-link issue
- Cons: bandwidth + compute on your servers
Token-in-URL:
- /files/abc123?token=xyz
- Token expires
- User can pass to other authorized users (intended sharing)
Multi-tenant scoping:
- Always check user.org_id matches file.org_id
- Don't trust client-provided file_id alone
Watermarking:
- For sensitive docs: dynamic watermark with viewer email
- Server-side render PDF with watermark; serve customized
Output:
1. Access pattern recommendation
2. Signed URL implementation
3. Expiration policy
4. Multi-tenant enforcement
5. Watermarking (if needed)
The signed-URL pattern is standard. Cloudflare R2 / S3 native support; ~5 lines of code; secure by default.
8. Annotations and comments on files
For collaborative products, annotation = comment on a region of a file.
Implement file annotations.
Patterns:
Pin annotations (Figma, Loom):
- Click on file → drop pin at coordinates
- Comment thread anchored to pin
- Stored as: file_id, x, y, comment_thread_id
Region annotations (Adobe, Notion):
- Drag rectangle on file
- Comment thread on region
- Stored as: file_id, x1/y1/x2/y2, comment_thread_id
Page annotations (PDF):
- Comment on specific page
- Lighter than coordinate-precision
Implementation:
- Overlay layer on top of file viewer
- Pin/region as positioned div
- Click pin → expand comment thread (see comments-threading-mentions-chat)
Tools:
- PDF.js + custom overlay
- Pintura (image editor with annotation)
- Filestack annotations
Storage:
- annotations table: file_id, type (pin/region/page), coords, thread_id, author_id
- Comment thread re-uses comments table
Real-time collab:
- See others' cursors / annotations live
- Optimistic update
Permission:
- Who can annotate? (any reader / only collaborators)
- Who can resolve?
Output:
1. Annotation type for [USE CASE]
2. Storage schema
3. Overlay UI
4. Comment integration
5. Real-time sync
The Linear / Loom pattern: pin annotations with anchored threads. Simpler than region; covers 80% of needs.
9. Download + share affordances
Users still want to download. Make it easy.
Provide download + share affordances.
Download:
- Button visible (not hidden)
- Direct download (Content-Disposition: attachment)
- Original filename preserved
- Size in tooltip
Share:
- Copy link button (with permission scope)
- Email file (paste link)
- Permission options (anyone / specific people / org)
- Expiry (optional; for sensitive)
Bulk operations:
- Multiple file selection
- Download as ZIP (server-generates on-demand)
- Share multiple
Permissions:
- Who can download? (all viewers / only owner)
- Watermark with viewer email if sensitive
- Audit log of downloads
Anti-patterns:
- Download requires login when share link should be self-auth
- ZIP generation blocks UI (do background)
- No preview before download (waste)
Output:
1. Download button placement
2. Share permission options
3. Bulk download ZIP
4. Audit logging
5. Watermark (sensitive use case)
The ZIP generation pattern: server-side stream files into a ZIP archive on request. Tools: Archiver (Node), zipstream (Python). Stream as response; doesn't load into memory.
10. Mobile + responsive
File preview on mobile breaks differently than desktop.
Optimize file preview for mobile.
Image gallery:
- Pinch zoom (gesture)
- Swipe to navigate
- Tap to fullscreen
- Don't load multiple full-size images at once
PDF viewer:
- PDF.js works on mobile but slow on large docs
- Alternative: server-render to images; show as gallery
- Pinch zoom
Video:
- Native fullscreen (iOS)
- Don't autoplay
- Low data mode (lower quality)
Office docs:
- Conversion to PDF still works
- Or: thumbnail only; "Open in app" for native
Memory:
- Mobile browsers crash on large files
- File-size limits (warn if >50MB)
- Progressive load
Touch:
- Larger tap targets
- Bottom action bar (thumb-friendly)
- Gesture support (pinch, swipe, double-tap zoom)
Output:
1. Per-format mobile strategy
2. File-size warnings
3. Touch gestures
4. Native fullscreen integration
5. Test on iPhone Safari + Android Chrome
The mobile-PDF crash: 50MB+ PDFs crash mobile browsers regularly. Either limit size, or server-render to images for mobile only.
What Done Looks Like
A v1 file preview system for B2B SaaS in 2026:
- Per-format preview strategy (images / PDFs / Office / video / audio / code)
- Server-side thumbnail generation (background job)
- PDF.js or react-pdf for PDFs
- LibreOffice for Office conversion
- HLS / Mux / Cloudflare Stream for video
- Signed URLs for access control
- Multi-tenant scoping
- Download + share affordances
- Mobile-friendly viewers
- Loading + error states
- Accessibility (keyboard + screen reader for media)
Add later when product is mature:
- Annotations (pins, regions)
- Real-time collab
- Watermarking
- Search inside files
- OCR on scanned PDFs
- AI-driven summaries / Q&A
The mistake to avoid: rolling your own video infrastructure. Mux / Cloudflare Stream / Vimeo / Wistia exist for a reason. $50/mo beats 2 weeks of engineering.
The second mistake: public URLs to user files. Anyone with URL can access. Use signed URLs.
The third mistake: synchronous Office conversion in upload response. 30-second wait blocks user. Background job + status.
See Also
- File Uploads — upload pipeline upstream
- Image Upload Processing Pipeline — image variants
- PDF Generation in App — generating PDFs
- Markdown Rendering & Sanitization — adjacent rendering
- Comments, Threading & @Mentions — annotations integration
- Real-Time Collaboration — collaborative file viewing
- Accessibility — accessible viewers
- Performance Optimization — bundle size + lazy loading
- Multi-Tenancy — tenant scoping
- VibeReference: Document Parsing & OCR Services — parse files for search
- VibeReference: PDF & Document Generation Tools — generation side
- VibeReference: Image CDN Providers — Cloudinary / imgix
- VibeReference: File Storage Providers — S3 / R2 / Blob
- VibeReference: Video Hosting & Streaming Providers — Mux / Cloudflare Stream