HTTP/2 Optimization
Leverage HTTP/2 features like multiplexing, server push, and header compression
HTTP/2 Optimization
HTTP/2 brings major performance improvements over HTTP/1.1. Understanding and leveraging its features can significantly boost your application's speed.
HTTP/1.1 vs HTTP/2
HTTP/1.1 Problems
1. Head-of-line blocking: Requests queue up
2. Multiple connections: 6-8 connections per domain
3. Large headers: Repeated on every request
4. No prioritization: All requests equal priority
5. Text protocol: Inefficient parsingHTTP/2 Solutions
1. Multiplexing: Multiple requests over single connection
2. Single connection: One connection per domain
3. Header compression: HPACK algorithm
4. Stream prioritization: Critical resources first
5. Binary protocol: Faster parsing
6. Server push: Proactively send resourcesMultiplexing
HTTP/2 allows multiple requests/responses simultaneously over a single connection.
HTTP/1.1 (Serial)
Connection 1: Request CSS → Wait → Response CSS
Connection 2: Request JS → Wait → Response JS
Connection 3: Request IMG → Wait → Response IMG
Problem: Limited connections, head-of-line blockingHTTP/2 (Parallel)
Single Connection:
Stream 1: Request CSS ⟷ Response CSS
Stream 2: Request JS ⟷ Response JS
Stream 3: Request IMG ⟷ Response IMG
All happen simultaneously!Impact on Bundling
HTTP/1.1: Bundle everything to reduce requests
- bundle.js (500 KB)
- bundle.css (100 KB)
HTTP/2: Split into smaller files
- home.js (50 KB)
- auth.js (30 KB)
- dashboard.js (80 KB)
- shared.js (40 KB)
- ...
Benefits:
- Better caching (change home.js, others cached)
- Parallel downloads
- Faster parsingServer Push
Server push allows the server to send resources before the client requests them.
Example Flow
Client: Requests index.html
Server: Sends index.html
+ PUSH: style.css
+ PUSH: script.js
+ PUSH: logo.svg
Client: Receives everything immediatelyImplementation
Nginx
location / {
# Push critical resources
http2_push /css/critical.css;
http2_push /js/app.js;
http2_push /fonts/inter-var.woff2;
root /var/www/html;
index index.html;
}Node.js (http2 module)
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert'),
});
server.on('stream', (stream, headers) => {
if (headers[':path'] === '/') {
// Push critical resources
stream.pushStream({ ':path': '/css/app.css' }, (err, pushStream) => {
pushStream.respondWithFile('public/css/app.css', {
'content-type': 'text/css',
});
});
stream.pushStream({ ':path': '/js/app.js' }, (err, pushStream) => {
pushStream.respondWithFile('public/js/app.js', {
'content-type': 'application/javascript',
});
});
// Send HTML
stream.respondWithFile('public/index.html', {
'content-type': 'text/html',
});
}
});
server.listen(443);Link Preload Headers
// Express
app.get('/', (req, res) => {
res.set('Link', [
'</css/app.css>; rel=preload; as=style',
'</js/app.js>; rel=preload; as=script',
'</fonts/inter.woff2>; rel=preload; as=font; crossorigin',
].join(', '));
res.sendFile('index.html');
});What to Push
✅ Push:
- Critical CSS
- Hero image
- Primary JavaScript bundle
- Web fonts
❌ Don't Push:
- Non-critical resources
- Resources likely cached
- Large files (> 50 KB)
- User-specific dataPush Caching Problem
Problem: Server can't know if client has cached resource
Solution: Cache-Digest header (experimental)
Client sends digest of cached resources
Server only pushes what's missingHeader Compression (HPACK)
HTTP/2 compresses headers, reducing overhead significantly.
HTTP/1.1 (Repeated Headers)
Request 1:
GET /api/users HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0...
Accept: application/json
Authorization: Bearer token...
Cookie: session=abc...
(500-800 bytes per request)
Request 2: All headers repeated again
Request 3: All headers repeated again
...HTTP/2 (HPACK Compression)
Request 1:
:method: GET
:path: /api/users
:authority: api.example.com
user-agent: Mozilla/5.0...
(Indexed in static/dynamic table)
Request 2:
:method: 2 (index reference)
:path: 3 (index reference)
... (mostly indexes, ~50 bytes)
Savings: 80-90% reduction in header sizeStream Prioritization
HTTP/2 allows prioritizing critical resources.
Priority Levels
Critical (Highest):
- HTML document
- Critical CSS
- Fonts
High:
- Visible images
- Primary JavaScript
Medium:
- Non-critical JavaScript
- Below-fold images
Low:
- Analytics
- Ads
- Tracking scriptsImplementation
// Chromium priority hints
<link rel="preload" href="/critical.css" as="style" importance="high" />
<link rel="preload" href="/analytics.js" as="script" importance="low" />
<img src="/hero.jpg" importance="high" />
<img src="/footer-logo.jpg" importance="low" />
<script src="/app.js" importance="high"></script>
<script src="/analytics.js" importance="low"></script>Enabling HTTP/2
Nginx
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# HTTP/2 push
http2_push_preload on;
location / {
root /var/www/html;
index index.html;
}
}Apache
# Requires mod_http2
LoadModule http2_module modules/mod_http2.so
<VirtualHost *:443>
ServerName example.com
# Enable HTTP/2
Protocols h2 http/1.1
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/key.pem
</VirtualHost>Node.js
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert'),
allowHTTP1: true, // Fallback to HTTP/1.1
});
server.on('stream', (stream, headers) => {
stream.respond({
'content-type': 'text/html',
':status': 200,
});
stream.end('<html><body>Hello HTTP/2!</body></html>');
});
server.listen(443);Cloudflare
HTTP/2 is enabled by default on Cloudflare (free plan included).
HTTP/2 Best Practices
1. Don't Concatenate Everything
// ❌ HTTP/1.1 approach (bad for HTTP/2)
import './a.js';
import './b.js';
import './c.js';
// Webpack bundles into bundle.js (500 KB)
// ✅ HTTP/2 approach (split into chunks)
// Webpack code splitting
const A = lazy(() => import('./a.js')); // 50 KB
const B = lazy(() => import('./b.js')); // 80 KB
const C = lazy(() => import('./c.js')); // 30 KB2. Split by Route/Feature
// next.config.js
module.exports = {
webpack: (config) => {
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
home: {
test: /[\\/]pages[\\/]index/,
name: 'home',
priority: 10,
},
dashboard: {
test: /[\\/]pages[\\/]dashboard/,
name: 'dashboard',
priority: 10,
},
},
};
return config;
},
};3. Push Sparingly
Only push truly critical resources that are:
- Small (< 50 KB)
- Guaranteed to be needed
- Not likely cached
4. Use Link Preload
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<head>
<link
rel="preload"
href="/css/critical.css"
as="style"
/>
<link
rel="preload"
href="/fonts/inter-var.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</head>
<body>{children}</body>
</html>
);
}5. Enable HTTP/3 (QUIC)
HTTP/3 builds on HTTP/2 with even better performance:
# Nginx (with QUIC support)
listen 443 ssl http3;
listen 443 ssl http2;
add_header Alt-Svc 'h3=":443"; ma=86400';Domain Sharding (Don't Do This with HTTP/2!)
❌ HTTP/1.1 optimization (bad for HTTP/2):
static1.example.com
static2.example.com
static3.example.com
static4.example.com
✅ HTTP/2 optimization:
example.com (single domain)
Reason: HTTP/2 multiplexing makes multiple domains slower (extra DNS, TCP, TLS)Testing HTTP/2
Check if HTTP/2 is Enabled
# cURL
curl -I --http2 https://example.com
# Should see:
HTTP/2 200
# Check protocol in browser DevTools
Network tab → Protocol column → h2Verify Server Push
# Chrome DevTools
Network tab → Look for "Push / " in Initiator columnTest Performance
// Measure time to load all resources
performance.getEntriesByType('resource').forEach(entry => {
console.log(entry.name, entry.nextHopProtocol); // 'h2' for HTTP/2
});HTTP/2 vs HTTP/3
HTTP/2:
- Uses TCP
- Head-of-line blocking at TCP level
- 95%+ support
HTTP/3 (QUIC):
- Uses UDP
- No head-of-line blocking
- Faster connection establishment
- Better for mobile (connection migration)
- 75%+ support (growing)Best Practices Summary
- Enable HTTP/2: Everywhere (it's 2024!)
- Split Code: Smaller chunks, better caching
- Single Domain: No domain sharding
- Server Push: Only critical resources
- Prioritization: Use importance hints
- Monitor: Check protocol in DevTools
- HTTP/3: Enable if supported
- TLS Required: HTTP/2 requires HTTPS
Common Pitfalls
❌ Bundling everything: Defeats HTTP/2 benefits
✅ Code splitting by route/feature
❌ Domain sharding: Slower with HTTP/2
✅ Single domain
❌ Pushing everything: Wastes bandwidth
✅ Push only critical, small resources
❌ No prioritization: Everything loads equally
✅ Use importance hints
HTTP/2 is a game-changer—use it properly and see dramatic performance improvements!