Data URI and Base64: Embedded Images in HTML and CSS

May 26, 20269 min read

Introduction

Data URIs (Uniform Resource Identifiers) allow you to embed files directly into HTML or CSS documents, eliminating the need for separate HTTP requests. This technique is commonly used with Base64-encoded images, fonts, and other small assets.

But is embedding assets always a good idea? This guide covers the Data URI format, its advantages and disadvantages, performance characteristics, and practical guidelines for when to use inline assets in your web projects.

What is a Data URI?

A Data URI is a URI scheme that embeds data directly into a document, defined in RFC 2397. Instead of linking to an external file, the data itself is included in the URI.

Data URI Syntax

data:[<media type>][;base64],<data>

The components are:

  • data: — The URI scheme identifier
  • <media type> — MIME type (e.g., image/png, text/plain)
  • ;base64 — Optional flag indicating Base64 encoding
  • <data> — The actual data (raw or Base64-encoded)

Examples

<!-- Inline PNG image -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" alt="Red dot">

<!-- Inline CSS background -->
<style>
  .icon {
    background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==');
  }
</style>

<!-- Inline plain text -->
<a href="data:text/plain;base64,SGVsbG8gV29ybGQ=">Download hello.txt</a>

How Base64 Encoding Works in Data URIs

When embedding binary data (like images) in a Data URI, the data must be Base64-encoded because URIs can only contain ASCII characters.

The Encoding Process

// Step 1: Get binary image data (e.g., from canvas or fetch)
const response = await fetch('image.png');
const blob = await response.blob();

// Step 2: Convert to Base64
const reader = new FileReader();
reader.onload = function() {
  const base64 = reader.result; // data:image/png;base64,iVBORw0KGgo...
  console.log(base64);
};
reader.readAsDataURL(blob);

// Step 3: Use in HTML/CSS
// <img src="data:image/png;base64,iVBORw0KGgo...">

Generating Data URIs in Different Languages

Node.js:

const fs = require('fs');

function createDataUri(filePath) {
  const mimeTypes = {
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.gif': 'image/gif',
    '.svg': 'image/svg+xml',
    '.woff2': 'font/woff2'
  };

  const ext = path.extname(filePath);
  const mimeType = mimeTypes[ext] || 'application/octet-stream';
  const data = fs.readFileSync(filePath);
  const base64 = data.toString('base64');

  return `data:${mimeType};base64,${base64}`;
}

const dataUri = createDataUri('logo.png');
console.log(dataUri); // data:image/png;base64,iVBORw0KGgo...

Python:

import base64
import mimetypes

def create_data_uri(file_path):
    mime_type, _ = mimetypes.guess_type(file_path)
    if not mime_type:
        mime_type = 'application/octet-stream'

    with open(file_path, 'rb') as f:
        data = f.read()

    base64_data = base64.b64encode(data).decode('ascii')
    return f'data:{mime_type};base64,{base64_data}'

data_uri = create_data_uri('logo.png')
print(data_uri)

Advantages of Data URIs

1. Reduced HTTP Requests

Each external resource requires a separate HTTP request. Data URIs eliminate these requests for embedded assets:

Without Data URIs:
- HTML document (1 request)
- logo.png (1 request)
- icon.svg (1 request)
- font.woff2 (1 request)
= 4 HTTP requests

With Data URIs:
- HTML document with embedded assets (1 request)
= 1 HTTP request

2. No External Dependencies

Embedded assets are self-contained within the document. If the HTML file is saved or shared, all embedded resources come along with it.

3. Useful for Email HTML

Many email clients block external images by default. Data URIs allow images to display immediately without requiring the user to "show images":

<!-- Email-safe embedded image -->
<img src="data:image/png;base64,iVBORw0KGgo..." alt="Company Logo">

4. Works Offline

Since the data is embedded, it works without network connectivity (as long as the containing document is available).

Disadvantages of Data URIs

1. Increased File Size

Base64 encoding increases the data size by approximately 33% compared to the original binary:

Original PNG: 10 KB
Base64-encoded: ~13.3 KB (+33%)

Original JPEG: 50 KB
Base64-encoded: ~66.7 KB (+33%)

This size increase happens because Base64 represents 3 bytes of binary data using 4 ASCII characters.

2. Not Cacheable Independently

When you embed an image in HTML or CSS, it becomes part of that file. If the same image is used on 10 pages, it gets downloaded 10 times (once per page).

External image approach:
- logo.png cached after first download
- All pages reference the same cached file

Data URI approach:
- logo.png embedded in each HTML file
- Each page download includes the full image data

3. Slower Parsing and Rendering

The browser must decode the Base64 data before rendering, which takes additional CPU time:

// Browser has to do this internally:
const binaryData = atob(base64String); // Decoding step
// Then render the image

For large images or many embedded assets, this can impact page load performance.

4. Increased HTML/CSS File Size

Embedding large assets makes your HTML or CSS files significantly larger, which can slow down initial page parsing:

/* Embedding a 100KB font file in CSS */
@font-face {
  font-family: 'CustomFont';
  src: url('data:font/woff2;base64,d09GMgABAAAA...[100KB of Base64]...') format('woff2');
}
/* This CSS file is now 100KB+ larger */

5. No Progressive Loading

External images can be loaded progressively (especially JPEG), showing a low-quality preview while downloading. Data URIs must be fully decoded before rendering.

Performance Comparison

Here's a realistic comparison of different approaches:

MetricExternal FileData URIHTTP/2 Multiplexing
HTTP Requests1 per file0 (embedded)1 connection, multiple streams
File SizeOriginal+33% (Base64)Original
CacheabilityYes (independent)No (part of parent)Yes (independent)
Parsing OverheadNoneBase64 decodeNone
Best ForLarge assets, repeated assetsTiny assets, one-time useGeneral purpose

When HTTP/2 Changes the Equation

With HTTP/2, multiple files can be requested over a single connection with multiplexing. The request overhead is much lower, reducing the advantage of Data URIs:

HTTP/1.1: 10 assets = 10 connections = slow
HTTP/2:   10 assets = 1 connection, multiplexed = fast

Conclusion: Data URIs matter less with HTTP/2

When to Use Data URIs

✅ Good Use Cases

1. Very Small Images (under 2KB)

<!-- Small icons, spacers, simple SVG -->
<img src="data:image/png;base64,iVBORw0KGgoAAAA..." alt="icon">

2. Email Templates

<!-- Email clients often block external images -->
<img src="data:image/png;base64,..." alt="Logo">

3. One-Time Use Assets If an image appears only on one page and is very small, embedding can reduce requests.

4. SVG Icons (with caveats)

<!-- Simple SVG icons can be inlined -->
<svg viewBox="0 0 24 24">
  <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
</svg>
<!-- Or as Data URI for CSS background -->

5. Offline-First Applications When building offline-capable apps where every asset must be self-contained.

❌ Bad Use Cases

1. Large Images

<!-- DON'T DO THIS -->
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...[500KB of Base64]...">
<!-- Use an external file instead -->
<img src="photo.jpg">

2. Repeated Assets If the same image appears on multiple pages, use an external file for caching.

3. Frequently Changing Assets If you update the image often, you'd need to update every file that embeds it.

4. Font Files (Usually)

/* AVOID for large font files */
@font-face {
  src: url('data:font/woff2;base64,...') format('woff2');
}
/* Use external file with cache */

Practical Guidelines

The 2KB Rule

As a general rule, only embed assets smaller than 2KB as Data URIs. Beyond that size, the benefits of caching and separate HTTP requests outweigh the request reduction.

Use Build Tools for Optimization

Modern build tools can automatically decide what to embed:

Webpack (url-loader + limit):

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 2048, // 2KB - embed if smaller, else file
              name: 'images/[name].[hash].[ext]'
            }
          }
        ]
      }
    ]
  }
};

Vite (built-in asset handling):

// Vite automatically inlines assets below a threshold
// config: build.assetsInlineLimit (default: 4096 bytes)

SVG: Special Considerations

For SVG images, you have three options:

<!-- Option 1: Inline SVG (best for icons, interactive graphics) -->
<svg viewBox="0 0 24 24">
  <path d="..."/>
</svg>

<!-- Option 2: Data URI with Base64 -->
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0...">

<!-- Option 3: Data URI with URL-encoded SVG (smaller than Base64) -->
<img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3C%2Fsvg%3E">

URL-encoded SVG is often smaller than Base64-encoded SVG for simple graphics.

Security Considerations

Content Security Policy (CSP)

Data URIs may be blocked by strict CSP policies:

# This policy blocks Data URIs for images
Content-Security-Policy: default-src 'self'; img-src 'self'

# To allow Data URIs (less secure):
Content-Security-Policy: default-src 'self'; img-src 'self' data:

XSS Risks

Never allow user-supplied data to be embedded as Data URIs without proper validation:

// DANGEROUS: User-supplied Data URI
const userInput = req.body.avatar; // "data:text/html,<script>alert('xss')</script>"
document.getElementById('avatar').src = userInput; // XSS!

// SAFE: Validate the Data URI
function isValidImageDataUri(uri) {
  return /^data:image\/(png|jpeg|gif|webp|svg\+xml);base64,/.test(uri);
}

Summary

Data URIs with Base64 encoding are a useful tool in specific scenarios, but they are not a silver bullet for performance optimization.

Use Data URIs when:

  • Assets are very small (< 2KB)
  • Assets are used only once
  • Building email templates
  • HTTP requests are extremely expensive (rare in HTTP/2)

Avoid Data URIs when:

  • Assets are large
  • Assets are reused across pages
  • You need browser caching
  • The asset changes frequently