Biến React App thành PWA chuyên nghiệp: Từ Service Worker đến chiến lược Offline ‘xịn’

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Tại sao PWA không còn là tùy chọn, mà là bắt buộc?

Mạng chập chờn là kẻ thù số một của trải nghiệm người dùng. Thực tế cho thấy, hơn 50% người dùng sẽ rời bỏ một trang web nếu nó mất quá 3 giây để tải. Thử tưởng tượng khách hàng đang đứng trong thang máy hoặc vùng sóng yếu, trang web của bạn hiện lỗi “No Internet” trắng xóa. Đó là lúc bạn mất điểm trầm trọng.

PWA (Progressive Web App) chính là lời giải. Nó giúp web app có thể cài đặt trực tiếp vào màn hình điện thoại, gửi thông báo đẩy và hoạt động mượt mà khi offline. Mình từng cứu một dự án quản lý kho ở vùng biên giới nhờ PWA. Lúc đó sóng 3G chỉ đạt mức 1-2 vạch, nhưng nhờ cơ chế cache dữ liệu và đồng bộ sau (background sync), nhân viên vẫn nhập liệu phăm phăm mà không mất một byte dữ liệu nào.

Ba con đường tích hợp PWA vào React

Khi bắt đầu, bạn thường sẽ đứng trước ba lựa chọn với những ưu nhược điểm riêng biệt:

1. Template cũ của Create React App (CRA)

Trước đây, lệnh --template cra-template-pwa là tiêu chuẩn. Tuy nhiên, CRA hiện đã lỗi thời (deprecated). Việc can thiệp vào cấu hình Workbox trong CRA giống như đi vào ngõ cụt vì nó cực kỳ gò bó.

2. Tự viết Service Worker (Hardcore)

Cách này giúp bạn kiểm soát 100% logic từ install đến fetch. Tuy nhiên, rủi ro cực lớn là lỗi “cache vĩnh viễn”. Chỉ cần sai một dòng code, người dùng sẽ bị kẹt ở phiên bản cũ mãi mãi. Mình từng phải muối mặt gọi điện hướng dẫn khách hàng xóa cache thủ công vì dính lỗi này.

3. Vite PWA Plugin (Lựa chọn tối ưu)

Nếu dự án dùng Vite, vite-plugin-pwa là chân ái. Nó đóng gói sẵn Workbox, giúp bạn triển khai PWA chỉ trong vòng 5-10 phút cấu hình nhưng vẫn đảm bảo độ tùy biến cao.

Triển khai PWA với Vite trong 3 bước

Giả sử bạn đã có sẵn một dự án React khởi tạo bằng Vite. Hãy bắt đầu ngay.

Bước 1: Cài đặt

npm add -D vite-plugin-pwa

Bước 2: Thiết lập linh hồn cho App (vite.config.ts)

Mở file vite.config.ts và thêm cấu hình Manifest. Đây là thông tin để điện thoại nhận diện web của bạn là một ứng dụng thực thụ.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    react(),
    VitePWA({
      registerType: 'autoUpdate', 
      includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'mask-icon.svg'],
      manifest: {
        name: 'React Pro App',
        short_name: 'ReactPWA',
        description: 'Ứng dụng tối ưu trải nghiệm offline',
        theme_color: '#ffffff',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png',
            purpose: 'any maskable'
          }
        ]
      }
    })
  ]
})

Mẹo nhỏ: Đừng quên thuộc tính purpose: 'any maskable'. Thiếu nó, icon app trên Android sẽ bị bao quanh bởi một viền trắng rất xấu và thiếu chuyên nghiệp.

Bước 3: Kích hoạt Service Worker

Tại file main.tsx, hãy đăng ký Service Worker để trình duyệt bắt đầu quá trình lưu trữ tài nguyên.

import { registerSW } from 'virtual:pwa-register'

const updateSW = registerSW({
  onNeedRefresh() {
    if (confirm('Có bản cập nhật mới, bạn muốn tải lại chứ?')) {
      updateSW(true)
    }
  },
  onOfflineReady() { console.log('App đã sẵn sàng chạy offline!') },
})

Chiến lược Offline: Đừng để Cache làm phiền dữ liệu

Cache file tĩnh (CSS, JS) là chưa đủ. Với dữ liệu API, mình khuyên dùng chiến lược Stale-While-Revalidate.

Cơ chế này hoạt động rất thông minh: App sẽ ưu tiên lấy dữ liệu cũ từ Cache để hiển thị ngay lập tức (tăng tốc độ tải), sau đó mới âm thầm gọi API để cập nhật dữ liệu mới nhất vào Cache cho lần sau. Người dùng sẽ thấy app phản hồi tức thì thay vì phải chờ đợi vòng xoay loading.

Cấu hình Workbox cho API

workbox: {
  runtimeCaching: [
    {
      urlPattern: ({ url }) => url.pathname.startsWith('/api'),
      handler: 'StaleWhileRevalidate',
      options: {
        cacheName: 'api-cache',
        expiration: {
          maxEntries: 50,
          maxAgeSeconds: 86400 // Cache trong 24 giờ
        }
      }
    }
  ]
}

Bài học xương máu: Những “hố đen” cần tránh

Sau nhiều dự án thực tế, đây là 3 điều bạn cần ghi nhớ để không phải thức đêm fix bug:

  • Header Cache-Control: Tuyệt đối không set max-age quá dài cho file sw.js trên server. Nếu file này bị cache cứng, bạn sẽ không thể đẩy bất kỳ bản cập nhật nào tới người dùng.
  • Nỗi ám ảnh iOS: Safari trên iPhone có hành vi rất khác Chrome. Hãy dùng ngrok để test trực tiếp trên điện thoại thật thay vì chỉ tin vào DevTools của trình duyệt.
  • Dung lượng bộ nhớ: Đừng cache vô tội vạ hàng nghìn tấm ảnh. Trình duyệt sẽ tự động xóa sạch cache của bạn nếu dung lượng vượt quá giới hạn cho phép (thường là 50MB – 100MB tùy thiết bị).

Lời kết

Lên PWA không chỉ là để có cái icon trên màn hình điện thoại. Đó là cách bạn thể hiện sự tôn trọng với thời gian của người dùng, giúp họ tiếp cận thông tin ngay cả khi hạ tầng mạng không ủng hộ. Chúc anh em triển khai thành công, nếu gặp lỗi “lạ” nào cứ để lại comment mình cùng thảo luận nhé!

Share: