Use Case: Large Documents
Patterns for rendering 500+ page PDFs with bounded memory and responsive navigation.
DocumentViewer uses EmbedPDF’s tiling + viewport virtualization, so rendering a 1000-page PDF takes the same memory as rendering a 10-page PDF — only the pages currently in view are kept in memory.
What’s already optimized
These behaviours are on by default with no configuration needed:
- Page virtualization — only pages in the visible viewport (plus a small overscan) are rendered to canvas
- Tile virtualization — within each page, only tiles in the viewport are rendered; the rest are placeholder regions
- Thumbnail lazy-loading — even with
features.thumbnailson, thumbnails are generated as they scroll into view, not all at once - Worker mode — heavy operations (decode, search, render) run on a separate worker thread by default
Best practices
1. Use thumbnails for navigation
For 500+ page documents, scrolling end-to-end is slow even with virtualization. The thumbnail panel makes random-access navigation practical:
<DocumentViewer
:features="{
zoom: true,
search: true,
outline: true,
thumbnails: true,
download: true,
}"
...
/>
2. Default to fit-width
fit-page for a 1000-page document means tiny pages. fit-width is usually better:
<DocumentViewer :feature-config="{ zoom: { defaultMode: 'fit-width' } }" ... />
3. Keep worker: true
The default worker={true} keeps the main thread responsive. Only opt out for the relative-wasmUrl issue (see Troubleshooting).
4. Surface a “loading” indicator
For PDFs larger than ~10 MB, the WASM decode + initial page render can take 1–3 seconds. Show a loading state while document-loaded hasn’t fired:
<script setup lang="ts">
import { ref } from 'vue'
import { DocumentViewer } from '@meldui/vue'
const loading = ref(true)
</script>
<template>
<div class="relative h-[600px]">
<div
v-if="loading"
class="absolute inset-0 z-10 flex items-center justify-center bg-background/80"
>
<Spinner />
</div>
<DocumentViewer
source="/very-large.pdf"
wasm-url="/pdfium.wasm"
@document-loaded="loading = false"
@document-error="loading = false"
/>
</div>
</template>
5. Page-jump UI in your own toolbar
For documents where users routinely navigate to specific page numbers (legal contracts, statutes), add a “Go to page” input:
<script setup lang="ts">
const target = ref(1)
function go() {
viewer.value?.goToPage(target.value)
}
</script>
<template>
<DocumentViewer ref="viewer" ... />
<input type="number" v-model.number="target" min="1" />
<button @click="go">Go</button>
</template>
Memory expectations
Approximate steady-state memory for a typical PDF (8.5 × 11 inch, mixed text + images):
| Pages | Memory (Chrome, fit-width) |
|---|---|
| 10 | ~50 MB |
| 100 | ~80 MB |
| 1000 | ~120 MB |
| 5000 | ~180 MB |
The constant ~50 MB is the WASM runtime + DOM. The growth is dominated by the document’s parsed structure (independent of how much you’ve scrolled).
Initial page
If a user lands on a deep-link to a specific page, set initial-page to avoid them scrolling there manually:
<DocumentViewer source="/long-doc.pdf" wasm-url="/pdfium.wasm" :initial-page="page" ... />
Documents with many annotations
If you’re loading 1000+ saved annotations on mount, loadAnnotations does the work in a single batch — no need to chunk.
For very high counts (10,000+ annotations across many pages), consider lazy-loading per page:
<DocumentViewer @page-change="onPageChange" />
async function onPageChange({ page }: { page: number }) {
const range = [page - 1, page, page + 1] // page ± 1
const annotations = await fetch(`/api/annotations?pages=${range.join(',')}`).then((r) => r.json())
await viewer.value?.loadAnnotations(annotations)
}
See also
- Bundle & Perf — virtualization internals
- Plugin: tiling
- Plugin: thumbnail