Shared Dictionaries — Khi trình duyệt chỉ tải những gì thực sự thay đổi

Posted on: 4/22/2026 9:12:24 AM

90%+Giảm payload so với Gzip
2.6 KBThay vì 92 KB (Gzip) cho bundle 272 KB
RFC 9842Chuẩn hóa chính thức từ 09/2025
Chrome 130+Đã hỗ trợ từ 10/2024

Bài toán: Tại sao nén truyền thống không còn đủ?

Mỗi khi bạn deploy phiên bản mới của JavaScript bundle, dù chỉ sửa một dòng code, toàn bộ file nén phải được truyền lại từ đầu. Gzip và Brotli nén tốt so với bản gốc — nhưng chúng không biết rằng trình duyệt đã có 99% nội dung giống hệt từ lần tải trước.

Với mô hình phát triển hiện đại — CI/CD deploy nhiều lần mỗi ngày, AI-assisted coding tăng tốc độ ship code — vấn đề này ngày càng nghiêm trọng. Ship 10 thay đổi nhỏ mỗi ngày nghĩa là bạn đã vô tình vô hiệu hóa cache 10 lần.

Vấn đề cốt lõi

Nén truyền thống (Gzip, Brotli, Zstd) hoạt động như thể mỗi request là lần đầu tiên. Shared Dictionaries cho phép server nén chỉ phần khác biệt so với phiên bản client đã cache — giống như git diff cho HTTP.

Gzip → Brotli → Zstandard: Lịch sử nén web

Gzip — Chuẩn mực 30 năm

Ra đời năm 1992, sử dụng thuật toán DEFLATE với cửa sổ 32 KB. Gzip vẫn là lựa chọn mặc định của hầu hết web server vì tính tương thích phổ quát. Tuy nhiên, giới hạn dictionary window 32 KB đồng nghĩa với việc nó không tận dụng được các pattern lặp lại ở khoảng cách xa trong file lớn.

Brotli — Bước tiến từ Google

Phát hành năm 2015, Brotli mang theo một built-in dictionary chứa các pattern phổ biến trên web (thẻ HTML, thuộc tính CSS, từ khóa JavaScript). Kết quả là nén tốt hơn Gzip 10-20% cho nội dung web, với cửa sổ lên đến 16 MB. Đến 2026, Brotli được hỗ trợ bởi 98%+ trình duyệt.

Zstandard (Zstd) — Tốc độ và hiệu quả

Được Facebook phát triển và chuẩn hóa qua RFC 8878, Zstandard đạt t��� lệ nén gần bằng Brotli nhưng nhanh hơn đáng kể — nén nhanh hơn 42% với tỷ lệ tương đương. Zstd đặc biệt mạnh khi sử dụng custom dictionary vì kiến trúc của nó được thiết kế từ đầu cho dictionary-based compression.

Thuật toánRa đờiDictionary WindowTốc độ nénTỷ lệ nénBrowser Support
Gzip (DEFLATE)199232 KBNhanhCơ bản100%
Brotli201516 MBChậm hơn Gzip+10-20% vs Gzip98%+
Zstandard2016Rất lớnNhanh hơn Brotli 42%≈ BrotliChrome 123+, Firefox 126+

Shared Dictionaries: Nén delta cho HTTP

Ý tưởng đơn giản nhưng mạnh mẽ: thay vì nén từ con số 0, server sử dụng phiên bản cũ mà client đã cache làm dictionary. Kết quả? Chỉ truyền phần diff — giống hệt cách git lưu trữ thay đổi giữa các commit.

sequenceDiagram
    participant B as Trình duyệt
    participant S as Server

    Note over B,S: Lần tải đầu tiên
    B->>S: GET /js/app.bundle.js
    S->>B: 200 OK (92 KB Brotli)
Use-As-Dictionary: match="/js/app.bundle.js" Note over B: Cache file + đánh dấu làm dictionary Note over B,S: Lần tải sau (file đã thay đổi) B->>S: GET /js/app.bundle.js
Available-Dictionary: :hash-abc:
Accept-Encoding: br, zstd, dcb, dcz S->>B: 200 OK (2.6 KB dcz)
Chỉ gửi phần khác biệt! Note over B: Giải nén bằng dictionary đã cache

Luồng hoạt động của Shared Dictionaries — lần tải sau chỉ truyền delta

Các HTTP Header mới

Use-As-Dictionary — Server gửi header này kèm response, báo cho trình duyệt: "Giữ lại file này vì nó sẽ hữu ích cho lần sau". Header chứa pattern match cho URL mà dictionary sẽ áp dụng.

HTTP/1.1 200 OK
Content-Type: application/javascript
Content-Encoding: br
Use-As-Dictionary: match="/js/app.bundle.js"

Available-Dictionary — Trình duyệt gửi header này trong request tiếp theo, kèm hash SHA-256 của dictionary đã cache, báo cho server: "Đây là những gì tôi đã có".

GET /js/app.bundle.js HTTP/1.1
Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fCG4=:
Accept-Encoding: br, zstd, dcb, dcz

dcb / dcz — Hai Content-Encoding mới:

  • dcb: Dictionary-Compressed Brotli
  • dcz: Dictionary-Compressed Zstandard

Hiệu năng thực tế: Những con số ấn tượng

97%Giảm so với Gzip (272KB → 2.6KB)
99.5%Giảm cho bundle deploy liên tục (Cloudflare demo)
78%Giảm so với Brotli cho YouTube JS (sau 2 tháng)
90%Giảm so với Brotli cho YouTube JS (sau 1 tuần)

Case study: YouTube Desktop

Phương phápKích thướcSo với Brotli
Không nén10 MB
Brotli1.8 MBBaseline
Dictionary (deploy cách 2 tháng)384 KB-78%
Dictionary (deploy cách 1 tuần)172 KB-90%

Case study: Amazon Product Pages

Phương phápKích thước HTMLSo với Brotli
Không nén539 KB
Brotli84 KBBaseline
Brotli + Custom Dictionary10 KB-88%

Khi nào hiệu quả nhất?

Shared Dictionaries đặc biệt mạnh với: (1) JavaScript/CSS bundles deploy thường xuyên — chỉ vài dòng thay đổi mỗi lần, (2) SPA frameworks với code splitting — nhiều chunk chia sẻ cấu trúc giống nhau, (3) WASM applications (Figma, Google Earth) — cải thiện lên đến 95%.

Triển khai trên Server

Node.js với Zstandard Dictionary

const zlib = require('zlib');
const fs = require('fs');
const crypto = require('crypto');

// Tạo dictionary từ phiên bản cũ
const oldBundle = fs.readFileSync('dist/app.v1.js');
const newBundle = fs.readFileSync('dist/app.v2.js');

// Nén KHÔNG dictionary
const normalCompressed = zlib.zstdCompressSync(newBundle);
console.log(`Zstd thường: ${normalCompressed.length} bytes`);

// Nén VỚI dictionary (phiên bản cũ làm dictionary)
const dictCompressed = zlib.zstdCompressSync(newBundle, {
  dictionary: oldBundle
});
console.log(`Zstd + dictionary: ${dictCompressed.length} bytes`);

// Tính hash cho Available-Dictionary header
const dictHash = crypto
  .createHash('sha256')
  .update(oldBundle)
  .digest('base64');
console.log(`Dictionary hash: :${dictHash}:`);

Express.js Middleware

const express = require('express');
const app = express();

// Lưu trữ dictionary versions
const dictionaryStore = new Map();

app.get('/js/:file', (req, res) => {
  const filePath = `dist/${req.params.file}`;
  const content = fs.readFileSync(filePath);
  const dictHeader = req.headers['available-dictionary'];
  const acceptEnc = req.headers['accept-encoding'] || '';

  if (dictHeader && acceptEnc.includes('dcz')) {
    // Client có dictionary — gửi delta
    const dictHash = dictHeader.replace(/:/g, '');
    const dictionary = dictionaryStore.get(dictHash);

    if (dictionary) {
      const delta = zlib.zstdCompressSync(content, { dictionary });
      res.set('Content-Encoding', 'dcz');
      res.set('Vary', 'Accept-Encoding, Available-Dictionary');
      return res.send(delta);
    }
  }

  // Fallback: nén thường + đánh dấu làm dictionary cho lần sau
  const compressed = zlib.brotliCompressSync(content);
  const hash = crypto.createHash('sha256').update(content).digest('base64');

  dictionaryStore.set(hash, content);

  res.set('Content-Encoding', 'br');
  res.set('Use-As-Dictionary', `match="/js/${req.params.file}"`);
  res.set('Vary', 'Accept-Encoding, Available-Dictionary');
  res.send(compressed);
});

Nginx Configuration

# Bật Zstd compression
zstd on;
zstd_comp_level 3;
zstd_types application/javascript application/json text/css;

# Forward Shared Dictionary headers
proxy_set_header Available-Dictionary $http_available_dictionary;
proxy_pass_header Use-As-Dictionary;

# Vary header cho caching đúng
add_header Vary "Accept-Encoding, Available-Dictionary" always;

Cloudflare Shared Dictionaries: Lộ trình 3 giai đoạn

graph LR
    subgraph Phase1["Phase 1: Passthrough (Beta 04/2026)"]
        A1[Origin tự quản lý dictionary] --> A2[Cloudflare forward headers]
        A2 --> A3[Cache key mở rộng
Vary: Available-Dictionary] end subgraph Phase2["Phase 2: Managed Dictionaries"] B1[Admin cấu hình rules] --> B2[Edge inject headers] B2 --> B3[Delta compress tại edge] end subgraph Phase3["Phase 3: Auto Detection"] C1[Cloudflare phát hiện
URL patterns versioned] --> C2[Tự lưu version cũ] C2 --> C3[Tự nén delta] end Phase1 --> Phase2 --> Phase3 style A1 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style A2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style A3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50 style B1 fill:#e94560,stroke:#fff,color:#fff style B2 fill:#e94560,stroke:#fff,color:#fff style B3 fill:#e94560,stroke:#fff,color:#fff style C1 fill:#2c3e50,stroke:#fff,color:#fff style C2 fill:#2c3e50,stroke:#fff,color:#fff style C3 fill:#2c3e50,stroke:#fff,color:#fff

Lộ trình Shared Dictionaries trên Cloudflare — từ passthrough đến tự động hoàn toàn

Phase 1 — Bắt đầu từ 30/04/2026

Cloudflare sẽ forward các header Use-As-Dictionary, Available-Dictionary và các encoding dcb/dcz mà không strip hoặc recompress. Origin server tự quản lý dictionary — Cloudflare chỉ đảm bảo CDN layer không chặn protocol mới.

Bảo mật: Bài học từ SDCH và giải pháp RFC 9842

Năm 2008, Google triển khai SDCH (Shared Dictionary Compression over HTTP) trong Chrome — một ý tưởng tương tự. Tuy nhiên, SDCH bị loại bỏ vào 2017 do các lỗ hổng bảo mật nghiêm trọng:

  • CRIME/BREACH attacks — kẻ tấn công có thể suy đoán nội dung encrypted response bằng cách quan sát kích thước nén thay đổi
  • Vi phạm Same-Origin Policy — dictionary có thể được chia sẻ cross-origin, tạo kênh rò rỉ dữ liệu

RFC 9842 (chuẩn hóa tháng 9/2025) khắc phục triệt để:

Vấn đề SDCHGiải pháp RFC 9842
Cross-origin dictionary sharingDictionary chỉ dùng được cho same-origin responses
Side-channel compression attacksDictionary hash là opaque — không tiết lộ nội dung
Privacy tracking qua dictionaryDictionary tuân thủ cache partition (mỗi top-level site riêng biệt)
Không có chuẩn transportHTTP header rõ ràng: Use-As-Dictionary, Available-Dictionary

Chiến lược áp dụng thực tế cho dự án 2026

graph TD
    START[Đánh giá dự án] --> Q1{Deploy > 1 lần/tuần?}
    Q1 -->|Có| Q2{Bundle JS/CSS > 100KB?}
    Q1 -->|Không| SKIP[Brotli/Zstd đủ tốt]
    Q2 -->|Có| Q3{Target Chrome 130+?}
    Q2 -->|Không| SKIP
    Q3 -->|Có| IMPL[Triển khai Shared Dictionaries]
    Q3 -->|Không| WAIT[Đợi Safari/Firefox hỗ trợ]
    IMPL --> S1[Bước 1: Thêm Use-As-Dictionary header]
    S1 --> S2[Bước 2: Xử lý Available-Dictionary request]
    S2 --> S3[Bước 3: Serve dcb/dcz response]
    S3 --> S4[Bước 4: Monitor compression ratio]

    style START fill:#f8f9fa,stroke:#e94560,color:#2c3e50
    style IMPL fill:#e94560,stroke:#fff,color:#fff
    style SKIP fill:#f8f9fa,stroke:#e0e0e0,color:#888
    style WAIT fill:#f8f9fa,stroke:#ff9800,color:#2c3e50
    style S1 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
    style S2 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
    style S3 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
    style S4 fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50

Decision tree áp dụng Shared Dictionaries cho dự án web

Checklist triển khai

  1. Đo baseline — Kiểm tra kích thước bundle hiện tại với Gzip/Brotli. Nếu delta giữa 2 version liên tiếp nhỏ (dưới 5% thay đổi), Shared Dictionaries sẽ cực kỳ hiệu quả.
  2. Chọn thuật toándcz (Zstandard) được khuyến nghị: nhanh hơn Brotli đáng kể khi dùng dictionary, hỗ trợ dictionary lớn hơn.
  3. Cấu hình server — Thêm header Use-As-Dictionary cho các static assets (JS, CSS, WASM). Xử lý header Available-Dictionary từ client.
  4. CDN compatibility — Đảm bảo CDN (Cloudflare, CloudFront, Fastly) không strip các header mới. Mở rộng Vary header.
  5. Fallback — Luôn serve Brotli/Gzip cho client không hỗ trợ. Progressive enhancement, không phải breaking change.

Lưu ý quan trọng

Shared Dictionaries là progressive enhancement. Client không hỗ trợ sẽ không gửi header Available-Dictionary và server fallback về Brotli/Gzip bình thường. Không có risk cho user cũ — chỉ có lợi cho user trên trình duyệt mới.

Tương lai: Từ Web đến Agentic Internet

Cloudflare nhấn mạnh một xu hướng quan trọng: trong kỷ nguyên Agentic Web, không chỉ người dùng mà cả AI agents cũng liên tục fetch trang web — và chúng request thường xuyên hơn con người rất nhiều. Shared Dictionaries giảm tải không chỉ cho end-user mà cho cả infrastructure khi lượng traffic từ agents tăng theo cấp số nhân.

Với RFC 9842 được chuẩn hóa, Chrome đã hỗ trợ, Firefox và Safari đang triển khai, cùng Cloudflare chuẩn bị beta — 2026 chính là năm Shared Dictionaries chuyển từ thí nghiệm sang production. Đây không phải tối ưu nhỏ lẻ — đây là thay đổi kiến trúc giúp web nhanh hơn một bậc cho mọi lần deploy tiếp theo.

Tham khảo