当你在 Facebook、Twitter、LinkedIn、Discord 或 Slack 上分享链接时,出现的富媒体预览卡片是由 Open Graph (OG) 元标签和 Twitter Card 标签驱动的。正确设置这些标签意味着更多点击、更高互动和更专业的外观。设置错误则意味着预览损坏、图片缺失和流量流失。本完整参考指南涵盖了每个标签、每个平台要求和每个常见错误——并附带所有主流框架的可复制代码。
必备 Open Graph 标签
Open Graph 协议由 Facebook 于 2010 年创建,此后几乎被所有社交平台采用。这四个标签是任何页面的最低要求:
og:title
页面在社交分享卡片中显示的标题。保持在 60 个字符以内以获得最佳显示效果。
<meta property="og:title" content="How to Build a REST API with Node.js in 2026" />og:description
预览卡片中标题下方显示的页面内容简介。目标 120-160 个字符。
<meta property="og:description" content="Step-by-step guide to building a production-ready REST API with Node.js, Express, and TypeScript. Includes auth, validation, and deployment." />og:image
社交分享卡片中显示的图片。这是影响最大的标签——带图片的帖子获得 2-3 倍的互动。必须是绝对 URL。
<meta property="og:image" content="https://example.com/images/rest-api-guide-og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="REST API with Node.js — Complete Guide" />
<meta property="og:image:type" content="image/png" />og:url
页面的规范 URL。告诉平台将分享内容与哪个 URL 关联。
<meta property="og:url" content="https://example.com/blog/rest-api-nodejs" />og:type
内容类型。常用值:website(默认)、article(博客文章)、product、profile。
<!-- For a general page -->
<meta property="og:type" content="website" />
<!-- For a blog post / article -->
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2026-02-10T08:00:00Z" />
<meta property="article:author" content="https://example.com/about" />
<meta property="article:section" content="Technology" />
<meta property="article:tag" content="Node.js" />
<meta property="article:tag" content="REST API" />其他 Open Graph 标签
除了基础标签外,这些标签让你对内容显示有更精细的控制:
<!-- Site name — shown above the title on Facebook -->
<meta property="og:site_name" content="DevToolBox" />
<!-- Locale — language_TERRITORY format -->
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="fr_FR" />
<meta property="og:locale:alternate" content="de_DE" />
<meta property="og:locale:alternate" content="es_ES" />
<meta property="og:locale:alternate" content="ja_JP" />
<!-- Video (for og:type = video.*) -->
<meta property="og:video" content="https://example.com/video.mp4" />
<meta property="og:video:width" content="1280" />
<meta property="og:video:height" content="720" />
<meta property="og:video:type" content="video/mp4" />
<!-- Audio -->
<meta property="og:audio" content="https://example.com/podcast-ep1.mp3" />
<meta property="og:audio:type" content="audio/mpeg" />Twitter Card 标签
Twitter(现为 X)支持自己的卡片系统,有四种卡片类型。如果缺少 Twitter 专属标签,Twitter 会回退使用 Open Graph 标签。
Twitter Card 类型
| 卡片类型 | 描述 | 使用场景 |
|---|---|---|
| summary | 小方形图片配标题和描述 | 博客文章、一般页面 |
| summary_large_image | 标题和描述上方的大图 | 视觉内容、特色文章 |
| player | 推文中嵌入的视频/音频播放器 | 视频托管、播客 |
| app | 移动应用直接下载卡片 | 应用商店推广 |
<!-- Summary Card (small image) -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@yourusername" />
<meta name="twitter:creator" content="@authorname" />
<meta name="twitter:title" content="How to Build a REST API with Node.js" />
<meta name="twitter:description" content="Complete guide with auth, validation, and deployment." />
<meta name="twitter:image" content="https://example.com/images/rest-api-square.png" />
<meta name="twitter:image:alt" content="REST API Guide illustration" />
<!-- Summary Card with Large Image -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@yourusername" />
<meta name="twitter:title" content="How to Build a REST API with Node.js" />
<meta name="twitter:description" content="Complete guide with auth, validation, and deployment." />
<meta name="twitter:image" content="https://example.com/images/rest-api-wide.png" />
<!-- Player Card (video/audio) -->
<meta name="twitter:card" content="player" />
<meta name="twitter:player" content="https://example.com/embed/video123" />
<meta name="twitter:player:width" content="1280" />
<meta name="twitter:player:height" content="720" />
<!-- App Card -->
<meta name="twitter:card" content="app" />
<meta name="twitter:app:id:iphone" content="123456789" />
<meta name="twitter:app:id:googleplay" content="com.example.app" />
<meta name="twitter:app:name:iphone" content="My App" />
<meta name="twitter:app:name:googleplay" content="My App" />各平台图片要求
每个平台有不同的图片尺寸要求。使用错误的尺寸会导致图片被裁剪、拉伸或缺失:
| 平台 | 推荐尺寸 | 最小尺寸 | 最大文件 | 宽高比 | 格式 |
|---|---|---|---|---|---|
| 1200 x 630 | 600 x 315 | 8 MB | 1.91:1 | JPG, PNG, GIF, WebP | |
| Twitter (summary) | 144 x 144 | 144 x 144 | 5 MB | 1:1 | JPG, PNG, GIF, WebP |
| Twitter (large image) | 1200 x 675 | 300 x 157 | 5 MB | 2:1 / 16:9 | JPG, PNG, GIF, WebP |
| 1200 x 627 | 200 x 200 | 5 MB | 1.91:1 | JPG, PNG | |
| Discord | 1200 x 630 | 256 x 256 | 8 MB | 1.91:1 | JPG, PNG, GIF |
| 1200 x 630 | 300 x 200 | 5 MB | 1.91:1 | JPG, PNG | |
| Slack | 1200 x 630 | 250 x 250 | 5 MB | 1.91:1 | JPG, PNG, GIF |
| Telegram | 1200 x 630 | 200 x 200 | 5 MB | 1.91:1 | JPG, PNG |
| 1000 x 1500 | 600 x 600 | 10 MB | 2:3 | JPG, PNG | |
| iMessage | 1200 x 630 | 300 x 300 | 5 MB | 1.91:1 | JPG, PNG |
完整 HTML 标记示例
这是一个包含所有 Open Graph 和 Twitter Card 标签的生产就绪 HTML head。复制并替换占位值:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Primary Meta Tags -->
<title>How to Build a REST API with Node.js in 2026</title>
<meta name="description" content="Step-by-step guide to building a production-ready REST API with Node.js, Express, and TypeScript." />
<link rel="canonical" href="https://example.com/blog/rest-api-nodejs" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="article" />
<meta property="og:url" content="https://example.com/blog/rest-api-nodejs" />
<meta property="og:title" content="How to Build a REST API with Node.js in 2026" />
<meta property="og:description" content="Step-by-step guide to building a production-ready REST API with Node.js, Express, and TypeScript." />
<meta property="og:image" content="https://example.com/images/rest-api-og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="REST API with Node.js guide banner" />
<meta property="og:site_name" content="My Dev Blog" />
<meta property="og:locale" content="en_US" />
<!-- Article-specific OG tags -->
<meta property="article:published_time" content="2026-02-10T08:00:00Z" />
<meta property="article:modified_time" content="2026-02-10T12:00:00Z" />
<meta property="article:author" content="https://example.com/about" />
<meta property="article:section" content="Web Development" />
<meta property="article:tag" content="Node.js" />
<meta property="article:tag" content="REST API" />
<meta property="article:tag" content="TypeScript" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@yourusername" />
<meta name="twitter:creator" content="@authorname" />
<meta name="twitter:title" content="How to Build a REST API with Node.js in 2026" />
<meta name="twitter:description" content="Step-by-step guide to building a production-ready REST API." />
<meta name="twitter:image" content="https://example.com/images/rest-api-twitter.png" />
<meta name="twitter:image:alt" content="REST API with Node.js guide banner" />
</head>
<body>
<!-- Page content -->
</body>
</html>框架专属示例
现代框架提供了管理元标签的内置 API。以下是在最流行框架中实现 OG 和 Twitter Card 标签的方法:
Next.js(App Router — Metadata API)
// app/blog/[slug]/page.tsx
import { Metadata } from 'next';
type Props = { params: { slug: string } };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
alternates: {
canonical: `https://example.com/blog/${params.slug}`,
},
openGraph: {
type: 'article',
title: post.title,
description: post.excerpt,
url: `https://example.com/blog/${params.slug}`,
siteName: 'My Dev Blog',
locale: 'en_US',
images: [
{
url: post.ogImage, // Must be absolute URL
width: 1200,
height: 630,
alt: post.title,
},
],
publishedTime: post.date,
authors: [post.author],
tags: post.tags,
},
twitter: {
card: 'summary_large_image',
site: '@yourusername',
creator: '@authorname',
title: post.title,
description: post.excerpt,
images: [
{
url: post.twitterImage, // Can differ from OG image
alt: post.title,
},
],
},
};
}
export default function BlogPost({ params }: Props) {
// Page component...
}Nuxt 3(useSeoMeta)
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`);
useSeoMeta({
title: post.value.title,
description: post.value.excerpt,
ogType: 'article',
ogTitle: post.value.title,
ogDescription: post.value.excerpt,
ogUrl: `https://example.com/blog/${route.params.slug}`,
ogImage: post.value.ogImage,
ogImageWidth: 1200,
ogImageHeight: 630,
ogImageAlt: post.value.title,
ogSiteName: 'My Dev Blog',
ogLocale: 'en_US',
articlePublishedTime: post.value.date,
articleAuthor: post.value.author,
articleTag: post.value.tags,
twitterCard: 'summary_large_image',
twitterSite: '@yourusername',
twitterCreator: '@authorname',
twitterTitle: post.value.title,
twitterDescription: post.value.excerpt,
twitterImage: post.value.twitterImage,
twitterImageAlt: post.value.title,
});
</script>
<template>
<article>
<!-- Page content -->
</article>
</template>静态 HTML
<!-- For static HTML sites, simply add meta tags in <head> -->
<!-- You can automate this with a build tool or templating engine -->
<!-- Example with Eleventy (11ty) Nunjucks template -->
<head>
<title>{{ title }}</title>
<meta name="description" content="{{ description }}" />
<meta property="og:type" content="{% if layout == 'post' %}article{% else %}website{% endif %}" />
<meta property="og:title" content="{{ title }}" />
<meta property="og:description" content="{{ description }}" />
<meta property="og:image" content="{{ site.url }}{{ ogImage }}" />
<meta property="og:url" content="{{ site.url }}{{ page.url }}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ title }}" />
<meta name="twitter:description" content="{{ description }}" />
<meta name="twitter:image" content="{{ site.url }}{{ ogImage }}" />
</head>
<!-- Example with Hugo -->
<!-- layouts/partials/head.html -->
<meta property="og:title" content="{{ .Title }}" />
<meta property="og:description" content="{{ .Description }}" />
<meta property="og:image" content="{{ .Params.ogImage | absURL }}" />
<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{{ .Title }}" />
<meta name="twitter:image" content="{{ .Params.ogImage | absURL }}" />常见错误及修复方法
这些是开发者在社交元标签中最常遇到的问题。每一个都可能破坏你的分享预览:
| # | 错误 | 问题 | 解决方案 |
|---|---|---|---|
| 1 | Relative image URLs | og:image="/images/og.png" — platforms cannot resolve relative paths | Always use absolute URLs: og:image="https://example.com/images/og.png" |
| 2 | Missing image dimensions | Facebook may not display the image on first share without width/height | Always include og:image:width and og:image:height tags |
| 3 | HTTP instead of HTTPS | Many platforms reject or warn about non-HTTPS image URLs | Serve all OG images over HTTPS. Use protocol-relative URLs as last resort |
| 4 | Not handling cache | Platforms cache previews for hours/days; updating tags has no immediate effect | Use platform debug tools to force refresh. Append ?v=2 for cache-busting |
| 5 | Same tags on every page | Every page shows the same preview card — confusing and poor CTR | Generate unique og:title, og:description, og:image per page |
| 6 | Missing twitter:card tag | Twitter will not render any card at all without this tag | Always include <meta name="twitter:card" content="summary_large_image" /> |
| 7 | Image too large (>8MB) | Facebook silently fails; Twitter shows no image | Optimize images: compress to <1MB, use JPEG for photos, PNG for graphics |
| 8 | Wrong image aspect ratio | Image gets cropped awkwardly — text cut off, faces cropped | Use 1200x630 (1.91:1) for OG, 1200x675 (16:9) for Twitter large image |
| 9 | Duplicate og:image tags | Unpredictable behavior — platform may pick the wrong image | Use only one og:image tag per page (or intentionally provide multiple with primary first) |
| 10 | Forgetting og:url | Shares from different URLs (www vs non-www, query params) counted separately | Set og:url to the canonical URL to consolidate share counts |
测试与调试工具
发布前务必验证你的元标签。每个平台都会积极缓存预览,修复标签后需要等待缓存过期或手动清除。
| 工具 | 平台 | URL | 说明 |
|---|---|---|---|
| Facebook Sharing Debugger | Facebook / Meta | developers.facebook.com/tools/debug/ | Shows all OG tags, image preview, and errors. Click "Scrape Again" to refresh cache. |
| Twitter Card Validator | Twitter / X | cards-dev.twitter.com/validator | Preview your Twitter Card. Validates tag structure. Requires Twitter login. |
| LinkedIn Post Inspector | linkedin.com/post-inspector/ | Validates OG tags for LinkedIn. Click "Inspect" to refresh cached preview. | |
| OpenGraph.xyz | Universal | opengraph.xyz | Preview how your URL appears on Facebook, Twitter, LinkedIn, Discord simultaneously. |
| Metatags.io | Universal | metatags.io | Real-time preview editor. Edit tags and see Facebook/Twitter/Google previews instantly. |
| Social Share Preview (VS Code) | Development | VS Code Extension | Preview OG tags directly in your IDE without deploying. |
| Ngrok / Cloudflare Tunnel | Development | ngrok.com | Expose localhost to test with real platform validators during development. |
动态 OG 图片
为每个页面生成唯一的社交图片可以显著提高点击率。每个页面都有展示其标题或数据的自定义卡片,而不是整个网站使用一张通用图片。
为什么动态 OG 图片很重要
GitHub、Vercel 和 DEV.to 等网站为每个页面生成 OG 图片。结果:它们的链接在社交信息流中脱颖而出。
<!-- Instead of this (same image for every page): -->
<meta property="og:image" content="https://example.com/default-og.png" />
<!-- Generate this (unique per page): -->
<meta property="og:image" content="https://example.com/api/og?title=How+to+Build+a+REST+API" />使用 @vercel/og 生成
// app/api/og/route.tsx — Next.js Edge Route Handler
import { ImageResponse } from 'next/og';
import { NextRequest } from 'next/server';
export const runtime = 'edge';
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const title = searchParams.get('title') || 'Default Title';
const description = searchParams.get('desc') || '';
return new ImageResponse(
(
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
padding: '60px 80px',
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
fontFamily: 'Inter, sans-serif',
}}
>
<div style={{
fontSize: 56,
fontWeight: 800,
color: '#f8fafc',
lineHeight: 1.2,
marginBottom: 20,
}}>
{title}
</div>
{description && (
<div style={{
fontSize: 24,
color: '#94a3b8',
lineHeight: 1.5,
}}>
{description}
</div>
)}
<div style={{
display: 'flex',
alignItems: 'center',
marginTop: 'auto',
fontSize: 20,
color: '#64748b',
}}>
example.com
</div>
</div>
),
{
width: 1200,
height: 630,
}
);
}
// Usage in metadata:
// openGraph: { images: [`/api/og?title=${encodeURIComponent(post.title)}`] }使用 Node Canvas 生成
// scripts/generate-og-images.ts — Build-time generation
import { createCanvas, registerFont } from 'canvas';
import fs from 'fs';
import path from 'path';
registerFont('fonts/Inter-Bold.ttf', { family: 'Inter', weight: 'bold' });
interface OGConfig {
title: string;
slug: string;
theme?: string;
}
function generateOGImage(config: OGConfig): Buffer {
const { title, theme = '#0f172a' } = config;
const width = 1200;
const height = 630;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// Background gradient
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, theme);
gradient.addColorStop(1, '#1e293b');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Title text
ctx.fillStyle = '#f8fafc';
ctx.font = 'bold 52px Inter';
const words = title.split(' ');
let line = '';
let y = 240;
for (const word of words) {
const test = line + word + ' ';
if (ctx.measureText(test).width > 1040) {
ctx.fillText(line.trim(), 80, y);
line = word + ' ';
y += 70;
} else {
line = test;
}
}
ctx.fillText(line.trim(), 80, y);
// Site name
ctx.fillStyle = '#64748b';
ctx.font = 'bold 24px Inter';
ctx.fillText('example.com', 80, 560);
return canvas.toBuffer('image/png');
}
// Generate for all posts
const posts: OGConfig[] = [
{ title: 'How to Build a REST API with Node.js', slug: 'rest-api-nodejs' },
{ title: 'TypeScript Generics Explained', slug: 'typescript-generics' },
];
for (const post of posts) {
const buffer = generateOGImage(post);
const outPath = path.join('public', 'og', `${post.slug}.png`);
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, buffer);
console.log(`Generated: ${outPath}`);
}使用 Cloudinary URL 转换
// Using Cloudinary URL-based image transformations
// No server-side code needed — just construct the URL
function getCloudinaryOGImage(title: string): string {
const cloudName = 'your-cloud-name';
const baseImage = 'og-template.png'; // Upload a background template first
// URL-encode the title
const encodedTitle = encodeURIComponent(title);
// Cloudinary text overlay transformation
return [
`https://res.cloudinary.com/${cloudName}/image/upload`,
// Resize to OG dimensions
'w_1200,h_630,c_fill',
// Add text overlay
`l_text:Inter_52_bold:${encodedTitle}`,
'co_rgb:f8fafc', // Text color
'g_west', // Left-align
'x_80,y_-40', // Position
'w_1040,c_fit', // Max text width
// Base image
baseImage,
].join('/');
}
// Usage:
// <meta property="og:image"
// content={getCloudinaryOGImage('How to Build a REST API with Node.js')} />
// Result URL looks like:
// https://res.cloudinary.com/your-cloud-name/image/upload/
// w_1200,h_630,c_fill/
// l_text:Inter_52_bold:How%20to%20Build%20a%20REST%20API/
// co_rgb:f8fafc,g_west,x_80,y_-40,w_1040,c_fit/
// og-template.png常见问题
Open Graph 和 Twitter Card 标签有什么区别?
Open Graph 由 Facebook 创建,现在是 Facebook、LinkedIn、Discord、Slack、WhatsApp 等大多数平台使用的通用标准。Twitter Card 是 Twitter/X 的专有系统。Twitter 在没有专属标签时会回退使用 OG 标签,但两者都有可确保在所有平台上最佳显示。
我需要同时使用 Open Graph 和 Twitter Card 标签吗?
技术上不需要——Twitter 会读取 OG 标签作为后备。但最佳实践是两者都包含,因为 twitter:card 没有 OG 等价物且是 Twitter 显示卡片所必需的。
为什么分享链接时 OG 图片不显示?
最常见的原因:使用相对 URL 而非绝对 URL、图片 URL 返回 404、图片文件过大、平台缓存了旧版本、缺少 og:image:width 和 og:image:height 标签。
理想的 OG 图片尺寸是多少?
推荐尺寸为 1200 x 630 像素,宽高比 1.91:1。这在 Facebook、LinkedIn、Discord 等平台都表现良好。如果只能创建一张图片,1200 x 630 是最安全的通用选择。
如何强制平台刷新 OG 预览?
每个平台有自己的缓存清除工具:Facebook 使用 Sharing Debugger 点击"重新抓取",Twitter 使用 Card Validator,LinkedIn 使用 Post Inspector。
可以为 Facebook 和 Twitter 使用不同的图片吗?
可以。用 og:image 设置 Facebook 图片,用 twitter:image 设置 Twitter 图片。当两者都存在时 Twitter 优先使用 twitter:image。