DevToolBox무료
블로그

Open Graph & Twitter Card 메타 태그: 완전한 개발자 참조

10분 읽기by DevToolBox

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" />

플랫폼별 이미지 요구사항

각 플랫폼마다 다른 이미지 크기 요구사항이 있습니다. 잘못된 크기는 잘림, 늘어남, 이미지 누락의 원인이 됩니다:

플랫폼권장 크기최소 크기최대 파일종횡비형식
Facebook1200 x 630600 x 3158 MB1.91:1JPG, PNG, GIF, WebP
Twitter (summary)144 x 144144 x 1445 MB1:1JPG, PNG, GIF, WebP
Twitter (large image)1200 x 675300 x 1575 MB2:1 / 16:9JPG, PNG, GIF, WebP
LinkedIn1200 x 627200 x 2005 MB1.91:1JPG, PNG
Discord1200 x 630256 x 2568 MB1.91:1JPG, PNG, GIF
WhatsApp1200 x 630300 x 2005 MB1.91:1JPG, PNG
Slack1200 x 630250 x 2505 MB1.91:1JPG, PNG, GIF
Telegram1200 x 630200 x 2005 MB1.91:1JPG, PNG
Pinterest1000 x 1500600 x 60010 MB2:3JPG, PNG
iMessage1200 x 630300 x 3005 MB1.91:1JPG, 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 }}" />

흔한 실수와 해결 방법

개발자들이 소셜 메타 태그에서 가장 자주 겪는 문제:

#실수문제해결책
1Relative image URLsog:image="/images/og.png" — platforms cannot resolve relative pathsAlways use absolute URLs: og:image="https://example.com/images/og.png"
2Missing image dimensionsFacebook may not display the image on first share without width/heightAlways include og:image:width and og:image:height tags
3HTTP instead of HTTPSMany platforms reject or warn about non-HTTPS image URLsServe all OG images over HTTPS. Use protocol-relative URLs as last resort
4Not handling cachePlatforms cache previews for hours/days; updating tags has no immediate effectUse platform debug tools to force refresh. Append ?v=2 for cache-busting
5Same tags on every pageEvery page shows the same preview card — confusing and poor CTRGenerate unique og:title, og:description, og:image per page
6Missing twitter:card tagTwitter will not render any card at all without this tagAlways include <meta name="twitter:card" content="summary_large_image" />
7Image too large (>8MB)Facebook silently fails; Twitter shows no imageOptimize images: compress to <1MB, use JPEG for photos, PNG for graphics
8Wrong image aspect ratioImage gets cropped awkwardly — text cut off, faces croppedUse 1200x630 (1.91:1) for OG, 1200x675 (16:9) for Twitter large image
9Duplicate og:image tagsUnpredictable behavior — platform may pick the wrong imageUse only one og:image tag per page (or intentionally provide multiple with primary first)
10Forgetting og:urlShares from different URLs (www vs non-www, query params) counted separatelySet og:url to the canonical URL to consolidate share counts

테스트 및 디버깅 도구

게시 전 메타 태그를 반드시 검증하세요. 각 플랫폼은 프리뷰를 적극적으로 캐시합니다.

도구플랫폼URL참고
Facebook Sharing DebuggerFacebook / Metadevelopers.facebook.com/tools/debug/Shows all OG tags, image preview, and errors. Click "Scrape Again" to refresh cache.
Twitter Card ValidatorTwitter / Xcards-dev.twitter.com/validatorPreview your Twitter Card. Validates tag structure. Requires Twitter login.
LinkedIn Post InspectorLinkedInlinkedin.com/post-inspector/Validates OG tags for LinkedIn. Click "Inspect" to refresh cached preview.
OpenGraph.xyzUniversalopengraph.xyzPreview how your URL appears on Facebook, Twitter, LinkedIn, Discord simultaneously.
Metatags.ioUniversalmetatags.ioReal-time preview editor. Edit tags and see Facebook/Twitter/Google previews instantly.
Social Share Preview (VS Code)DevelopmentVS Code ExtensionPreview OG tags directly in your IDE without deploying.
Ngrok / Cloudflare TunnelDevelopmentngrok.comExpose 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를 우선합니다.

메타 태그 생성기로 완벽한 OG & Twitter Card 태그 만들기 →

𝕏 Twitterin LinkedIn
도움이 되었나요?

최신 소식 받기

주간 개발 팁과 새 도구 알림을 받으세요.

스팸 없음. 언제든 구독 해지 가능.

Try These Related Tools

🏷️Meta Tag Generator👁️Open Graph Image Preview📐Schema Markup GeneratorFavicon Generator

Related Articles

Favicon 가이드 2026: 모든 크기, 형식, 생성 방법

2026년 완벽한 Favicon 참조. ICO vs PNG vs SVG, 모든 브라우저와 디바이스 크기.

모든 웹사이트에 필요한 메타 태그: HTML 메타 태그 완전 가이드

SEO, Open Graph, Twitter Cards, 보안, 성능을 위한 필수 HTML 메타 태그. 복사 붙여넣기 가능한 완전한 템플릿 포함.