Rotating a PDF sounds like the simplest operation imaginable. Yet building it client-side has a few nuances worth documenting. Users expect to upload a file, pick an angle, and get a fixed PDF back — all without uploading their document to a server.
I built en.sotool.top/rotate/ to do exactly that. Here's how it works with Vue 3 and pdf-lib.
Why Client-Side Rotation?
PDFs often contain sensitive information. Scanned contracts, handwritten notes, receipts. Rotating a file on a server means trusting someone else with your document.
Client-side benefits:
- No upload bandwidth or size limits
- Instant rotation for normal files
- Works offline after the page loads
- Zero quality loss — rotation is a metadata change
The Stack
- Vue 3 — UI and state
- pdf-lib — Load, rotate, and save PDFs
- File API — Read the uploaded file
- lucide-vue-next — Icons
npm install pdf-lib
Loading the PDF
First, read the file and prepare it for rotation.
import { PDFDocument } from 'pdf-lib'
const pdfFile = ref<File | null>(null)
const angle = ref(90)
const processing = ref(false)
async function handleFile(files: File[]) {
if (files.length === 0) return
pdfFile.value = files[0]
}
The Rotation Angle Selector
Three preset angles: 90°, 180°, and 270°.
<div class="grid grid-cols-3 gap-3">
<button
v-for="deg in [90, 180, 270]"
:key="deg"
:class="angle === deg ? 'border-primary bg-primary/5 text-primary' : 'border-border text-text-muted'"
@click="angle = deg"
>
{{ deg }}°
</button>
</div>
The button highlights the selected angle. The user picks one and clicks rotate.
Performing the Rotation
This is the core logic. pdf-lib makes it surprisingly simple.
async function rotate() {
if (!pdfFile.value) return
processing.value = true
try {
const bytes = await pdfFile.value.arrayBuffer()
const pdf = await PDFDocument.load(bytes)
pdf.getPages().forEach(page => {
page.setRotation(degrees(angle.value))
})
const blob = new Blob([await pdf.save()], { type: 'application/pdf' })
downloadBlob(blob, 'rotated.pdf')
} catch (e) {
console.error(e)
alert('Processing failed')
} finally {
processing.value = false
}
}
The key is degrees() from pdf-lib. It converts degrees to radians internally. page.setRotation() modifies the page's /Rotate entry in the PDF dictionary.
Downloading the Result
function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
}
Lessons Learned
Rotation is a metadata change. Unlike compression or watermarking, rotation doesn't re-encode anything. The file size stays virtually identical because only the /Rotate entry in the PDF dictionary changes.
Rotate all pages at once. Our tool rotates every page with the same angle. This covers 95% of use cases — scanned documents, batch exports, etc. Supporting per-page rotation would require a more complex UI.
Handle the processing state. Even though rotation is fast, users might upload a 500 MB PDF. Show a loading indicator so they don't click twice.
No arbitrary angles. We support 90°, 180°, and 270°. For arbitrary angles (e.g., 45°), you'd need to re-render the page content, which is a much heavier operation.
Try It
The tool is live at en.sotool.top/rotate/.
Free, no signup, nothing uploads to a server.
Full source is on GitHub. The rotation logic is in src/views/Rotate.vue.
Want More Advanced PDF Tools?
If you need to rotate individual pages, use arbitrary angles, or batch-process hundreds of files with OCR, Wondershare PDFelement is a solid desktop option.
This post contains affiliate links.
Have you built PDF manipulation tools in the browser? What edge cases did you run into?
Top comments (0)