Facebook, Twitter, LinkedIn, Discord, Slack에서 링크를 공유할 때 나타나는 리치 프리뷰 카드는 Open Graph (OG) 메타 태그와 Twitter Card 태그로 구동됩니다. 올바르게 설정하면 더 많은 클릭과 참여를 얻을 수 있습니다. 이 완전한 참조 가이드는 모든 태그, 플랫폼 요구사항, 일반적인 실수를 다룹니다.
무료 메타 태그 생성기로 완벽한 메타 태그를 즉시 생성하세요 →
필수 Open Graph 태그
Open Graph 프로토콜은 2010년 Facebook에서 만들었으며, 현재 거의 모든 소셜 플랫폼에서 채택하고 있습니다.
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
소셜 공유 카드에 표시되는 이미지. 가장 영향력 있는 태그입니다. 절대 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)는 4가지 카드 유형을 지원합니다. 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를 제공합니다:
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 등에서 사용되는 범용 표준입니다. Twitter Card는 Twitter/X의 독자 시스템입니다. Twitter는 전용 태그가 없으면 OG 태그로 폴백하지만, 둘 다 있으면 모든 플랫폼에서 최적 표시됩니다.
Open Graph와 Twitter Card 태그 둘 다 필요한가요?
기술적으로는 아닙니다. Twitter는 OG 태그를 폴백으로 읽습니다. 하지만 twitter:card는 OG 동등물이 없고 Twitter에서 카드 표시에 필수이므로, 둘 다 포함하는 것이 모범 사례입니다.
링크 공유 시 OG 이미지가 표시되지 않는 이유는?
가장 흔한 원인: 상대 URL 사용, 이미지 URL이 404 반환, 파일 크기 초과, 플랫폼이 이전 버전 캐시, og:image:width와 og:image:height 누락.
이상적인 OG 이미지 크기는?
권장 크기는 1200 x 630 픽셀, 종횡비 1.91:1입니다. Facebook, LinkedIn, Discord 등에서 잘 표시됩니다.
플랫폼에서 OG 프리뷰 새로고침을 강제하려면?
각 플랫폼에 캐시 삭제 도구가 있습니다: Facebook Sharing Debugger, Twitter Card Validator, LinkedIn Post Inspector.
Facebook과 Twitter에 다른 이미지를 사용할 수 있나요?
네. og:image로 Facebook 이미지, twitter:image로 Twitter 이미지를 설정할 수 있습니다. 둘 다 있으면 Twitter는 twitter:image를 우선합니다.