Data URI and Base64: Embedded Images in HTML and CSS
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:
| Metric | External File | Data URI | HTTP/2 Multiplexing |
|---|---|---|---|
| HTTP Requests | 1 per file | 0 (embedded) | 1 connection, multiple streams |
| File Size | Original | +33% (Base64) | Original |
| Cacheability | Yes (independent) | No (part of parent) | Yes (independent) |
| Parsing Overhead | None | Base64 decode | None |
| Best For | Large assets, repeated assets | Tiny assets, one-time use | General 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
Related Tools
- Encode and decode Base64 with our Base64 Encoder/Decoder