Mengapa Memilih Render HTML ke PNG untuk Social Card?
Membuat Open Graph (OG) image secara dinamis sering diselesaikan lewat Canvas API atau library manipulasi gambar seperti sharp. Masalahnya, rendering teks yang kompleks, flexbox, grid, dan styling modern jauh lebih mudah dirancang langsung menggunakan HTML dan CSS. Menggunakan Playwright headless browser memungkinkan tim developer mendesain template visual persis seperti halaman web standar tanpa keterbatasan API grafis tingkat rendah.
Namun, mengeksekusi render berbasis browser di dalam Continuous Integration (CI) memiliki tantangan tersendiri: overhead alokasi memori instance browser, ketidakkonsistenan font rendering lintas sistem operasi, dan layout shift saat rendering terjadi sebelum aset selesai dimuat. Artikel ini menguraikan arsitektur implementasi deterministik berbasis Playwright CLI dan Node.js.
1. Arsitektur Layout CSS Deterministik
Agar hasil screenshot identik di setiap eksekusi CI, hilangkan seluruh faktor nondeterministik dalam CSS. Dimensi viewport harus di-lock ke standar Open Graph (1200x630 piksel), animasi harus dimatikan total, dan layout harus rigid.
/* template.css */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
/* Nonaktifkan transisi dan animasi agar snapshot tidak blur */
transition: none !important;
animation: none !important;
}
body {
width: 1200px;
height: 630px;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 64px;
background: #0f172a;
color: #f8fafc;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.badge {
font-size: 20px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #38bdf8;
}
.title {
font-size: 56px;
font-weight: 700;
line-height: 1.15;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.meta {
font-size: 24px;
color: #94a3b8;
}2. Batch Renderer: Browser Context Reuse
Memanggil perintah CLI playwright screenshot per file secara berulang akan memulai ulang proses browser baru untuk setiap gambar. Pendekatan ini boros CPU dan memori. Solusi yang benar adalah membuat script runner CLI yang memanfaatkan satu instance browser dan mendaur ulang halaman via browserContext.
// generate-cards.mjs
import { chromium } from 'playwright';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const items = [
{ id: 'post-1', title: 'Panduan Event-Driven Architecture di Node.js', date: '2026-03-01' },
{ id: 'post-2', title: 'Otomasi Social Card CI: Render HTML ke PNG via Playwright CLI', date: '2026-03-02' },
];
const templateHtml = readFileSync('./template.html', 'utf-8');
async function run() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1200, height: 630 },
deviceScaleFactor: 2, // Menghasilkan PNG 2400x1260 untuk layar Retina/HiDPI
});
const page = await context.newPage();
for (const item of items) {
const renderedHtml = templateHtml
.replace('{{TITLE}}', item.title)
.replace('{{DATE}}', item.date);
await page.setContent(renderedHtml, {
waitUntil: 'networkidle' // Pastikan font dan asset eksternal selesai diambil
});
// Verifikasi font siap pakai sebelum capture
await page.evaluate(() => document.fonts.ready);
const outputPath = resolve(`./dist/og-${item.id}.png`);
await page.screenshot({ path: outputPath, type: 'png' });
console.log(`Saved: ${outputPath}`);
}
await browser.close();
}
run().catch((err) => {
console.error(err);
process.exit(1);
});3. Mengatasi Font Mismatch di Linux Runner
Masalah paling umum saat render di CI (misalnya Ubuntu runner pada GitHub Actions) adalah font fallback ke font bitmap standar seperti DejaVu Sans karena font seperti Inter atau Roboto belum terinstal. Hal ini membuat tata letak teks bergeser (text overflow).
Solusi 1: Self-host Font via Base64
Inject font langsung ke dalam CSS template menggunakan Base64 encoding. Pendekatan ini menghilangkan network roundtrip dan dependensi font sistem operasi.
@font-face {
font-family: 'Inter';
src: url(data:font/woff2;base64,d09GMgABAAAA...) format('woff2');
font-weight: 400 700;
font-display: block;
}Solusi 2: Instalasi Font di Level OS Runner
Jika ukuran Base64 terlalu besar untuk di-embed, pasang font package sebelum script dieksekusi: sudo apt-get install -y fonts-inter atau unduh langsung file .ttf ke ~/.local/share/fonts/ lalu refresh cache dengan fc-cache -f -v.
4. Integrasi Workflow GitHub Actions
Integrasikan script generation ke dalam pipeline GitHub Actions. Kita menggunakan cache browser binary untuk mempercepat proses eksekusi.
name: Generate Social Cards
on:
push:
branches: [main]
paths:
- 'content/**'
- 'template.html'
jobs:
build-og-images:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Install Playwright Chromium Only
# Unduh hanya browser Chromium untuk menghemat bandwidth runner CI
run: npx playwright install --with-deps chromium
- name: Generate PNGs
run: node generate-cards.mjs
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: og-images
path: dist/*.pngTrade-offs dan Kesimpulan
Membangun social card dengan Playwright headless memberikan kontrol styling absolut setara web development modern. Trade-off utamanya adalah resource footprint yang lebih berat dibanding tool berbasis C/Rust native (seperti resvg atau Satori). Namun, jika kebutuhan visual Anda memerlukan CSS modern seperti clamp(), complex flexbox/grid, dan line-clamping multi-line yang akurat tanpa kompromi, Playwright CI pipeline adalah solusi paling deterministik dan mudah dipelihara.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!