I needed a dead-simple way to fix rotated PDFs without sending them to a server. Most tools want you to upload the file, wait for processing, then download it. For scans and contracts that's a dealbreaker.
So I built en.sotool.top/rotate — pick a PDF, choose the pages and angle, get a correctly oriented file. All in the browser. No server involved.
Here's how it works under the hood with Vue 3 and pdf-lib.
Why Client-Side?
The obvious reason is privacy. Scans, contracts, medical records — people don't want them on a stranger's server. Beyond that:
- No upload bandwidth limits
- No file size caps from your backend
- Nothing to clean up on a server
- Works offline after the page loads
The catch? You're limited by what the browser can do. For rotation, pdf-lib is perfect because it can set the rotation angle on individual pages without re-rendering the entire PDF.
The Stack
- Vue 3 — UI and state
- pdf-lib — Load, modify, save PDFs
- File API — Read the uploaded PDF
npm install pdf-lib
Loading the PDF
First, read the file into an ArrayBuffer and load it with pdf-lib.
import { PDFDocument } from 'pdf-lib';
async function loadPdf(file) {
const arrayBuffer = await file.arrayBuffer();
const pdfDoc = await PDFDocument.load(arrayBuffer);
return pdfDoc;
}
Rotating Pages
Each page in pdf-lib has a setRotation method that takes a degrees value. Common angles are 90, 180, and 270.
function rotatePages(pdfDoc, angle, selectedPages = null) {
const pages = pdfDoc.getPages();
const targets = selectedPages ?? pages.map((_, i) => i);
targets.forEach((index) => {
const page = pages[index];
if (!page) return;
const currentRotation = page.getRotation().angle;
const newRotation = (currentRotation + angle) % 360;
page.setRotation({ angle: newRotation });
});
return pdfDoc;
}
The key detail is reading the existing rotation first. Some PDFs already have a rotation value baked in, and you want to add to it, not overwrite it.
Saving and Downloading
Once the pages are rotated, save the document and trigger a download.
async function saveAndDownload(pdfDoc, filename) {
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
Wiring It Up in Vue 3
A minimal component looks like this:
<template>
<input type="file" accept="application/pdf" @change="handleFile" />
<select v-model="angle">
<option :value="90">90°</option>
<option :value="180">180°</option>
<option :value="270">270°</option>
</select>
<button @click="rotate" :disabled="!pdfDoc">Rotate PDF</button>
</template>
<script setup>
import { ref } from 'vue';
import { PDFDocument } from 'pdf-lib';
const pdfDoc = ref(null);
const angle = ref(90);
const originalName = ref('');
async function handleFile(event) {
const file = event.target.files[0];
if (!file) return;
originalName.value = file.name.replace(/\.pdf$/i, '');
const arrayBuffer = await file.arrayBuffer();
pdfDoc.value = await PDFDocument.load(arrayBuffer);
}
async function rotate() {
if (!pdfDoc.value) return;
const pages = pdfDoc.value.getPages();
pages.forEach((page) => {
const current = page.getRotation().angle;
page.setRotation({ angle: (current + angle.value) % 360 });
});
const bytes = await pdfDoc.value.save();
const blob = new Blob([bytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${originalName.value}-rotated.pdf`;
a.click();
URL.revokeObjectURL(url);
}
</script>
Handling Mixed Orientations
In the real tool, users can rotate all pages or pick specific ones. The logic is the same — you just pass a list of page indexes instead of rotating every page.
const selectedPages = [0, 2, 5]; // zero-based indexes
selectedPages.forEach((index) => {
const page = pages[index];
const current = page.getRotation().angle;
page.setRotation({ angle: (current + angle) % 360 });
});
Why Not Just Rotate the Canvas?
You could render each page to a canvas, rotate the canvas, and export a new PDF. That works, but it re-enders the content, which can lose vector quality, bloat file size, and break text selection. pdf-lib updates the page's rotation metadata directly, so the original content stays intact.
Try It
If you want to see it in action:
Free, no signup, and your file never leaves the browser.
Built with Vue 3 and pdf-lib. If you need desktop-grade editing, check out Wondershare PDFelement.
Top comments (0)