Ba nguyên nhân chiếm phần lớn trường hợp:
1. URL ảnh là đường dẫn tương đối. Crawler mạng xã hội cần URL tuyệt đối. Khai báo metadataBase một lần ở root layout, Next.js sẽ tự nối tiền tố:
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'),
openGraph: { images: ['/og.png'] },
}Thiếu metadataBase, thẻ sinh ra là /og.png — crawler không phân giải được.
2. Trang render động, metadata sinh sau. Metadata phải nằm trong HTML server trả về. Đặt trong Client Component hay set bằng JavaScript sau khi mount thì crawler mạng xã hội không thấy — chúng không chạy JS. Dùng export const metadata hoặc generateMetadata trong Server Component.
3. Crawler đã cache bản cũ. Facebook/Zalo lưu lại kết quả lần đầu. Sau khi sửa phải dùng công cụ debug của nền tảng để bắt nó quét lại.
Ngoài ra kiểm tra: ảnh phải công khai (không nằm sau auth hay preview protection), tỉ lệ khuyến nghị 1200x630, và nếu dùng opengraph-image.tsx để sinh ảnh động thì thử mở thẳng URL ảnh xem có trả về 200 không.