Sẽ mở trong thẻ mới

Công cụ tối ưu ảnh cho WordPress bằng Node.js, Sharp và FFmpeg

Nếu bạn sử dụng WordPress đủ lâu, gần như chắc chắn sẽ gặp một vấn đề rất quen thuộc: thư viện Media ngày càng phình to, hình ảnh ngày càng nặng và website ngày càng khó tối ưu tốc độ.

Một bức ảnh chụp từ điện thoại ngày nay có thể có dung lượng vài MB. Người dùng tải ảnh lên WordPress, WordPress tạo thêm hàng loạt kích thước thumbnail, rồi theme, page builder và plugin lại tiếp tục sử dụng những phiên bản khác nhau của cùng một hình ảnh.

Cuối cùng, một website có thể chứa hàng chục nghìn file ảnh.

Có rất nhiều plugin và dịch vụ đã giải quyết vấn đề này. ShortPixel, Imagify và nhiều công cụ khác cho phép WordPress tự động nén ảnh, chuyển đổi sang WebP hoặc AVIF, resize ảnh và xử lý hàng loạt.

Tôi cũng đã sử dụng những giải pháp như vậy.

Nhưng sau nhiều năm làm WordPress, tôi bắt đầu tự hỏi một câu khá đơn giản:

Nếu tôi có thể tự xây hệ thống xử lý ảnh thì sao?

Và đó là lý do ImgPress ra đời.

ImgPress là gì?

ImgPress là một hệ thống xử lý media mà tôi đang xây dựng bằng Node.js.

Thay vì để WordPress tự xử lý tất cả, tôi tách phần xử lý media thành một service riêng.

Mô hình cơ bản là:

WordPress
    │
    │ API
    ▼
 ImgPress
    │
    ├── Sharp
    ├── FFmpeg
    └── Ghostscript
    │
    ▼
Optimized Media

WordPress chịu trách nhiệm quản lý Media Library và dữ liệu liên quan đến website.

ImgPress chịu trách nhiệm xử lý file.

Đây là một sự phân chia khá đơn giản, nhưng nó mở ra nhiều khả năng hơn so với việc nhét toàn bộ logic vào một plugin WordPress.

Source code của ImgPress được tôi public trên GitHub:

ImgPress.org – Node.js media processing service

ImgPress for WordPress

Tại sao không chỉ dùng một plugin WordPress?

Đây là câu hỏi hợp lý nhất.

Nếu mục tiêu chỉ là nén ảnh cho một website WordPress, việc cài một plugin có sẵn chắc chắn dễ dàng hơn rất nhiều.

Bạn cài plugin.

Nhập API key.

Chọn mức compression.

Xong.

Tôi không xây ImgPress vì muốn nói rằng những plugin đó không tốt.

Ngược lại, những dịch vụ như ShortPixel hay Imagify đã giải quyết bài toán này ở quy mô lớn và có rất nhiều tính năng mà ImgPress hiện chưa có.

Tôi xây ImgPress vì muốn kiểm soát toàn bộ quá trình xử lý.

Khi tự host service, tôi có thể quyết định file được xử lý ở đâu, chất lượng ảnh như thế nào, API hoạt động ra sao, storage nằm ở đâu và hệ thống có thể được mở rộng theo cách nào.

Đặc biệt với những website tôi tự quản lý, việc có một media-processing service riêng khá thú vị.

Sharp làm phần khó nhất trở nên đơn giản

Phần quan trọng nhất của ImgPress là Sharp.

Sharp là một thư viện xử lý ảnh hiệu năng cao cho Node.js, sử dụng libvips bên dưới. Nó hỗ trợ resize và chuyển đổi giữa nhiều định dạng phổ biến như JPEG, PNG, WebP, AVIF và TIFF.

Ví dụ, về mặt ý tưởng, việc resize một ảnh và xuất thành WebP có thể đơn giản như:

await sharp(input)
    .resize({
        width: 1600,
        withoutEnlargement: true
    })
    .webp({
        quality: 80
    })
    .toFile(output);

Đây chính là một trong những lý do tôi thích Node.js cho dự án này.

Tôi không cần phải tự viết image-processing engine.

Tôi chỉ cần xây phần workflow và API xung quanh một engine đã rất mạnh.

Sharp cũng được thiết kế để xử lý ảnh với hiệu năng cao nhờ libvips. Theo tài liệu của dự án, việc resize thường nhanh hơn đáng kể so với một số cấu hình ImageMagick và GraphicsMagick tương ứng.

Đối với một service có thể phải xử lý hàng trăm hoặc hàng nghìn ảnh, điều này rất quan trọng.

Nén ảnh không chỉ là đổi JPEG thành WebP

Khi mới bắt đầu, tôi cũng nghĩ bài toán khá đơn giản:

JPEG → WebP

Nhưng thực tế không phải vậy.

Một ảnh 6000 × 4000 pixel dù được nén rất tốt vẫn có thể quá lớn đối với một website.

Nếu ảnh chỉ được hiển thị ở khoảng 1200px, việc gửi một file 6000px đến trình duyệt vốn đã là một sự lãng phí.

Vì vậy ImgPress xử lý hai vấn đề khác nhau:

Resize để giảm kích thước vật lý của ảnh.

Compression để giảm dung lượng file.

Hai thứ này thường được gọi chung là “tối ưu ảnh”, nhưng thực tế chúng là hai bước khác nhau.

Một ảnh từ máy ảnh hoặc điện thoại có thể cần resize xuống kích thước phù hợp trước, sau đó mới encode lại thành WebP hoặc AVIF.

Sharp hỗ trợ cả resize lẫn nhiều thao tác xử lý hình ảnh khác, bao gồm chuyển đổi màu, rotation, compositing và nhiều thao tác khác.

WebP và AVIF

Một trong những mục tiêu chính của ImgPress là tạo ra các định dạng phù hợp hơn với web hiện đại.

JPEG vẫn rất phổ biến.

PNG vẫn cần thiết trong nhiều trường hợp.

Nhưng WebP và AVIF ngày càng trở thành những lựa chọn đáng cân nhắc khi muốn giảm dung lượng hình ảnh.

Sharp hỗ trợ cả WebP và AVIF nên việc thêm các format này vào pipeline không cần phải xây dựng một encoder riêng.

Điều thú vị là chất lượng không đơn giản chỉ là một con số.

Ví dụ:

quality = 80

không có nghĩa rằng WebP quality 80 và AVIF quality 80 sẽ cho ra hai kết quả tương đương.

Mỗi codec có cách encode và đặc tính khác nhau.

Đó là lý do một image optimizer thực sự không chỉ là một form có ô nhập:

Quality: 80

Đằng sau con số đó là cả một quá trình lựa chọn format, kích thước và encoder.

FFmpeg đưa ImgPress ra ngoài phạm vi hình ảnh

Ban đầu tôi chỉ định làm một image optimizer.

Nhưng khi đã có một service chuyên xử lý media, tôi bắt đầu nghĩ:

Tại sao chỉ xử lý image?

Và FFmpeg xuất hiện.

FFmpeg là một framework xử lý multimedia rất mạnh, có thể decode, encode, transcode và xử lý audio/video.

Trong ImgPress, FFmpeg được sử dụng cho những trường hợp mà Sharp không phải công cụ phù hợp.

Ví dụ:

HEIC / HEIF
      ↓
   FFmpeg
      ↓
Image pipeline
      ↓
WebP / AVIF / JPEG

Nó cũng mở đường cho việc xử lý audio và video.

Như vậy, ImgPress dần trở thành một media-processing service thay vì chỉ là một image compressor.

Đây là một thay đổi khá thú vị trong quá trình phát triển dự án.

Tôi bắt đầu bằng một vấn đề rất cụ thể của WordPress, nhưng kiến trúc cuối cùng lại có thể phục vụ nhiều ứng dụng khác.

WordPress plugin chỉ là phần kết nối

Song song với Node.js service, tôi xây dựng một plugin WordPress riêng.

Plugin này không thực hiện compression trực tiếp.

Nó đóng vai trò cầu nối giữa WordPress và ImgPress.

WordPress Plugin
       │
       │ HTTP API
       ▼
    ImgPress
       │
       ▼
Optimized File

Điều này có một lợi ích lớn.

Nếu sau này tôi thay đổi cách xử lý ảnh, WordPress plugin không nhất thiết phải thay đổi.

Nếu ImgPress được sử dụng bởi một ứng dụng khác ngoài WordPress, service cũng không cần biết ứng dụng đó là gì.

Đó là lý do tôi thích việc tách API khỏi WordPress.

Tự động tối ưu ngay khi upload

Một trong những tính năng quan trọng nhất của plugin là khả năng tự động xử lý ảnh khi upload.

Thay vì quy trình quen thuộc:

Upload ảnh
    ↓
WordPress lưu ảnh gốc
    ↓
Tạo thumbnails
    ↓
Plugin phát hiện ảnh
    ↓
Gọi API
    ↓
Optimize

tôi muốn quá trình xử lý được tích hợp gần hơn với thời điểm WordPress tạo attachment.

Mục tiêu là tránh việc website phải giữ một file quá lớn lâu hơn cần thiết.

Đây cũng là một ví dụ cho thấy xây một plugin image optimizer không đơn giản chỉ là gọi API nén ảnh.

Bạn phải hiểu WordPress Media Library, attachment metadata, thumbnail generation và cách WordPress lưu URL của media.

Và đây mới là phần khó

Nếu chỉ viết một API:

POST /compress

thì ImgPress không quá phức tạp.

Phần khó hơn nằm ở WordPress.

Giả sử tôi có:

/uploads/2026/09/photo.jpg

và sau khi tối ưu nó trở thành:

/uploads/2026/09/photo.webp

Thay đổi file là một chuyện.

Thay đổi mọi nơi đang tham chiếu đến file đó lại là chuyện khác.

URL hình ảnh có thể xuất hiện trong post content, attachment metadata, custom fields, theme options hoặc dữ liệu của page builder.

Với WordPress, tôi đặc biệt quan tâm đến Bricks Builder vì đây là builder tôi sử dụng khá nhiều.

Do đó plugin phải xử lý cả những trường hợp URL hình ảnh nằm trong dữ liệu của Bricks.

Đây là kiểu vấn đề chỉ xuất hiện khi bạn thực sự xây một sản phẩm cho WordPress.

Image compression có thể giải quyết bằng một thư viện.

WordPress integration thì không.

Bulk optimization

Một website mới có thể xử lý ảnh ngay khi upload.

Nhưng website cũ thì sao?

Đây mới là vấn đề lớn.

Một website WordPress đã chạy vài năm có thể có hàng nghìn hoặc hàng chục nghìn hình ảnh.

Bạn không thể yêu cầu quản trị viên tải lại từng ảnh.

Vì vậy ImgPress for WordPress cũng có workflow để xử lý các media đã tồn tại trong Media Library.

Bạn có thể chọn ảnh và chạy optimization theo batch.

Khi đó ImgPress trở thành một công cụ không chỉ dành cho website mới mà còn có thể dùng để dọn dẹp và tối ưu một website WordPress cũ.

Không nhất thiết phải lưu media trên cùng server

Một phần khác tôi đang thử nghiệm là sử dụng Cloudflare R2 làm storage.

Điều này mở ra một kiến trúc khác:

WordPress
    │
    ▼
 ImgPress
    │
    ▼
Cloudflare R2
    │
    ▼
  CDN
    │
    ▼
 Visitor

WordPress không nhất thiết phải là nơi cuối cùng lưu toàn bộ media.

ImgPress có thể xử lý file, sau đó media có thể được đưa sang object storage.

Đây là hướng đặc biệt thú vị với những website có lượng media lớn.

Thay vì coi WordPress server là nơi làm mọi thứ, chúng ta có thể tách:

application → processing → storage → delivery

thành những thành phần riêng biệt.

Vậy có nên tự xây một image optimizer không?

Nếu bạn chỉ có một website WordPress nhỏ thì có lẽ câu trả lời thực tế là không cần.

Cài một plugin có sẵn sẽ nhanh hơn rất nhiều.

Nhưng nếu bạn là developer, đang quản lý nhiều website hoặc muốn xây infrastructure của riêng mình thì câu chuyện lại khác.

Việc tự xây một hệ thống như ImgPress mang lại một giá trị khác: bạn hiểu rõ hơn những gì đang xảy ra phía sau một tính năng tưởng như rất đơn giản.

Ví dụ, khi một plugin nói:

Optimize image

thực tế có thể bao gồm:

Detect format
     ↓
Read metadata
     ↓
Auto orientation
     ↓
Resize
     ↓
Color conversion
     ↓
Encode
     ↓
Compare size
     ↓
Store result
     ↓
Update WordPress metadata
     ↓
Update references
     ↓
Optional remote storage

Đằng sau một nút bấm nhỏ là khá nhiều engineering.

Điều tôi thích nhất ở dự án này

Điều thú vị nhất của ImgPress không phải là tôi đã viết được một chương trình nén ảnh.

Sharp đã làm phần lớn công việc khó đó rồi.

Điều tôi thích là tôi có thể lấy những công cụ rất mạnh như Node.js, Sharp, FFmpeg và Docker, sau đó ghép chúng lại thành một service phục vụ đúng nhu cầu của mình.

Tôi cũng thích việc WordPress không còn phải làm tất cả.

WordPress làm tốt vai trò CMS.

Node.js làm tốt vai trò media-processing service.

Object storage làm tốt vai trò lưu trữ.

CDN làm tốt vai trò phân phối.

Khi mỗi thành phần có một nhiệm vụ rõ ràng, hệ thống trở nên dễ mở rộng hơn.

Từ một plugin WordPress đến một mảnh infrastructure

ImgPress bắt đầu từ một câu hỏi rất đơn giản:

Tôi có thể tự xây một phiên bản self-hosted của các dịch vụ tối ưu ảnh cho WordPress không?

Câu trả lời là có.

Nhưng trong quá trình xây, tôi nhận ra bài toán thú vị hơn không nằm ở việc “nén ảnh”.

Nó nằm ở việc xây một media-processing layer mà WordPress chỉ là một trong những client.

Hôm nay tôi có thể dùng nó cho WordPress.

Ngày mai một Laravel application có thể gọi API đó.

Một Node.js application khác cũng có thể sử dụng.

Hoặc một script chạy trên server có thể gửi file vào pipeline.

Đó là lý do tôi muốn giữ ImgPress như một service độc lập thay vì biến toàn bộ dự án thành một plugin WordPress khổng lồ.

Mã nguồn

Tôi đã public cả hai phần của dự án trên GitHub.

ImgPress.org là Node.js service xử lý media:

https://github.com/binjuhor/imgpress.org

ImgPress for WordPress là plugin kết nối WordPress với service:

https://github.com/binjuhor/imgpress-wp

Dự án vẫn đang được phát triển và chắc chắn còn rất nhiều thứ có thể cải thiện.

Nhưng với tôi, đây là một ví dụ khá rõ về cách một vấn đề rất quen thuộc trong WordPress có thể dẫn đến một giải pháp hoàn toàn khác: thay vì tiếp tục phụ thuộc vào một dịch vụ bên ngoài, hãy tách bài toán ra và tự xây phần infrastructure mà mình thực sự cần.

Để lại bình luận đầu tiên

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Danh mục