Cách tôi tạo ảnh hero và OG cho blog kỹ thuật mà không mất công sức

Table of Contents
Mọi bài đăng trên blog tôi viết đều bắt đầu theo cùng một cách: tôi hoàn thành bài viết, sau đó dành 30 phút trong Figma để tạo hình ảnh hero và hình ảnh OG. Bài viết đã xong, nhưng những công việc "nhỏ nhặt" này cứ chồng chất.
Nghe có quen không?
Điểm bùng phát đến khi tôi cần cập nhật thương hiệu cho blog của mình. Tôi thay đổi một màu và đột nhiên có hơn 40 hình ảnh cần tạo lại. Đó là lúc tôi quyết định tự động hóa toàn bộ quy trình làm việc này.
Hãy để tôi chỉ cho bạn cách tôi tự động tạo hình ảnh hero và OG nhất quán, chuyên nghiệp.
Vấn đề với việc tạo hình ảnh thủ công
Việc tạo hình ảnh thủ công cho mỗi bài đăng có nghĩa là bạn liên tục chuyển đổi ngữ cảnh giữa viết và thiết kế. Các tab Figma của tôi nhân lên. Tính nhất quán của blog của tôi bị ảnh hưởng. Các bài đăng khác nhau có phông chữ, màu sắc và bố cục hơi khác nhau vì tôi luôn "chỉ chỉnh sửa" mọi thứ.
Tệ hơn nữa, tôi cứ trì hoãn việc tạo hình ảnh. Thư mục bản nháp của tôi ngày càng lớn. Tôi đã xuất bản các bài viết mà không có hình ảnh chỉ vì tôi không muốn mở Figma nữa.
Tôi có đề cập rằng hình ảnh OG cần hoạt động ở kích thước 1200x630 pixel không? Đó là một tỷ lệ khung hình cụ thể rất khó để ước lượng bằng mắt thường.
Giải pháp tự động của tôi
Tôi đã xây dựng một hệ thống sử dụng các tuyến API của Next.js, Puppeteer và một hình ảnh mẫu. Bây giờ, khi tôi cần một hình ảnh, tôi gọi một URL với tiêu đề bài đăng và các tham số khác của mình. Máy chủ hiển thị một trang HTML đầy đủ, chụp nó dưới dạng hình ảnh và trả về PNG.
Không Figma. Không làm việc thủ công. Chỉ là các lệnh gọi hàm.
Đây là điểm cuối cốt lõi mà tôi đã tạo:
// app/api/og/route.jsx
import puppeteer from 'puppeteer';
export async function GET(request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Untitled Post';
const category = searchParams.get('category') || 'Tech';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1200, height: 630 });
await page.setContent(renderTemplate({ title, category }));
const imageBuffer = await page.screenshot({ type: 'png' });
await browser.close();
return new Response(imageBuffer, {
headers: { 'Content-Type': 'image/png' },
});
}
Hàm renderTemplate xây dựng HTML. Tôi sử dụng CSS Grid và Flexbox để bố cục. Kiểu dáng khớp chính xác với hệ thống thiết kế của blog của tôi.
Puppeteer khởi chạy một trình duyệt Chrome thực trên máy chủ. Đảm bảo môi trường lưu trữ của bạn hỗ trợ điều này. Vercel, Railway và Render đều hoạt động tốt với cấu hình phù hợp.
Xây dựng mẫu HTML
Mẫu của tôi chỉ là HTML và CSS được tạo kiểu để trông giống như một thẻ được thiết kế. Đây là cấu trúc đơn giản hóa:
<div class="og-card">
<div class="category-badge">{category}</div>
<h1 class="title">{title}</h1>
<div class="footer">
<span class="date">{formattedDate}</span>
<span class="site">mysite.com</span>
</div>
</div>
CSS là nơi điều kỳ diệu xảy ra. Tôi sử dụng Google Fonts được nhúng dưới dạng base64 hoặc được tải qua @import. Tôi chỉ dùng tối đa một hoặc hai phông chữ. Càng nhiều thứ mẫu của bạn cần tải từ xa, việc tạo hình ảnh của bạn càng trở nên không ổn định.
.og-card {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #ffffff;
padding: 60px;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
box-sizing: border-box;
}
Tôi đã học được qua thử nghiệm đau đớn rằng các gradient hiển thị khác nhau trên các hệ thống. Bây giờ tôi luôn kiểm tra các mẫu của mình bằng cách tạo 10 hình ảnh và kiểm tra chúng trên các thiết bị khác nhau.
Làm cho nó có tính xác định
Đây là điều không ai nói cho bạn biết: ảnh chụp màn hình của Puppeteer có thể thay đổi một chút dựa trên thời gian hiển thị. Tôi đã thêm các điều kiện chờ rõ ràng để đảm bảo tính nhất quán:
await page.setContent(renderTemplate({ title, category }));
await page.evaluateHandle('document.fonts.ready'); // Wait for fonts
await page.waitForTimeout(500); // Buffer for rendering
const imageBuffer = await page.screenshot({ type: 'png' });
Promise document.fonts.ready đảm bảo các phông chữ web đã được tải trước khi chụp. Bộ đệm 500ms đó xử lý các hoạt ảnh chuyển tiếp nếu mẫu của bạn sử dụng chúng.
Tạo hình ảnh Hero
Hình ảnh OG rất đơn giản vì chúng có kích thước cố định 1200x630 pixel. Hình ảnh hero phức tạp hơn vì blog của tôi hiển thị chúng ở nhiều kích thước khác nhau. Tôi sử dụng cùng một hệ thống nhưng gọi nó với các kích thước khác nhau.
Cách tiếp cận của tôi: tạo hai hoặc ba kích thước tại thời điểm xây dựng và để trình duyệt chọn kích thước phù hợp thông qua srcset.
Nhiều kích thước có đáng để tăng thời gian xây dựng không? Đối với tôi, có. Độc giả trên thiết bị di động nhận được hình ảnh có kích thước phù hợp và điểm Lighthouse của tôi vẫn cao.
const sizes = [
{ width: 800, height: 450, name: 'small' },
{ width: 1600, height: 900, name: 'large' },
];
for (const size of sizes) {
await page.setViewport({ width: size.width, height: size.height });
// capture and save
}
Chiến lược lưu trữ
Tạo hình ảnh trên mỗi yêu cầu rất chậm. Tôi đã thêm một lớp lưu trữ đơn giản bằng cách sử dụng bộ nhớ đệm biên của nền tảng triển khai của tôi. URL hình ảnh bao gồm một hàm băm của nội dung, vì vậy các yêu cầu giống hệt nhau sẽ truy cập bộ nhớ đệm.
# Example: cached image URL
https://api.mysite.com/og?title=My%20Post&category=Tech&hash=abc123
Khi tôi cập nhật thiết kế mẫu của mình, tôi vô hiệu hóa bộ nhớ đệm bằng cách thay đổi tham số phiên bản. Các hình ảnh cũ sẽ được phục vụ cho đến khi chúng hết hạn, các hình ảnh mới sẽ được tạo với thiết kế mới.
Kết quả
Sau khi triển khai hệ thống này, tôi đã không mở Figma để tạo hình ảnh blog trong sáu tháng. Tần suất đăng bài của tôi tăng gấp đôi. Mọi hình ảnh đều khớp chính xác với thương hiệu của tôi vì tất cả chúng đều được tạo từ cùng một mẫu.
Thời gian xây dựng của tôi tăng thêm 8 giây cho mỗi bài đăng. Điều đó có thể chấp nhận được với thời gian tiết kiệm được.
Có nhược điểm nào không? Chắc chắn rồi. Nếu Puppeteer có lỗi hoặc mẫu của tôi có lỗi CSS, tất cả các hình ảnh bị ảnh hưởng sẽ bị hỏng đồng thời. Tôi giảm thiểu điều này bằng cách kiểm tra kỹ lưỡng các mẫu trước khi triển khai các bản cập nhật.
Bắt đầu
Bạn muốn xây dựng hệ thống của riêng mình? Đây là thiết lập tối thiểu khả thi:
- Tạo một dự án Next.js với một tuyến API để tạo hình ảnh
- Xây dựng một mẫu HTML tĩnh với các kiểu nội tuyến
- Thêm Puppeteer làm dependency
- Kiểm tra với tiêu đề và danh mục bài đăng thực tế của bạn
- Lưu trữ mạnh mẽ
Mẫu là phần quan trọng nhất. Dành thời gian để làm cho nó trông đẹp vì bạn sẽ sử dụng nó cho mọi bài đăng.
Kết thúc
Tôi đã tránh tự động hóa hình ảnh trong nhiều năm vì tôi nghĩ nó sẽ phức tạp. Đúng là vậy. Nhưng lợi ích thu được đã biện minh cho khoản đầu tư ban đầu. Bây giờ tôi viết bài báo, thêm frontmatter, và hình ảnh tự động tạo ra.
Nếu bạn dành hơn 10 phút cho mỗi bài đăng để tạo hình ảnh, bạn nên tự động hóa việc này. Tương lai của bạn sẽ cảm ơn bạn.
Bạn vẫn đang thực hiện thủ công những công việc nào? Có công việc nào trong số đó có thể tự động hóa không?
Puppeteer Next.js Automation BlogBạn cũng có thể thích
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Cách xây dựng Playwright Reporter tùy chỉnh cho Next.js Dashboards
Hướng dẫn từng bước về cách viết Playwright reporter JSON tùy chỉnh và truyền trực tuyến kết quả thực thi kiểm thử end-to-end theo thời gian thực đến dashboard Next.js với phân tích độ ổn định, hỗ trợ CI sharding và lưu trữ PostgreSQL.
Read more
Các lựa chọn thay thế Playwright hàng đầu năm 2026: So sánh Cypress, WebdriverIO, Vitest & Puppeteer
Hướng dẫn toàn diện về các lựa chọn thay thế Playwright hàng đầu năm 2026: so sánh Cypress, WebdriverIO, Vitest & Puppeteer với các ví dụ thực tế đã được kiểm chứng.
Read more
suppressHydrationWarning trong Next.js: Hướng dẫn sử dụng an toàn & gỡ lỗi đầy đủ
Hướng dẫn toàn diện về suppressHydrationWarning trong Next.js: sử dụng an toàn & gỡ lỗi đầy đủ với các ví dụ thực tế đã được kiểm chứng.
Read more