Base64 Files
Deep Dive

Why Large Files Don't Work Well with Base64?
And When Should You Use It?

Base64 is a convenient way to turn a file into a text string, but it comes with real costs: larger size, doubled memory, and browser freezes. This article explains why, and helps you decide when to use Base64 and when to switch to a better option.

Why Base64 Makes Files Larger

The core idea behind Base64 is: take 3 bytes (24 bits) of raw data at a time, split them into four 6-bit groups, and represent each group with one of 64 printable ASCII characters (A–Z, a–z, 0–9, +, /).

The result: 3 bytes become 4 characters. Each character takes 1 byte, so the size grows to 4/3 ≈ 133% of the original.

Encoding Example

Raw Bytes
4D616E
3 bytes = 24 bits
Base64 Chars
TWFu
4 chars = 4 bytes

+33%

Size Increase

If the original data length is not a multiple of 3, = padding is added to make it a multiple of 4, so the actual increase is slightly more than 33%.

For example: a 5 MB image becomes about 6.7 MB in Base64; a 100 MB video becomes about 133 MB — 33 MB more than the original.

Why Browsers Freeze When Converting Large Files

The most common approach is FileReader.readAsDataURL(file). The problem lies in how this API works:

  1. 1
    Reads the entire file into memory:The whole file is loaded into an ArrayBuffer at once. A 50 MB file occupies 50 MB of memory immediately.
  2. 2
    Encodes on the main thread:Base64 encoding runs on the JavaScript main thread. During this time, the browser cannot respond to user interaction — the page freezes.
  3. 3
    Builds a massive string:The result is an enormous string (a 50 MB file produces ~67 MB of Base64). JavaScript strings are immutable, so constructing one involves heavy memory allocation and copying.
  4. 4
    Peak memory doubles:The original ArrayBuffer and the Base64 string both exist in memory at the same time, so peak usage is about 2.3x the file size. A 50 MB file can instantly consume ~115 MB of memory.

How to Avoid Freezing

Move the Base64 encoding into a Web Worker. The main thread passes the ArrayBuffer via postMessage(buffer, [buffer]) using a zero-copy transfer, the Worker encodes it in the background and sends the result back. The entire process does not block the UI — the page stays responsive.

Three Approaches: Which One Should You Use?

Different scenarios have different optimal solutions. Here are typical use cases for each of the three file handling approaches:

Use Base64
  • Small CSS background images (< 5 KB)
  • Inline favicon / app icon
  • Embedded images in HTML email
  • Inline small fonts with @font-face
  • Small attachments in JSON API (signatures, thumbnails)
  • Generate browser-side download links with Data URLs
Use multipart/form-data
  • User uploads avatars or documents to your server
  • Server-side validation of file type and size is needed
  • Submitted together with form fields
  • Uploaded via XMLHttpRequest or Fetch
  • File size is unknown (anywhere from a few KB to hundreds of MB)
  • Upload progress bar is needed (XHR progress event)
Use Object Storage
  • Images, videos, or audio that need long-term storage
  • CDN acceleration or global access is required
  • Frontend presigned URL direct upload (bypasses the server)
  • Large file (> 10 MB) multipart upload
  • Multiple users share the same file
  • Pay-as-you-go cost control is needed

File Size Reference Boundaries

There are no hard rules, but these boundaries are followed by most developers:

File SizeRecommended ApproachNotes
< 5 KBInline Base64CSS backgrounds, icons, email images — saves an HTTP request
5 KB – 1 MBBase64 or multipartDepends on whether inline is needed; multipart saves bandwidth for larger images
1 MB – 10 MBmultipart/form-dataBase64 overhead becomes significant; longer upload time, more browser memory pressure
> 10 MBDirect object storage uploadPresigned URL direct upload to S3/OSS from the frontend, no app server bandwidth used
> 100 MBMultipart object storage uploadResumable upload with concurrent parts — avoids single-request timeouts

Handling Large-File Base64 with a Web Worker

If you really need to Base64-encode a large file in the browser, offloading the work to a Worker prevents UI freezes:

base64.worker.js
self.onmessage = function(e) {
  const buffer = e.data            // ArrayBuffer (zero-copy transfer)
  const bytes  = new Uint8Array(buffer)
  let binary   = ''
  const CHUNK  = 8192
  for (let i = 0; i < bytes.length; i += CHUNK) {
    binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK))
  }
  const base64 = btoa(binary)
  self.postMessage(base64)
}
Main Thread Usage
const worker = new Worker('/base64.worker.js')

input.addEventListener('change', async (e) => {
  const file   = e.target.files[0]
  const buffer = await file.arrayBuffer()

  // Transferable: buffer ownership transferred to Worker (zero-copy)
  worker.postMessage(buffer, [buffer])
})

worker.onmessage = (e) => {
  const base64 = e.data             // encoded result
  console.log('data:...;base64,' + base64)
}

The second argument to postMessage(buffer, [buffer]) is the Transferable list. The ArrayBuffer ownership is transferred zero-copy to the Worker, and the main thread's buffer becomes empty (byteLength 0), avoiding doubled memory usage.

Quick Decision: One-line Guide

Q
Need to hard-code an image into CSS or HTML?Use Base64.
Q
User wants to send a file to your server?Use multipart/form-data.
Q
File needs to be stored, reused, or served via CDN?Use object storage + presigned URL.
Q
File is only processed in the browser and not uploaded?Use a Blob URL (URL.createObjectURL) — not Base64.
Q
API requires Base64, but the file is large?Consider switching to multipart or a presigned URL, and negotiate with the API provider.
Q
Must use Base64 and the file is > 5 MB?Use a Web Worker to run encoding in a background thread.

Related Tools

Frequently Asked Questions

Why does a file get larger after Base64 encoding?

Base64 encodes every 3 bytes as 4 ASCII characters, making the output 4/3 the original size — about 33% larger. If the data length is not a multiple of 3, = padding is added, making the actual increase slightly over 33%.

Why does the browser freeze when converting a large file to Base64?

FileReader.readAsDataURL() reads and encodes the entire file on the main thread, blocking all UI updates. Combined with both the raw data and the Base64 string in memory at once, peak usage is about 2.3x the file size. Use a Web Worker to fix this.

What is the difference between a Blob URL and a Base64 Data URL?

A Blob URL (URL.createObjectURL) is a temporary browser-internal reference that does not copy data, so memory usage is minimal — ideal for displaying or downloading files within the browser. A Base64 Data URL is a real text string you can embed in HTML, CSS, or JSON. It is suitable for inline scenarios but much larger.

When is multipart/form-data better than Base64?

When uploading to a server, multipart sends raw binary data as a stream with no size overhead. The server can write to disk while receiving, it supports progress events, and it handles any file size. Base64 is only suitable for small inline attachments or JSON API payloads.

What are Transferables in Web Workers?

When you call postMessage(buffer, [buffer]), the ArrayBuffer ownership is zero-copy transferred to the Worker — the main thread's variable becomes empty (byteLength 0) immediately. Compared to copying, Transferables are both faster and more memory-efficient for large files.

How do presigned URLs for object storage work?

The backend generates a time-limited, signed upload URL using the AWS SDK or OSS SDK. The frontend uses that URL to PUT the file directly to the storage service, bypassing the app server entirely. The file does not consume server bandwidth or memory — ideal for large files.