Giới thiệu

Trong các phần trước của mình cũng đề cập loạt kỹ về tối ưu cơ bản bây giờ mình sẽ nói tiếp các kỹ thuật nâng cao nhé. Enjoy time!!!

Các kỹ thuật tối ưu nâng cao không chỉ giúp cải thiện hiệu suất mà còn mở ra những khả năng mới cho ứng dụng web. Chúng cho phép xử lý khối lượng dữ liệu lớn, thực hiện các tính toán phức tạp, và tạo ra trải nghiệm người dùng mượt mà hơn ngay cả trên các thiết bị có cấu hình thấp hoặc kết nối mạng không ổn định.

Trong phần này, chúng ta sẽ khám phá bốn kỹ thuật tối ưu nâng cao:

  1. Web Workers: Tận dụng đa luồng để giải phóng main thread
  2. WebAssembly: Mang hiệu suất gần như native vào trình duyệt
  3. Micro-frontends: Kiến trúc phân tách để tối ưu hóa quy trình phát triển và hiệu suất
  4. Edge Computing: Đưa xử lý gần người dùng hơn để giảm độ trễ

Mỗi kỹ thuật này đều có những ưu điểm riêng và phù hợp với các tình huống cụ thể. Hãy cùng tìm hiểu chi tiết về từng kỹ thuật và cách áp dụng chúng vào dự án của bạn.

1. Web Workers: Tận dụng đa luồng

JavaScript là một ngôn ngữ đơn luồng (single-threaded), điều này có nghĩa là tất cả các tác vụ đều chạy trên một luồng duy nhất - main thread. Khi main thread bị chiếm dụng bởi các tác vụ nặng, giao diện người dùng sẽ bị đóng băng, dẫn đến trải nghiệm không mượt mà.

Web Workers giải quyết vấn đề này bằng cách cho phép chạy JavaScript trong các luồng nền, tách biệt với main thread. Điều này giúp giải phóng main thread để xử lý UI và tương tác người dùng, trong khi các tác vụ nặng được xử lý trong worker threads.

1.1 Các loại Web Workers

Dedicated Workers

Đây là loại worker phổ biến nhất, được tạo bởi một script duy nhất và chỉ có thể giao tiếp với script đã tạo ra nó.

// Tạo một dedicated worker
const worker = new Worker("worker.js");

// Gửi dữ liệu đến worker
worker.postMessage({ data: complexData });

// Nhận dữ liệu từ worker
worker.onmessage = function (event) {
  console.log("Kết quả từ worker:", event.data);
};

// Xử lý lỗi
worker.onerror = function (error) {
  console.error("Worker error:", error);
};

Trong file worker.js:

// Nhận dữ liệu từ main thread
self.onmessage = function (event) {
  const result = performComplexCalculation(event.data);

  // Gửi kết quả về main thread
  self.postMessage(result);
};

function performComplexCalculation(data) {
  // Thực hiện các tính toán phức tạp
  // ...
  return processedData;
}

Shared Workers

Shared Workers cho phép nhiều scripts từ các windows, iframes, hoặc workers khác nhau chia sẻ cùng một worker instance.

// Tạo một shared worker
const sharedWorker = new SharedWorker("shared-worker.js");

// Giao tiếp thông qua port
sharedWorker.port.postMessage({ action: "process", data: someData });

sharedWorker.port.onmessage = function (event) {
  console.log("Kết quả từ shared worker:", event.data);
};

Trong file shared-worker.js:

// Danh sách các kết nối
const connections = [];

// Xử lý khi có kết nối mới
self.onconnect = function (event) {
  const port = event.ports[0];
  connections.push(port);

  port.onmessage = function (event) {
    // Xử lý message
    const result = processData(event.data);
    port.postMessage(result);

    // Có thể broadcast cho tất cả các kết nối
    // connections.forEach(p => p.postMessage(someUpdate));
  };

  port.start();
};

Service Workers

Service Workers hoạt động như một proxy mạng giữa ứng dụng và server, cho phép offline caching, background sync, và push notifications.

// Đăng ký service worker
if ("serviceWorker" in navigator) {
  navigator.serviceWorker
    .register("/service-worker.js")
    .then((registration) => {
      console.log("Service Worker đã đăng ký thành công:", registration.scope);
    })
    .catch((error) => {
      console.error("Đăng ký Service Worker thất bại:", error);
    });
}

Trong file service-worker.js:

// Cài đặt service worker và cache assets
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("v1").then((cache) => {
      return cache.addAll([
        "/",
        "/index.html",
        "/styles.css",
        "/app.js",
        "/offline.html",
      ]);
    })
  );
});

// Xử lý fetch requests
self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // Trả về response từ cache nếu có
      return (
        response ||
        fetch(event.request).catch(() => {
          // Trả về trang offline nếu không có kết nối
          return caches.match("/offline.html");
        })
      );
    })
  );
});

1.2 Các use cases phù hợp cho Web Workers

1. Xử lý dữ liệu lớn

Khi cần xử lý và phân tích các tập dữ liệu lớn, Web Workers có thể thực hiện các tác vụ này mà không làm đóng băng UI.

// Main thread
const worker = new Worker("data-processor.js");

worker.postMessage({
  action: "processCSV",
  data: largeCSVData,
});

worker.onmessage = function (event) {
  updateUIWithProcessedData(event.data);
};

2. Mã hóa và giải mã

Các thuật toán mã hóa thường rất nặng về CPU và có thể gây lag UI nếu chạy trên main thread.

// Main thread
const encryptionWorker = new Worker("encryption-worker.js");

document.getElementById("encrypt-button").addEventListener("click", () => {
  const data = document.getElementById("data-input").value;
  const key = document.getElementById("key-input").value;

  encryptionWorker.postMessage({
    action: "encrypt",
    data: data,
    key: key,
  });
});

encryptionWorker.onmessage = function (event) {
  document.getElementById("result").textContent = event.data.result;
};

3. Xử lý ảnh và video

Các tác vụ xử lý ảnh như lọc, biến đổi, và nén có thể được thực hiện trong workers.

// Main thread
const imageWorker = new Worker("image-processor.js");

document.getElementById("upload").addEventListener("change", (event) => {
  const file = event.target.files[0];
  const reader = new FileReader();

  reader.onload = function (e) {
    const imageData = e.target.result;

    imageWorker.postMessage({
      action: "applyFilter",
      filter: "grayscale",
      imageData: imageData,
    });
  };

  reader.readAsDataURL(file);
});

imageWorker.onmessage = function (event) {
  document.getElementById("preview").src = event.data.processedImage;
};

1.3 Tích hợp Web Workers với React và Vue

React với Web Workers

Trong React, bạn có thể sử dụng các thư viện như worker-loader hoặc comlink để đơn giản hóa việc sử dụng Web Workers.

// Sử dụng worker-loader trong webpack
import Worker from 'worker-loader!./worker';

function ComplexCalculationComponent() {
  const [result, setResult] = useState(null);
  const [isCalculating, setIsCalculating] = useState(false);
  const workerRef = useRef(null);

  useEffect(() => {
    // Khởi tạo worker
    workerRef.current = new Worker();

    // Thiết lập event listener
    workerRef.current.onmessage = (event) => {
      setResult(event.data);
      setIsCalculating(false);
    };

    // Cleanup khi component unmount
    return () => {
      workerRef.current.terminate();
    };
  }, []);

  const handleCalculate = () => {
    setIsCalculating(true);
    workerRef.current.postMessage({
      action: 'calculate',
      data: /* dữ liệu phức tạp */
    });
  };

  return (
    <div>
      <button onClick={handleCalculate} disabled={isCalculating}>
        {isCalculating ? 'Đang tính...' : 'Bắt đầu tính toán'}
      </button>
      {result && <div>Kết quả: {result}</div>}
    </div>
  );
}

Vue với Web Workers

Trong Vue, bạn có thể sử dụng các thư viện như worker-plugin hoặc tạo một composable function để sử dụng Web Workers.

// worker.js
self.onmessage = function (event) {
  const result = performComplexCalculation(event.data);
  self.postMessage(result);
};

// useWorker.js (Vue 3 Composable)
import { ref, onMounted, onUnmounted } from "vue";

export function useWorker(workerPath) {
  const result = ref(null);
  const isProcessing = ref(false);
  let worker = null;

  onMounted(() => {
    worker = new Worker(workerPath);

    worker.onmessage = (event) => {
      result.value = event.data;
      isProcessing.value = false;
    };
  });

  const sendMessage = (data) => {
    if (worker) {
      isProcessing.value = true;
      worker.postMessage(data);
    }
  };

  onUnmounted(() => {
    if (worker) {
      worker.terminate();
    }
  });

  return {
    result,
    isProcessing,
    sendMessage,
  };
}

Sử dụng trong component Vue:

<template>
  <div>
    <button @click="calculate" :disabled="isProcessing">
      {{ isProcessing ? "Đang xử lý..." : "Bắt đầu tính toán" }}
    </button>
    <div v-if="result">Kết quả: {{ result }}</div>
  </div>
</template>

<script setup>
import { useWorker } from './useWorker';

const { result, isProcessing, sendMessage } = useWorker('./worker.js');

function calculate() {
  sendMessage({
    action: 'calculate',
    data: /* dữ liệu phức tạp */
  });
}
</script>

1.4 Những hạn chế và lưu ý khi sử dụng Web Workers

  1. Không truy cập DOM: Web Workers không thể trực tiếp truy cập DOM, window object, hoặc parent object. Chúng chỉ có thể giao tiếp thông qua message passing.

  2. Chi phí truyền dữ liệu: Dữ liệu được truyền giữa main thread và worker threads thông qua cơ chế structured cloning, có thể gây overhead khi truyền dữ liệu lớn. Sử dụng Transferable Objects hoặc SharedArrayBuffer để tối ưu hóa.

    // Sử dụng Transferable Objects
    const arrayBuffer = new ArrayBuffer(32 * 1024 * 1024); // 32MB buffer
    worker.postMessage({ data: arrayBuffer }, [arrayBuffer]); // Chuyển quyền sở hữu
    
  3. Khởi tạo chậm: Việc tạo một worker có thể tốn thời gian, vì vậy nên tạo workers sớm và tái sử dụng thay vì tạo mới liên tục.

  4. Hỗ trợ trình duyệt: Mặc dù hầu hết các trình duyệt hiện đại đều hỗ trợ Web Workers, nhưng vẫn nên kiểm tra khả năng tương thích và cung cấp fallback.

    if (window.Worker) {
      // Sử dụng Web Workers
    } else {
      // Fallback: thực hiện tác vụ trên main thread
    }
    

1.5 Ví dụ thực tế: Tối ưu hóa ứng dụng bằng Web Workers

Hãy xem xét một ứng dụng tìm kiếm và lọc danh sách sản phẩm lớn. Khi không sử dụng Web Workers, việc tìm kiếm và lọc có thể gây lag UI nếu danh sách lớn:

// Không sử dụng Web Workers - có thể gây lag UI
function searchProducts(query, products) {
  // Tìm kiếm có thể tốn thời gian nếu products là một mảng lớn
  return products.filter((product) => {
    return (
      product.name.toLowerCase().includes(query.toLowerCase()) ||
      product.description.toLowerCase().includes(query.toLowerCase())
    );
  });
}

document.getElementById("search").addEventListener("input", (e) => {
  const query = e.target.value;
  const filteredProducts = searchProducts(query, allProducts); // Có thể block UI
  renderProducts(filteredProducts);
});

Cải thiện bằng Web Workers:

// Main thread
const searchWorker = new Worker("search-worker.js");

// Gửi toàn bộ danh sách sản phẩm cho worker khi khởi tạo
searchWorker.postMessage({
  action: "initialize",
  products: allProducts,
});

document.getElementById("search").addEventListener("input", (e) => {
  const query = e.target.value;

  // Gửi yêu cầu tìm kiếm đến worker
  searchWorker.postMessage({
    action: "search",
    query: query,
  });
});

// Nhận kết quả từ worker và cập nhật UI
searchWorker.onmessage = function (event) {
  if (event.data.action === "searchResults") {
    renderProducts(event.data.results);
  }
};

Trong file search-worker.js:

let products = [];

self.onmessage = function (event) {
  const data = event.data;

  switch (data.action) {
    case "initialize":
      products = data.products;
      break;

    case "search":
      const results = searchProducts(data.query, products);
      self.postMessage({
        action: "searchResults",
        results: results,
      });
      break;
  }
};

function searchProducts(query, products) {
  if (!query) return products;

  return products.filter((product) => {
    return (
      product.name.toLowerCase().includes(query.toLowerCase()) ||
      product.description.toLowerCase().includes(query.toLowerCase())
    );
  });
}

Kết quả: UI vẫn mượt mà ngay cả khi tìm kiếm trong danh sách hàng nghìn sản phẩm, vì tác vụ tìm kiếm được thực hiện trong một luồng riêng biệt.

2. WebAssembly: Hiệu suất gần như native

WebAssembly (hay Wasm) là một định dạng mã nhị phân cấp thấp được thiết kế để chạy với hiệu suất gần như native trên các trình duyệt web. Nó không phải để thay thế JavaScript mà là để bổ sung cho nó, cho phép các ứng dụng web thực hiện các tác vụ đòi hỏi hiệu suất cao.

2.1 WebAssembly hoạt động như thế nào?

WebAssembly được thiết kế như một định dạng mã nhị phân nhỏ gọn, tải nhanh và thực thi hiệu quả. Quy trình cơ bản như sau:

  1. Biên dịch: Mã nguồn từ các ngôn ngữ như C, C++, Rust được biên dịch thành WebAssembly.
  2. Tải: File WebAssembly (.wasm) được tải vào trình duyệt.
  3. Biên dịch JIT: Trình duyệt biên dịch mã WebAssembly thành mã máy native.
  4. Thực thi: Mã được thực thi với hiệu suất gần như native.
  5. Tương tác với JavaScript: WebAssembly có thể gọi và được gọi từ JavaScript.
// Tải và khởi tạo module WebAssembly từ JavaScript
fetch("module.wasm")
  .then((response) => response.arrayBuffer())
  .then((bytes) => WebAssembly.instantiate(bytes, importObject))
  .then((result) => {
    // Sử dụng các hàm được export từ WebAssembly
    const instance = result.instance;
    const result = instance.exports.calculateSomething(10, 20);
    console.log("Kết quả từ WebAssembly:", result);
  });

2.2 Lợi ích của WebAssembly

1. Hiệu suất cao

WebAssembly thực thi nhanh hơn JavaScript trong nhiều tình huống, đặc biệt là các tác vụ tính toán nặng:

  • Thời gian phân tích cú pháp và biên dịch nhanh hơn
  • Tối ưu hóa tốt hơn cho mã máy
  • Khởi động nhanh hơn
  • Dự đoán kiểu dữ liệu tốt hơn

2. Tính di động

Mã WebAssembly có thể được biên dịch từ nhiều ngôn ngữ khác nhau và chạy trên bất kỳ trình duyệt hiện đại nào:

  • Hỗ trợ C/C++, Rust, Go, C#, và nhiều ngôn ngữ khác
  • Hoạt động trên tất cả các trình duyệt chính (Chrome, Firefox, Safari, Edge)
  • Cho phép tái sử dụng mã từ các ứng dụng desktop hoặc mobile

3. An toàn

WebAssembly được thiết kế với mô hình bảo mật nghiêm ngặt:

  • Chạy trong sandbox của trình duyệt
  • Không có quyền truy cập trực tiếp vào hệ thống tệp hoặc mạng
  • Quản lý bộ nhớ an toàn
  • Kiểm tra kiểu dữ liệu tại thời điểm biên dịch

2.3 Các use cases phù hợp cho WebAssembly

1. Xử lý đồ họa và trò chơi

WebAssembly đặc biệt phù hợp cho các ứng dụng đồ họa nặng như trò chơi web:

// Ví dụ: Sử dụng WebAssembly để tính toán vật lý trong game
import { initPhysics, updatePhysics } from "./physics.js"; // Module JavaScript wrapper cho WebAssembly

// Khởi tạo engine vật lý
initPhysics().then(() => {
  // Game loop
  function gameLoop(timestamp) {
    // Cập nhật vật lý với WebAssembly
    const bodies = updatePhysics(deltaTime);

    // Render các đối tượng với vị trí mới
    renderBodies(bodies);

    requestAnimationFrame(gameLoop);
  }

  requestAnimationFrame(gameLoop);
});

2. Xử lý ảnh và video

Các thuật toán xử lý ảnh phức tạp có thể được tăng tốc đáng kể:

// Ví dụ: Xử lý ảnh với WebAssembly
const imageProcessing = await WebAssembly.instantiateStreaming(
  fetch("image-processing.wasm"),
  {
    env: { memory: new WebAssembly.Memory({ initial: 10 }) },
  }
);

function applyFilter(imageData) {
  // Sao chép dữ liệu ảnh vào bộ nhớ WebAssembly
  const wasmMemory = new Uint8Array(
    imageProcessing.instance.exports.memory.buffer
  );
  wasmMemory.set(new Uint8Array(imageData.data.buffer));

  // Gọi hàm xử lý ảnh từ WebAssembly
  imageProcessing.instance.exports.applyFilter(
    0, // Offset trong bộ nhớ
    imageData.width,
    imageData.height
  );

  // Sao chép kết quả trở lại imageData
  imageData.data.set(
    new Uint8Array(wasmMemory.buffer, 0, imageData.data.length)
  );
  return imageData;
}

3. Mã hóa và giải mã

Các thuật toán mã hóa phức tạp có thể được thực hiện hiệu quả hơn:

// Ví dụ: Mã hóa dữ liệu với WebAssembly
import { initCrypto, encrypt, decrypt } from "./crypto-wasm.js";

async function encryptData(data, key) {
  await initCrypto();
  return encrypt(data, key);
}

async function decryptData(encryptedData, key) {
  await initCrypto();
  return decrypt(encryptedData, key);
}

4. Các ứng dụng tính toán khoa học và kỹ thuật

WebAssembly rất phù hợp cho các ứng dụng đòi hỏi tính toán phức tạp:

// Ví dụ: Mô phỏng khoa học với WebAssembly
import { initSimulation, runSimulation } from "./simulation-wasm.js";

async function runScientificSimulation(parameters) {
  await initSimulation();
  const results = [];

  for (let i = 0; i < 1000; i++) {
    // Chạy mô phỏng với WebAssembly - nhanh hơn nhiều so với JavaScript
    const result = runSimulation(parameters, i);
    results.push(result);
  }

  return results;
}

2.4 Tích hợp WebAssembly với React và Vue

React với WebAssembly

import React, { useState, useEffect } from "react";
import { initWasm, processData } from "../wasm/wasm-module.js";

function WasmComponent() {
  const [result, setResult] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [inputData, setInputData] = useState("");

  useEffect(() => {
    // Khởi tạo WebAssembly khi component mount
    initWasm()
      .then(() => {
        setIsLoading(false);
      })
      .catch((err) => {
        console.error("Lỗi khi khởi tạo WebAssembly:", err);
        setIsLoading(false);
      });
  }, []);

  const handleProcess = () => {
    if (!isLoading) {
      try {
        // Gọi hàm WebAssembly thông qua wrapper
        const processedResult = processData(inputData);
        setResult(processedResult);
      } catch (err) {
        console.error("Lỗi khi xử lý dữ liệu:", err);
      }
    }
  };

  return (
    <div>
      {isLoading ? (
        <p>Đang tải WebAssembly...</p>
      ) : (
        <>
          <input
            type="text"
            value={inputData}
            onChange={(e) => setInputData(e.target.value)}
            placeholder="Nhập dữ liệu"
          />
          <button onClick={handleProcess}>Xử </button>
          {result && <div>Kết quả: {result}</div>}
        </>
      )}
    </div>
  );
}

Vue với WebAssembly

<template>
  <div>
    <p v-if="isLoading">Đang tải WebAssembly...</p>
    <div v-else>
      <input v-model="inputData" placeholder="Nhập dữ liệu" />
      <button @click="processData">Xử lý</button>
      <div v-if="result">Kết quả: {{ result }}</div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import { initWasm, processWithWasm } from "../wasm/wasm-module.js";

const isLoading = ref(true);
const inputData = ref("");
const result = ref(null);

onMounted(async () => {
  try {
    await initWasm();
    isLoading.value = false;
  } catch (err) {
    console.error("Lỗi khi khởi tạo WebAssembly:", err);
    isLoading.value = false;
  }
});

function processData() {
  if (!isLoading.value) {
    try {
      result.value = processWithWasm(inputData.value);
    } catch (err) {
      console.error("Lỗi khi xử lý dữ liệu:", err);
    }
  }
}
</script>

2.5 Cách biên dịch và sử dụng WebAssembly

Biên dịch C/C++ sang WebAssembly với Emscripten

# Cài đặt Emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh

# Biên dịch file C/C++ sang WebAssembly
emcc -O3 -s WASM=1 -s EXPORTED_FUNCTIONS='["_calculateFunction", "_malloc", "_free"]' -s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' my_code.c -o my_code.js

File C đơn giản (my_code.c):

#include <emscripten.h>

EMSCRIPTEN_KEEPALIVE
int calculateFunction(int a, int b) {
  return a * a + b * b;
}

Biên dịch Rust sang WebAssembly

# Cài đặt wasm-pack
cargo install wasm-pack

# Tạo project Rust mới
cargo new --lib my-wasm-project
cd my-wasm-project

# Biên dịch sang WebAssembly
wasm-pack build --target web

File Rust đơn giản (src/lib.rs):

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    if n <= 1 {
        return n;
    }
    fibonacci(n - 1) + fibonacci(n - 2)
}

2.6 Những hạn chế và lưu ý khi sử dụng WebAssembly

  1. Không truy cập DOM trực tiếp: WebAssembly không thể trực tiếp tương tác với DOM mà phải thông qua JavaScript.

  2. Overhead giao tiếp: Việc chuyển dữ liệu giữa JavaScript và WebAssembly có thể tạo ra overhead, đặc biệt với dữ liệu phức tạp.

  3. Kích thước tải: Mặc dù nhỏ gọn, nhưng các module WebAssembly vẫn có thể khá lớn đối với web.

  4. Đường cong học tập: Phát triển với WebAssembly thường đòi hỏi kiến thức về các ngôn ngữ cấp thấp và công cụ biên dịch.

  5. Hỗ trợ trình duyệt: Mặc dù hầu hết các trình duyệt hiện đại đều hỗ trợ WebAssembly, nhưng vẫn cần kiểm tra khả năng tương thích.

    if (typeof WebAssembly === "object") {
      // WebAssembly được hỗ trợ
    } else {
      // Fallback: sử dụng JavaScript thuần
    }
    

2.7 Ví dụ thực tế: Tối ưu hóa ứng dụng với WebAssembly

Hãy xem xét một ứng dụng xử lý ảnh trực tiếp trên trình duyệt. Khi sử dụng JavaScript thuần, việc áp dụng các bộ lọc phức tạp có thể rất chậm:

// JavaScript thuần - có thể chậm với ảnh lớn
function applyBlurFilter(imageData, radius) {
  const { data, width, height } = imageData;
  const result = new Uint8ClampedArray(data.length);

  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      let r = 0,
        g = 0,
        b = 0,
        a = 0,
        count = 0;

      // Tính trung bình các pixel xung quanh
      for (let ky = -radius; ky <= radius; ky++) {
        for (let kx = -radius; kx <= radius; kx++) {
          const px = x + kx;
          const py = y + ky;

          if (px >= 0 && px < width && py >= 0 && py < height) {
            const i = (py * width + px) * 4;
            r += data[i];
            g += data[i + 1];
            b += data[i + 2];
            a += data[i + 3];
            count++;
          }
        }
      }

      // Gán giá trị trung bình cho pixel hiện tại
      const i = (y * width + x) * 4;
      result[i] = r / count;
      result[i + 1] = g / count;
      result[i + 2] = b / count;
      result[i + 3] = a / count;
    }
  }

  return new ImageData(result, width, height);
}

Cải thiện bằng WebAssembly (C code):

#include <emscripten.h>
#include <stdlib.h>

EMSCRIPTEN_KEEPALIVE
void applyBlurFilter(unsigned char* data, int width, int height, int radius) {
  unsigned char* result = (unsigned char*)malloc(width * height * 4);

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      int r = 0, g = 0, b = 0, a = 0, count = 0;

      // Tính trung bình các pixel xung quanh
      for (int ky = -radius; ky <= radius; ky++) {
        for (int kx = -radius; kx <= radius; kx++) {
          int px = x + kx;
          int py = y + ky;

          if (px >= 0 && px < width && py >= 0 && py < height) {
            int i = (py * width + px) * 4;
            r += data[i];
            g += data[i + 1];
            b += data[i + 2];
            a += data[i + 3];
            count++;
          }
        }
      }

      // Gán giá trị trung bình cho pixel hiện tại
      int i = (y * width + x) * 4;
      result[i] = r / count;
      result[i + 1] = g / count;
      result[i + 2] = b / count;
      result[i + 3] = a / count;
    }
  }

  // Sao chép kết quả trở lại mảng gốc
  for (int i = 0; i < width * height * 4; i++) {
    data[i] = result[i];
  }

  free(result);
}

Sử dụng trong JavaScript:

// Khởi tạo WebAssembly
let wasmModule;

async function initImageProcessing() {
  const response = await fetch("image-processing.wasm");
  const wasmBytes = await response.arrayBuffer();
  const { instance } = await WebAssembly.instantiate(wasmBytes, {
    env: {
      memory: new WebAssembly.Memory({ initial: 256 }), // 16MB
    },
  });

  wasmModule = instance.exports;
}

async function applyBlurFilterWithWasm(imageData, radius) {
  if (!wasmModule) await initImageProcessing();

  const { data, width, height } = imageData;
  const dataSize = data.length;

  // Cấp phát bộ nhớ trong WebAssembly
  const dataPtr = wasmModule._malloc(dataSize);

  // Sao chép dữ liệu ảnh vào bộ nhớ WebAssembly
  const wasmMemory = new Uint8Array(wasmModule.memory.buffer);
  wasmMemory.set(new Uint8Array(data), dataPtr);

  // Gọi hàm xử lý ảnh từ WebAssembly
  wasmModule.applyBlurFilter(dataPtr, width, height, radius);

  // Sao chép kết quả trở lại
  const resultData = new Uint8ClampedArray(
    wasmMemory.buffer.slice(dataPtr, dataPtr + dataSize)
  );

  // Giải phóng bộ nhớ
  wasmModule._free(dataPtr);

  return new ImageData(resultData, width, height);
}

Kết quả: Phiên bản WebAssembly có thể nhanh hơn JavaScript thuần từ 2-10 lần tùy thuộc vào kích thước ảnh và độ phức tạp của bộ lọc.

3. Micro-frontends: Kiến trúc phân tách

Micro-frontends là một cách tiếp cận kiến trúc để phát triển các ứng dụng frontend bằng cách chia nhỏ chúng thành các phần nhỏ hơn, độc lập về mặt kỹ thuật. Tương tự như microservices ở phía backend, micro-frontends cho phép các team phát triển, triển khai và mở rộng các phần khác nhau của giao diện người dùng một cách độc lập.

3.1 Nguyên tắc của Micro-frontends

1. Độc lập và tự chủ

Mỗi micro-frontend nên được phát triển, kiểm thử và triển khai độc lập với các micro-frontend khác.

2. Tập trung vào business domain

Mỗi micro-frontend nên đại diện cho một business domain hoặc một tính năng cụ thể, không phải một lớp kỹ thuật.

3. Trách nhiệm rõ ràng

Mỗi micro-frontend nên có một API rõ ràng và trách nhiệm được xác định rõ.

4. Khả năng tương tác

Các micro-frontend cần có khả năng giao tiếp với nhau thông qua các giao thức được xác định rõ ràng.

3.2 Các cách tiếp cận Micro-frontends

1. Client-side integration

Trong cách tiếp cận này, các micro-frontend được tải và kết hợp trên trình duyệt của người dùng.

// Shell application
import { mountApp as mountTeamA } from "team-a-app";
import { mountApp as mountTeamB } from "team-b-app";

// Mount các micro-frontend vào các container khác nhau
mountTeamA(document.getElementById("team-a-container"));
mountTeamB(document.getElementById("team-b-container"));

2. Server-side integration

Trong cách tiếp cận này, các micro-frontend được kết hợp trên server trước khi gửi đến trình duyệt.

// Server-side code (Node.js với Express)
app.get("/", async (req, res) => {
  // Lấy HTML từ các micro-frontend khác nhau
  const headerHtml = await fetchFromTeamA("/header");
  const productHtml = await fetchFromTeamB("/product");
  const cartHtml = await fetchFromTeamC("/cart");

  // Kết hợp HTML
  const html = `
    <!DOCTYPE html>
    <html>
      <head>...</head>
      <body>
        <div id="header">${headerHtml}</div>
        <div id="product">${productHtml}</div>
        <div id="cart">${cartHtml}</div>
      </body>
    </html>
  `;

  res.send(html);
});

3. Build-time integration

Các micro-frontend được kết hợp trong quá trình build.

// package.json
{
  "dependencies": {
    "team-a-app": "1.0.0",
    "team-b-app": "1.0.0",
    "team-c-app": "1.0.0"
  }
}
// Main application
import TeamAApp from "team-a-app";
import TeamBApp from "team-b-app";
import TeamCApp from "team-c-app";

function App() {
  return (
    <div>
      <TeamAApp />
      <TeamBApp />
      <TeamCApp />
    </div>
  );
}

4. Module Federation (Webpack 5)

Module Federation là một tính năng của Webpack 5 cho phép các ứng dụng JavaScript chia sẻ mã với nhau trong thời gian chạy.

// webpack.config.js (host application)
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        teamA: "teamA@http://localhost:3001/remoteEntry.js",
        teamB: "teamB@http://localhost:3002/remoteEntry.js",
      },
      shared: ["react", "react-dom"],
    }),
  ],
};
// App.js (host application)
import React, { lazy, Suspense } from "react";

// Tải các micro-frontend một cách lazy
const TeamAApp = lazy(() => import("teamA/App"));
const TeamBApp = lazy(() => import("teamB/App"));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading Team A...</div>}>
        <TeamAApp />
      </Suspense>
      <Suspense fallback={<div>Loading Team B...</div>}>
        <TeamBApp />
      </Suspense>
    </div>
  );
}

3.3 Lợi ích về hiệu suất của Micro-frontends

1. Tải và render độc lập

Mỗi micro-frontend có thể được tải và render độc lập, cho phép hiển thị nội dung quan trọng trước.

// Shell application
import { mountApp as mountHeader } from "header-app";
import { mountApp as mountContent } from "content-app";
import { mountApp as mountFooter } from "footer-app";

// Mount header ngay lập tức
mountHeader(document.getElementById("header-container"));

// Tải content và footer sau khi trang đã hiển thị
window.addEventListener("DOMContentLoaded", () => {
  mountContent(document.getElementById("content-container"));
});

window.addEventListener("load", () => {
  mountFooter(document.getElementById("footer-container"));
});

2. Các chiến lược cache riêng biệt

Mỗi micro-frontend có thể có chiến lược cache riêng, cho phép tối ưu hóa từng phần của ứng dụng.

// Service worker cho micro-frontend A
self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("micro-frontend-a-v1").then((cache) => {
      return cache.addAll([
        "/micro-frontend-a/main.js",
        "/micro-frontend-a/styles.css",
        "/micro-frontend-a/assets/logo.png",
      ]);
    })
  );
});

3. Tối ưu hóa kích thước bundle

Mỗi micro-frontend có thể được tối ưu hóa riêng, giúp giảm kích thước bundle tổng thể.

// webpack.config.js cho micro-frontend
module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: "all",
      maxInitialRequests: 5,
      cacheGroups: {
        vendor: {
          test: /[\\]node_modules[\\]/,
          name: "vendors",
          chunks: "all",
        },
      },
    },
  },
};

4. Lazy loading và code splitting

Các micro-frontend có thể được tải theo nhu cầu, giúp giảm thời gian tải ban đầu.

// Shell application
const routes = [
  {
    path: "/",
    component: () => import("./home/HomeApp"),
  },
  {
    path: "/products",
    component: () => import("./products/ProductsApp"),
  },
  {
    path: "/checkout",
    component: () => import("./checkout/CheckoutApp"),
  },
];

3.4 Thách thức và giải pháp

1. Trùng lặp dependencies

Mỗi micro-frontend có thể có các dependencies riêng, dẫn đến trùng lặp và tăng kích thước tải.

Giải pháp: Sử dụng Module Federation hoặc các kỹ thuật chia sẻ dependencies.

// webpack.config.js với Module Federation
module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      // ...
      shared: {
        react: { singleton: true, eager: true },
        "react-dom": { singleton: true, eager: true },
        "styled-components": { singleton: true },
      },
    }),
  ],
};

2. Style consistency

Duy trì giao diện nhất quán giữa các micro-frontend là một thách thức.

Giải pháp: Sử dụng Design System chung.

// Shared design system package
// @company/design-system

export const Button = styled.button`
  background-color: var(--primary-color);
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
  // ...
`;

export const Card = styled.div`
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 16px;
  // ...
`;

Sử dụng trong các micro-frontend:

// Team A's micro-frontend
import { Button, Card } from "@company/design-system";

function ProductCard({ product }) {
  return (
    <Card>
      <h3>{product.name}</h3>
      <p>{product.description}</p>
      <Button>Add to Cart</Button>
    </Card>
  );
}

3. Communication between micro-frontends

Các micro-frontend cần giao tiếp với nhau mà không tạo ra sự ràng buộc chặt chẽ.

Giải pháp: Sử dụng Event Bus hoặc Custom Events.

// Event bus implementation
class EventBus {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);

    return () => {
      this.events[event] = this.events[event].filter((cb) => cb !== callback);
    };
  }

  publish(event, data) {
    if (!this.events[event]) return;
    this.events[event].forEach((callback) => callback(data));
  }
}

// Create a singleton instance
export const eventBus = new EventBus();

Sử dụng trong các micro-frontend:

// Team A's micro-frontend (Publishing)
import { eventBus } from '@company/event-bus';

function ProductList() {
  const addToCart = (product) => {
    eventBus.publish('ADD_TO_CART', product);
  };

  return (
    // ...
  );
}
// Team B's micro-frontend (Subscribing)
import { eventBus } from '@company/event-bus';
import { useEffect, useState } from 'react';

function ShoppingCart() {
  const [items, setItems] = useState([]);

  useEffect(() => {
    const unsubscribe = eventBus.subscribe('ADD_TO_CART', (product) => {
      setItems(prevItems => [...prevItems, product]);
    });

    return unsubscribe;
  }, []);

  return (
    // ...
  );
}

4. Routing

Quản lý routing giữa các micro-frontend có thể phức tạp.

Giải pháp: Sử dụng một router trung tâm hoặc router lồng nhau.

// Shell application with central routing
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { lazy, Suspense } from "react";

const HomeApp = lazy(() => import("home/App"));
const ProductsApp = lazy(() => import("products/App"));
const CheckoutApp = lazy(() => import("checkout/App"));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<HomeApp />} />
          <Route path="/products/*" element={<ProductsApp />} />
          <Route path="/checkout/*" element={<CheckoutApp />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

3.5 Ví dụ thực tế: Tối ưu hóa hiệu suất với Micro-frontends

Hãy xem xét một trang thương mại điện tử với các phần khác nhau:

  1. Header và Navigation
  2. Product Listing
  3. Shopping Cart
  4. User Profile
  5. Checkout

Mỗi phần có thể được phát triển và triển khai như một micro-frontend riêng biệt.

Cấu trúc dự án

/
|-- container/                # Shell application
|-- header-nav/               # Team A's micro-frontend
|-- product-listing/          # Team B's micro-frontend
|-- shopping-cart/            # Team C's micro-frontend
|-- user-profile/             # Team D's micro-frontend
|-- checkout/                 # Team E's micro-frontend
|-- shared/                   # Shared libraries and components
    |-- design-system/        # Shared UI components
    |-- event-bus/            # Communication layer
    |-- utils/                # Shared utilities

Tối ưu hóa hiệu suất

1. Tải theo thứ tự ưu tiên

// container/src/App.js
import { lazy, Suspense } from "react";

// Tải ngay lập tức
import Header from "header-nav/Header";

// Tải lazy các phần khác
const ProductListing = lazy(() => import("product-listing/ProductListing"));
const ShoppingCart = lazy(() => import("shopping-cart/ShoppingCart"));
const UserProfile = lazy(() => import("user-profile/UserProfile"));
const Checkout = lazy(() => import("checkout/Checkout"));

function App() {
  return (
    <div>
      <Header />

      <main>
        <Suspense fallback={<div>Loading products...</div>}>
          <ProductListing />
        </Suspense>

        <Suspense fallback={<div>Loading cart...</div>}>
          <ShoppingCart />
        </Suspense>
      </main>

      {/* Các phần khác chỉ được tải khi cần */}
    </div>
  );
}

2. Prefetching thông minh

// product-listing/src/ProductDetail.js
import { useEffect } from "react";

function ProductDetail({ product }) {
  useEffect(() => {
    // Prefetch checkout module khi người dùng xem chi tiết sản phẩm
    const prefetchCheckout = () => {
      import("checkout/Checkout");
    };

    // Prefetch sau 2 giây người dùng xem chi tiết sản phẩm
    const timer = setTimeout(prefetchCheckout, 2000);

    return () => clearTimeout(timer);
  }, []);

  return (
    <div>
      <h2>{product.name}</h2>
      <p>{product.description}</p>
      <button>Add to Cart</button>
    </div>
  );
}

3. Shared runtime dependencies

// webpack.config.js (for all micro-frontends)
module.exports = {
  // ...
  plugins: [
    new ModuleFederationPlugin({
      // ...
      shared: {
        react: { singleton: true, eager: true },
        "react-dom": { singleton: true, eager: true },
        "@company/design-system": { singleton: true },
        "@company/event-bus": { singleton: true },
        "@company/utils": { singleton: true },
      },
    }),
  ],
};

4. Incremental loading của dữ liệu

// product-listing/src/ProductListing.js
import { useState, useEffect } from "react";
import { fetchProducts } from "./api";

function ProductListing() {
  const [products, setProducts] = useState([]);
  const [page, setPage] = useState(1);
  const [loading, setLoading] = useState(false);

  const loadMoreProducts = async () => {
    setLoading(true);
    const newProducts = await fetchProducts(page);
    setProducts((prev) => [...prev, ...newProducts]);
    setPage((prev) => prev + 1);
    setLoading(false);
  };

  useEffect(() => {
    loadMoreProducts();
  }, []);

  return (
    <div>
      <div className="product-grid">
        {products.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>

      {loading ? (
        <div>Loading more products...</div>
      ) : (
        <button onClick={loadMoreProducts}>Load More</button>
      )}
    </div>
  );
}

3.6 Những hạn chế và lưu ý khi sử dụng Micro-frontends

  1. Độ phức tạp về cài đặt và quản lý: Micro-frontends có thể làm tăng độ phức tạp trong quản lý dự án và cài đặt.

  2. Overhead về kích thước: Nếu không được quản lý tốt, các micro-frontend có thể dẫn đến trùng lặp dependencies và tăng kích thước tải.

  3. Vấn đề về hiệu suất: Nhiều HTTP requests để tải các micro-frontend có thể ảnh hưởng đến hiệu suất.

  4. Khó khăn trong việc debug: Debug các vấn đề xuyên suốt nhiều micro-frontend có thể phức tạp hơn.

  5. Consistency: Duy trì sự nhất quán về UX và UI giữa các micro-frontend có thể là một thách thức.

Lưu ý khi sử dụng:

  • Chỉ áp dụng cho các ứng dụng lớn với nhiều team phát triển
  • Đầu tư vào infrastructure và tooling
  • Xây dựng design system chung
  • Thiết lập các tiêu chuẩn và guidelines rõ ràng
  • Sử dụng các kỹ thuật chia sẻ dependencies

4. Edge Computing: Đưa tính toán đến gần người dùng hơn

Edge Computing là một mô hình điện toán phân tán, đưa việc xử lý dữ liệu và tính toán đến gần người dùng hơn, thay vì tập trung tại các trung tâm dữ liệu lớn. Trong phát triển web, Edge Computing cho phép thực hiện các tác vụ tại các điểm cuối của mạng, giúp giảm độ trễ và cải thiện hiệu suất ứng dụng.

4.1 Các thành phần của Edge Computing

1. CDN (Content Delivery Network)

CDN là mạng lưới các máy chủ phân tán trên toàn cầu, lưu trữ bản sao của nội dung tĩnh và phân phối chúng đến người dùng từ vị trí gần nhất.

// Cấu hình CDN trong Next.js
// next.config.js
module.exports = {
  assetPrefix:
    process.env.NODE_ENV === "production" ? "https://cdn.example.com" : "",
};

2. Edge Functions/Workers

Edge Functions là các hàm nhỏ, chạy tại các điểm cuối của mạng, gần với người dùng hơn.

// Cloudflare Worker example
addEventListener("fetch", (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  // Xử lý request tại edge
  const url = new URL(request.url);

  if (url.pathname.startsWith("/api/")) {
    // Xử lý API requests
    return await handleApiRequest(request);
  }

  // Thêm header cache
  const response = await fetch(request);
  const newResponse = new Response(response.body, response);
  newResponse.headers.set("Cache-Control", "public, max-age=3600");

  return newResponse;
}

3. Edge Caching

Lưu trữ dữ liệu tại các điểm edge để giảm thời gian truy cập và tải dữ liệu.

// Vercel Edge Cache example
export const config = {
  runtime: "edge",
  regions: ["sfo1", "hnd1", "cdg1"], // Chọn các regions cụ thể
};

export default async function handler(req) {
  const data = await fetchData();

  return new Response(JSON.stringify(data), {
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "public, s-maxage=60, stale-while-revalidate=600",
    },
  });
}

4.2 Lợi ích của Edge Computing trong tối ưu frontend

1. Giảm độ trễ (Latency)

Việc đưa tính toán và xử lý gần với người dùng hơn giúp giảm đáng kể độ trễ trong giao tiếp mạng.

// Đo lường độ trễ trước và sau khi sử dụng Edge Functions
const startTime = performance.now();

// Trước: Gọi API từ server trung tâm
const response1 = await fetch("https://central-api.example.com/data");

const centralLatency = performance.now() - startTime;
console.log(`Central API latency: ${centralLatency}ms`);

// Sau: Gọi API từ edge
const edgeStartTime = performance.now();
const response2 = await fetch("https://edge.example.com/data");

const edgeLatency = performance.now() - edgeStartTime;
console.log(`Edge API latency: ${edgeLatency}ms`);

// Kết quả: Edge latency thường thấp hơn 50-80% so với central latency

2. Cải thiện khả năng phản hồi của ứng dụng

Xử lý tại edge giúp ứng dụng phản hồi nhanh hơn, đặc biệt là cho người dùng ở xa trung tâm dữ liệu.

3. Giảm tải cho backend

Nhiều tác vụ có thể được xử lý tại edge mà không cần gửi về backend trung tâm.

// Edge Function xử lý authentication và rate limiting
async function handleRequest(request) {
  // Kiểm tra token tại edge
  const token = request.headers.get("Authorization");

  if (!token || !validateTokenAtEdge(token)) {
    return new Response(JSON.stringify({ error: "Unauthorized" }), {
      status: 401,
      headers: { "Content-Type": "application/json" },
    });
  }

  // Rate limiting tại edge
  const clientIP = request.headers.get("CF-Connecting-IP");
  const rateLimitKey = `rate_limit:${clientIP}`;

  // Kiểm tra và cập nhật rate limit
  if (await isRateLimited(rateLimitKey)) {
    return new Response(JSON.stringify({ error: "Rate limit exceeded" }), {
      status: 429,
      headers: { "Content-Type": "application/json" },
    });
  }

  // Nếu mọi thứ OK, chuyển tiếp request đến origin
  return fetch(request);
}

4. Tối ưu hóa cho người dùng toàn cầu

Ứng dụng có thể phục vụ người dùng toàn cầu với hiệu suất nhất quán, không phụ thuộc vào vị trí địa lý.

4.3 Các kỹ thuật Edge Computing trong frontend

1. Server-Side Rendering (SSR) tại Edge

Thực hiện SSR tại các điểm edge thay vì tại server trung tâm.

// Next.js Edge SSR
// pages/product/[id].js
export const config = {
  runtime: "experimental-edge",
};

export async function getServerSideProps({ params, req }) {
  const { id } = params;
  const userRegion = req.headers.get("x-vercel-ip-country");

  // Fetch dữ liệu sản phẩm với context vùng miền
  const product = await fetchProductWithRegionalPricing(id, userRegion);

  return {
    props: { product },
  };
}

export default function ProductPage({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>Price: {product.localizedPrice}</p>
      {/* ... */}
    </div>
  );
}

2. Edge Middleware

Xử lý và biến đổi requests/responses tại edge trước khi chúng đến server hoặc client.

// Next.js Edge Middleware
// middleware.js
export const config = {
  matcher: "/api/:path*",
};

export default function middleware(request) {
  const url = request.nextUrl.clone();
  const userCountry = request.geo?.country || "US";

  // A/B testing tại edge dựa trên vùng miền
  if (userCountry === "JP") {
    url.searchParams.set("variant", "japan");
  } else if (userCountry === "DE") {
    url.searchParams.set("variant", "germany");
  } else {
    url.searchParams.set("variant", "default");
  }

  return NextResponse.rewrite(url);
}

3. Edge Caching và Invalidation

Quản lý cache thông minh tại edge để cân bằng giữa hiệu suất và tính cập nhật của dữ liệu.

// Vercel Edge API Route với cache strategies
export const config = {
  runtime: "edge",
};

export default async function handler(req) {
  const url = new URL(req.url);
  const productId = url.searchParams.get("id");

  // Tạo cache key dựa trên query params
  const cacheKey = `product:${productId}`;

  // Thử lấy từ cache trước
  const cachedData = await cache.get(cacheKey);
  if (cachedData) {
    return new Response(cachedData, {
      headers: {
        "Content-Type": "application/json",
        "X-Cache": "HIT",
      },
    });
  }

  // Nếu không có trong cache, fetch dữ liệu mới
  const data = await fetchProductData(productId);

  // Lưu vào cache với TTL
  await cache.set(cacheKey, JSON.stringify(data), { ttl: 60 });

  return new Response(JSON.stringify(data), {
    headers: {
      "Content-Type": "application/json",
      "X-Cache": "MISS",
      "Cache-Control": "public, s-maxage=60, stale-while-revalidate=600",
    },
  });
}

4. Edge-based Authentication và Authorization

Xử lý xác thực và phân quyền tại edge để tăng tốc độ và giảm tải cho backend.

// Cloudflare Worker xử lý JWT authentication
async function handleRequest(request) {
  const token = request.headers.get("Authorization")?.split(" ")[1];

  if (!token) {
    return new Response(JSON.stringify({ error: "Missing token" }), {
      status: 401,
      headers: { "Content-Type": "application/json" },
    });
  }

  try {
    // Verify JWT tại edge
    const payload = await verifyJwtAtEdge(token);

    // Thêm user info vào headers
    const headers = new Headers(request.headers);
    headers.set("X-User-ID", payload.sub);
    headers.set("X-User-Role", payload.role);

    // Tạo request mới với headers đã được bổ sung
    const newRequest = new Request(request.url, {
      method: request.method,
      headers,
      body: request.body,
    });

    // Chuyển tiếp request đến origin
    return fetch(newRequest);
  } catch (error) {
    return new Response(JSON.stringify({ error: "Invalid token" }), {
      status: 401,
      headers: { "Content-Type": "application/json" },
    });
  }
}

4.4 Các nền tảng Edge Computing phổ biến

1. Cloudflare Workers

Cho phép chạy JavaScript/WebAssembly tại hơn 200 điểm edge trên toàn cầu.

// wrangler.toml
name = "my-worker"
type = "javascript"

[site]
bucket = "./public"
entry-point = "workers-site"

[env.production]
route = "https://example.com/*"
zone_id = "your-zone-id"

2. Vercel Edge Functions

Tích hợp sẵn với Next.js, cho phép xử lý tại edge với cú pháp đơn giản.

// pages/api/edge-function.js
export const config = {
  runtime: "edge",
};

export default function handler(req) {
  return new Response(
    JSON.stringify({
      name: "Edge Function",
      region: process.env.VERCEL_REGION,
      date: new Date().toISOString(),
    }),
    {
      status: 200,
      headers: {
        "content-type": "application/json",
      },
    }
  );
}

3. AWS Lambda@Edge

Chạy Node.js functions tại các điểm edge của Amazon CloudFront.

// Lambda@Edge function
exports.handler = async (event) => {
  const request = event.Records[0].cf.request;
  const headers = request.headers;

  // Thêm header bảo mật
  headers["strict-transport-security"] = [
    {
      key: "Strict-Transport-Security",
      value: "max-age=31536000; includeSubDomains; preload",
    },
  ];

  headers["x-content-type-options"] = [
    {
      key: "X-Content-Type-Options",
      value: "nosniff",
    },
  ];

  return request;
};

4. Fastly Compute@Edge

Chạy WebAssembly tại edge với hiệu suất cao và độ trễ thấp.

// Fastly Compute@Edge với JavaScript
async function handleRequest(event) {
  // Lấy client request
  let req = event.request;

  // Tạo URL object từ request
  let url = new URL(req.url);

  // Nếu path là /api, chuyển tiếp đến backend API
  if (url.pathname.startsWith("/api/")) {
    return fetch(req, {
      backend: "api_backend",
    });
  }

  // Nếu không, phục vụ từ content backend
  return fetch(req, {
    backend: "content_backend",
  });
}

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

4.5 Ví dụ thực tế: Tối ưu hóa ứng dụng React với Edge Computing

Hãy xem xét một ứng dụng React với các yêu cầu tối ưu hóa hiệu suất toàn cầu:

1. Cấu trúc ứng dụng

/
|-- src/
|   |-- components/
|   |-- pages/
|   |-- api/
|   |-- utils/
|-- public/
|-- edge/
|   |-- middleware.js
|   |-- api/
|       |-- product.js
|       |-- user.js
|-- next.config.js
|-- package.json

2. Cấu hình CDN và Edge caching

// next.config.js
module.exports = {
  images: {
    domains: ["cdn.example.com"],
    minimumCacheTTL: 60,
  },
  async headers() {
    return [
      {
        source: "/api/:path*",
        headers: [
          {
            key: "Cache-Control",
            value: "public, max-age=60, stale-while-revalidate=600",
          },
        ],
      },
      {
        source: "/:path*",
        headers: [
          {
            key: "Cache-Control",
            value: "public, max-age=3600, stale-while-revalidate=86400",
          },
        ],
      },
    ];
  },
};

3. Edge API Routes

// edge/api/product.js
export const config = {
  runtime: "edge",
};

export default async function handler(req) {
  const url = new URL(req.url);
  const id = url.searchParams.get("id");
  const country = req.geo?.country || "US";

  // Fetch dữ liệu sản phẩm với thông tin vùng miền
  const product = await fetchProductData(id, country);

  // Cá nhân hóa dữ liệu dựa trên vùng miền
  const localizedProduct = {
    ...product,
    price: formatCurrency(product.price, country),
    shipping: calculateShippingForCountry(country),
    inStock: isProductAvailableInRegion(product, country),
  };

  return new Response(JSON.stringify(localizedProduct), {
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "public, s-maxage=60, stale-while-revalidate=600",
    },
  });
}

4. Edge Middleware cho A/B testing và personalization

// edge/middleware.js
import { NextResponse } from "next/server";

export const config = {
  matcher: ["/", "/products/:path*"],
};

export default function middleware(request) {
  const response = NextResponse.next();

  // Lấy thông tin vùng miền từ request
  const country = request.geo?.country || "US";
  const city = request.geo?.city || "Unknown";
  const region = request.geo?.region || "Unknown";

  // Thêm cookies cho client-side personalization
  response.cookies.set(
    "user-geo",
    JSON.stringify({
      country,
      city,
      region,
    })
  );

  // A/B testing dựa trên vùng miền
  const testGroup = getTestGroupForRegion(country);
  response.cookies.set("ab-test-group", testGroup);

  // Thêm headers cho analytics
  response.headers.set("X-User-Geo", `${country}|${region}|${city}`);

  return response;
}

function getTestGroupForRegion(country) {
  // Logic phân nhóm A/B test dựa trên vùng miền
  const asiaCountries = ["JP", "KR", "CN", "VN", "TH"];
  const euCountries = ["DE", "FR", "UK", "IT", "ES"];

  if (asiaCountries.includes(country)) {
    return "asia-variant";
  } else if (euCountries.includes(country)) {
    return "eu-variant";
  } else {
    return "default-variant";
  }
}

5. Client-side sử dụng thông tin từ Edge

// src/pages/products/[id].js
import { useEffect, useState } from "react";
import { useRouter } from "next/router";

export default function ProductPage() {
  const router = useRouter();
  const { id } = router.query;
  const [product, setProduct] = useState(null);
  const [userGeo, setUserGeo] = useState(null);

  useEffect(() => {
    // Lấy thông tin geo từ cookie đã được set bởi Edge Middleware
    const geoData = document.cookie
      .split("; ")
      .find((row) => row.startsWith("user-geo="))
      ?.split("=")[1];

    if (geoData) {
      setUserGeo(JSON.parse(decodeURIComponent(geoData)));
    }
  }, []);

  useEffect(() => {
    if (id && userGeo) {
      // Fetch sản phẩm từ Edge API với thông tin vùng miền
      fetch(`/api/product?id=${id}&country=${userGeo.country}`)
        .then((res) => res.json())
        .then((data) => setProduct(data));
    }
  }, [id, userGeo]);

  if (!product) return <div>Loading...</div>;

  return (
    <div>
      <h1>{product.name}</h1>
      <p>Price: {product.price}</p>
      <p>Shipping: {product.shipping}</p>
      <p>Availability: {product.inStock ? "In Stock" : "Out of Stock"}</p>

      {userGeo && (
        <div className="geo-info">
          <p>
            Personalized for: {userGeo.city}, {userGeo.country}
          </p>
        </div>
      )}

      {/* Hiển thị nội dung khác nhau dựa trên A/B test group */}
      <div className="product-actions">
        <AddToCartButton product={product} />
      </div>
    </div>
  );
}

4.6 Những hạn chế và lưu ý khi sử dụng Edge Computing

1. Giới hạn về tài nguyên

Edge functions thường có giới hạn về thời gian thực thi, bộ nhớ và kích thước code.

2. Độ phức tạp trong debugging

Debug các vấn đề tại edge có thể phức tạp hơn so với debugging tại local hoặc server trung tâm.

3. Chi phí

Tùy thuộc vào nhà cung cấp, chi phí cho Edge Computing có thể cao hơn so với hosting truyền thống.

4. Tính nhất quán của dữ liệu

Quản lý tính nhất quán của dữ liệu giữa các điểm edge có thể là một thách thức.

5. Hạn chế về runtime và libraries

Không phải tất cả các libraries JavaScript đều tương thích với môi trường edge.

Lưu ý khi sử dụng:

  • Chỉ đưa những tính năng cần độ trễ thấp lên edge
  • Sử dụng các kỹ thuật cache hiệu quả
  • Theo dõi chi phí và hiệu suất
  • Cân nhắc kỹ lưỡng giữa edge và server trung tâm cho từng tính năng
  • Thiết kế hệ thống với khả năng phục hồi khi edge gặp sự cố

Kết luận

Các kỹ thuật tối ưu nâng cao như Web Workers, WebAssembly, Micro-frontends và Edge Computing mang đến những cách tiếp cận mạnh mẽ để cải thiện hiệu suất của ứng dụng web hiện đại. Mỗi kỹ thuật có những ưu điểm và hạn chế riêng, phù hợp với các trường hợp sử dụng khác nhau.

Khi áp dụng các kỹ thuật này, cần cân nhắc kỹ lưỡng về độ phức tạp, chi phí và lợi ích thực tế mà chúng mang lại. Không phải tất cả các ứng dụng đều cần sử dụng tất cả các kỹ thuật này, và việc áp dụng quá mức có thể dẫn đến sự phức tạp không cần thiết.

Bạn có thắc mắc hay góp ý gì bình luận bên dưới để cùng thảo luận nhé.