Bí kíp công nghệ thượng thừa giúp người dùng hào hứng chia sẻ trang web của bạn lên mạng xã hội... hoặc không
First things first
Hôm nay là 28 tết, và mình - chàng lập trình viên tính cả tuổi mụ đã tới đầu băm vẫn ngồi cặm cụi ở công ty khi mọi người xung quanh đã về hết để gõ những dòng chữ cuối cùng của năm 2022 này. Câu chuyện hôm nay mình mang tới cho các bạn được đặc biệt trích ra từ quyển bí kíp nghề nghiệp mình cất giấu bấy lâu nay. Quyển bí kíp này ngoài chứa đựng những công nghệ lõi của hệ thống mạng xã hội nói tiếng anh lớn nhất Việt Nam (cứ nhận thế cho oai) thì còn chứa rất nhiều các pha xử lý kinh điển đi vào lòng người của mình và đồng bọn nữa. Hôm nay vui lắm mới mang chiếc công nghệ lõi này ra hướng dẫn lì xì các bạn đầu năm đó nhé. Nhớ đáp lễ mình bằng thiệt nhiều upvote và bookmark nha.
30s để các bạn tìm nút upvote và bookmark bắt đầu... (cứ upvote với bookmark trước đi không thất vọng đâu)
Ok chưa? Ta bắt đầu luôn này!
Làm sao để có thumbnail khi chia sẻ trên facebook?
Hay nói rộng hơn là làm sao để website của bạn hiển thị preview trên các nền tảng mạng xã hội như Facebook, Twitter,... hay khi chia sẻ trên các ứng dụng chat như Telegram, Skype, Zalo,...?
Chắc nhiều bạn cũng biết rồi, thế nên chỗ này mình sẽ chỉ lướt qua 1 ít về luồng để những bạn chưa biết có thể follow tiếp thôi.
Việc hiển thị được chiếc ảnh preview khi bạn paste 1 cái url vào bài đăng Facebook sẽ được thực hiện như sau:
- Bạn paste url
yourweb.com
vào phần bài đăng - Browser request API lấy preview information tới server Facebook.
- Server Facebook thực hiện việc crawl url
yourweb.com
để lấy nội dung html trang web của bạn (tức là server Facebook sẽ truy cập vào trang web của bạn để lấy html). Request từ Facebook server có thể được nhận biết thông qua header user-agent dạngfacebookexternalhit/1.1
,... - Server của bạn trả về nội dung html như bình thường cho Facebook.
- Facebook sẽ thực hiện việc đọc nội dung html và thông qua các thẻ open-graph meta nằm trong phần
<head>
của trang để lấy các thông tin về trang web như title, description, image,... - Facebook trả về các thông tin trang web vừa lấy được cho browser để hiển thị preview bài đăng cho bạn.
Các bạn cần chú ý 2 thứ trong flow trên để có thể cấu hình link preview cho đúng nè:
- Trang web của bạn phải trả thông tin open-graph meta trong HTML. Nếu như các bạn sinh ra meta tag bằng việc chạy code javascript dưới browser (kiểu các trang web Single-page Application (SPA)) thì Facebook sẽ không đọc được. Nói như này có nghĩa là nếu như các bạn muốn thay đổi thông tin meta tag trên từng url (kiểu share mỗi bài báo vnexpress có preview riêng biệt) thì ứng dụng của bạn phải chạy ở dạng Server-Side Rendering (SSR).
- Các bạn phải cấu hình ĐÚNG nội dung trong các thẻ Open-Graph meta. Trong đó Open-Graph protocol là một giao thức chung được phát triển đầu tiên bởi Facebook để giúp facebook nhanh chóng lấy được nội dung preview trang web mà không cần phải đọc nội dung từ các thẻ trong body vốn rất khác nhau với từng trang web. Open-Graph protocol hiện tại còn được sử dụng rộng rãi bởi các search engine như google, bing,... hay các app chat và nền tảng mạng xã hội khác như Telegram, Skype,...
Ok thế là xong, giới thiệu sơ sơ về luồng như vậy đã, còn việc cấu hình các thẻ Open Graph meta ra sao hay ứng dụng của bạn có phải Server-Side Rendering hay không thì các bạn tự tìm hiểu trên google nhé.
Ngoài lề một chút với ứng dụng SPA
À ở trên mình có bảo ứng dụng SPA (Single-Page Application) thì chia sẻ url lên Facebook sẽ không đọc được do đó chỉ setup được 1 preview chung cho toàn bộ trang mà thôi (do chỉ có duy nhất 1 file html tĩnh). Tuy nhiên các bạn cũng đừng buồn, bởi vì vấn đề này có thể work-around được (và thực tế là hầu hết các website chạy SPA đều đang làm) và mình đã giới thiệu cho các bạn trong bài viết Tôi, NuxtJS và trang livestream vạn CCU rồi.
Đó chính là việc phân chia traffic đến từ Google, facebook, crawler bot,... tới 1 phiên bản khác (SSR) của ứng dụng còn người dùng thì tới 1 phiên bản khác (SPA). Facebook thì thường không đọc nội dung trong html body mà chỉ đọc meta tag trong head do đó phiên bản SSR của mình nhiều khi cũng chẳng có body hay giao diện gì mà chỉ generate nội dung meta tag thôi.
Mình đã có list user-agent của các crawler lớn như Facebook, Google, Twitter, Telegram,... rồi còn các crawler của các nền tảng nhỏ khác cũng thường xuyên lấy user-agent na ná các ông lớn như Facebook hay Google thôi nên cứ làm theo chuẩn với thằng Facebook là ngon nhé.
Preview "chất" khi share website lên facebook
Chất ở đây ngoài việc phải đẹp, đúng kích thước thì có 1 yếu tố rất quan trọng là personalized nữa. Tức là bức ảnh share trên facebook nó cần phải cá nhân hóa thì người ta mới share, chứ còn share link nào cũng chỉ ra 1 cái ảnh của website đó thì đâu ai thích. Để làm điều này thì tương đối đơn giản, chúng ta có thể cho phép user của chúng ta tự chọn ảnh share social (giống như chính viblo đang làm khi mình viết bài đây):
Đây chắc chắn là cách dễ nhất để cá nhân hóa preview website lên facebook rồi, và hiện nay phần lớn website đều đang làm vậy. Tuy nhiên cách làm này cũng có những bất cập như sau:
- Người dùng cần phải có kiến thức về Facebook preview như kích thước phù hợp, thể hiện được nội dung,... để có một chiếc preview đẹp. Mà tất nhiên là cái gì phải học thì không phải ai cũng biết rồi.
- Có nhiều dạng nội dung như status, profile,... không thể cho phép tự chọn preview image, do đó sẽ lấy những bức hình có sẵn đi kèm kiểu background, avatar, ảnh trong bài,... để làm preview, do đó nó không đẹp và không phù hợp về kích thước. Chẳng hạn như vầy:
- Ai cũng biết Text trong image thì bắt mắt hơn là Text trong title, thế nhưng không phải người dùng nào cũng biết photoshop để đưa text vào trong image để preview cho đẹp.
- Mất nhận diện thương hiệu của website. Do mỗi link có 1 hình ảnh preview hoàn toàn khác nhau do đó người dùng rất khó để nhận ra preview tới từ đâu nếu không đọc kỹ nội dung. Ví dụ như khi chia sẻ bài viết Viblo với custom image, mọi người sẽ rất khó nhận ra url này tới từ viblo nếu như không đọc kỹ.
Vậy người nông dân phải làm sao để tạo được một trải nghiệm sharing thật khác biệt? Hãy thử xem Clubhouse, một mạng xã hội âm thanh rất nổi tiếng gần đây đang làm thế nào nhé?
Khá ok đấy chứ nhỉ? Chiếc preview này hội tụ đủ những tiêu chí để kích thích người dùng chia sẻ:
- Text in image: Biến những dòng text trở nên thú vị và bắt mắt hơn khi đưa vào image
- Personalized: Chứa avatar của người host, tăng nhận diện với bạn bè của họ
- Branding: Mặc dù personalized nhưng vẫn chung 1 template hình ảnh khiến mọi người nhận ra website gốc nhanh hơn khi lướt qua.
Thế nhưng làm sao để tạo 1 cái preview như này một cách tự động khi cần? Hãy cùng đến với công nghệ lõi của tụi mình sau đây.
Tạo hình ảnh preview tự động với công cụ html to image
Ngay khi nhìn thấy kiểu preview custom như trên thì bọn mình đã nghĩ ngay tới các giải pháp html to image, tức là biểu diễn nội dung custom bằng HTML, sau đó capture lại thành hình ảnh. Trên mạng có rất nhiều công cụ làm việc này (html to image) với vô vàn phương thức như headless-browser, chrome kernel,... tuy nhiên sau khi test thử 1 vài tool thì tụi mình có sử dụng doctron. Đây là một open-source render html và convert thành hình ảnh trên github với 375 star và phát hành dưới licence Apache-2.0.
Doctron rất phù hợp với bên mình vì được viết trên Go, với dung lượng nhẹ, tốc độ nhanh và dễ dàng triển khai bằng Docker trên các hạ tầng Docker compose, swarm hay kubernetes.
Kiến trúc để sinh preview url tự động như sau:
Sau khi đã facebook hoàn thành việc lấy thông tin preview cho url (bằng cách extract các thẻ og:title
, og:description
, og:image
từ html) thì công việc sẽ được tiếp tục với 1 chiếc url hình ảnh (ví dụ: https://img.yourweb.com/monmen.jpg
)
- Facebook sẽ request tới url hình ảnh này để download ảnh preview. Ví dụ là
https://img.yourweb.com/monmen.png
- Doctron sẽ cần có 1 url chứa html để tạo preview, do đó mình sẽ cần 1 con web chạy Server-Side Rendering ở đường dẫn
https://preview.yourweb.com/monmen
để lấy thông tin về đường dẫn/monmen
(như avatar, tên của mình) sau đó trả về trang web cho doctron - Doctron sẽ load html, image,... và capture lại thành image và trả lại cho Facebook
- Quá trình lấy thông tin và tạo image này cũng mất thời gian do đó sẽ cần cache lại ảnh kết quả phía trước doctron để sử dụng lại. Ở đây mình sẽ dùng luôn nginx proxy cache.
Tạo web render html cho doctron
Chiếc web này các bạn có thể làm bằng ngôn ngữ hay công cụ gì cũng được, cố làm càng đơn giản càng tốt để tốc độ load nó nhanh và mình cũng không sure lắm về việc doctron nó chạy được nhiều script phức tạp hay html nặng đâu nhé. Mình sẽ ví dụ bằng 1 em web đơn giản chạy bằng express với sự hỗ trợ view html từ handlebars nhé:
facebook-image-preview
├── index.js
├── package.json
├── views
│ └── profile.handlebars
└── yarn.lock
index.js thì đơn giản như này thôi, các bạn có thể lồng logic lấy data từ db, api,... hay đâu là tùy các bạn
const express = require('express');
const { engine } = require('express-handlebars'); const db = [ { slug: 'monmen', name: 'Minh Monmen', avatar: 'https://images.viblo.asia/avatar/835402e9-e961-44f4-9ec4-d8d64d71b26b.jpg', }, { slug: 'hehe', name: 'Hehe', avatar: 'https://images.viblo.asia/11a38b08-a914-441c-8f5a-4f9fe4fe567e.png', },
]; const app = express(); app.engine('handlebars', engine());
app.set('view engine', 'handlebars');
app.set('views', './views'); app.get('/:slug', (req, res) => { const profile = db.find((item) => item.slug === req.params.slug); if (!profile) { res.sendStatus(404); return; } res.render('profile', { layout: false, ...profile });
}); app.listen(3000);
Lưu ý kích thước chuẩn để preview trên facebook là 1200x630, các bạn hãy format phần html của mình có kích thước 1200x630 để có thể hiển thị 1 cách đẹp nhất nha.
Phần html có thể tùy ý sử dụng CSS vì doctron có thể render được CSS bình thường. Như trong ví dụ mình xài bootstrap chẳng hạn, tuy nhiên mình vẫn khuyên các bạn nên tối giản mọi thứ hết sức có thể để việc render diễn ra nhanh chóng và đỡ tốn tài nguyên.
Config doctron và nginx
Tới đây là các bạn đã hoàn thành 50% công việc rồi. Giờ chỉ cần trổ tài config với một ít kiến thức về server, docker và nginx là được rồi. Mình cũng ví dụ luôn cho các bạn nè:
Cấu hình nginx để rewrite url ban đầu thành đường dẫn mà doctron có thể hiểu được và cache
server { listen 80; server_name _; location / { rewrite /(\w+).png$ /convert/html2image?u=doctron&p=lampnick&url=http%3A%2F%2Fpreview%3A3000%2F$1 break; proxy_pass http://doctron:8080; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; } }
Ở đây nginx sẽ biến đường dẫn /monmen.png
trở thành đường dẫn chứa user và pass và url gen html là http://preview:3000/monmen
để gọi tới project node chạy ở port 3000 mình mới tạo ở trên. Các bạn có thể thay đổi user và pass của doctron bằng file custom config khi khởi chạy container doctron nhé.
Cấu hình docker để chạy tất cả cụm nào:
FROM node:18-alpine LABEL AUTHOR=minhpq331@gmail.com
EXPOSE 3000 WORKDIR /app ADD . /app
RUN yarn --pure-lockfile
CMD ["node", "index.js"]
version: '3.9' services: doctron: image: lampnick/doctron ports: - "8080" restart: always nginx: image: nginx:alpine ports: - "8000:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf restart: always preview: build: . ports: - "3000" restart: always
Và thế là xong, các bạn chỉ cần chạy docker compose up -d
và truy cập đường dẫn http://localhost:8000/monmen.png
là sẽ nhận được 1 bức ảnh đúng chuẩn share facebook. Có thể đổi tên file thành hehe.png
để xem sự khác biệt nữa nhé. Đây là kết quả:
App NodeJS của mình được chạy trên port 3000, doctron được chạy trên port 8080 và nginx chạy trên port 80 được expose ra bên ngoài ở port 8000.
Vài lời sau cuối
Toàn bộ code example phục vụ bài viết này đã được mình đẩy lên github tại đây: https://github.com/minhpq331/facebook-image-preview. Các bạn chỉ cần pull về chạy docker compose up -d
là hoạt động, tuy nhiên mình chỉ dừng lại ở bước demo thôi, còn các bạn hãy tự triển khai hệ thống trên cho phù hợp với từng sản phẩm của mình, thêm proxy cache cho nginx và security cho doctron nếu cần nhé.
Nhân dịp đầu xuân khai phím, mình xin chúc các bạn độc giả đang đọc bài viết này nói riêng và toàn thể độc giả Viblo nói chung một năm mới an khang thịnh vượng, dồi dào sức khỏe và tiếp tục đọc nhiều để học hỏi thêm nhiều kiến thức cho bản thân nhé. Anh em ngày xuân vào đọc thấy hay ho nhớ cho mình 1 upvote, 1 bookmark và thật nhiều comment đóng góp để bài viết chất lượng hơn nha.
Chúc mừng năm mới!
p/s: Anh em Viblo nếu có sử dụng công nghệ này để tạo lại ảnh preview khi share web viblo lên facebook thì mình không cần cảm ơn gì nhiều đâu xin chiếc loa là đủ haha =))