DEV Community

sunshey
sunshey

Posted on

How I Rotate PDFs in the Browser with Vue 3 and pdf-lib

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
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)