๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
FrontEnd

[Web] OG ํƒœ๊ทธ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•˜๊ธฐ

by ํ‘ธ๊ณ ๋ฐฐ 2025. 7. 24.

๐Ÿ” ํ˜„์žฌ ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ

ํ˜„์žฌ OG ํƒœ๊ทธ๋ฅผ ์ ์šฉํ•  ํ”„๋กœ์ ํŠธ๋Š” Preact๋ฅผ ์‚ฌ์šฉํ•œ ์ „ํ˜•์ ์ธ **CSR ๊ธฐ๋ฐ˜์˜ SPA(Single Page Application)**์ด๋‹ค. ์„œ๋ฒ„๋Š” API์™€ ์ •์  ํŒŒ์ผ ์„œ๋น™๋งŒ ๋‹ด๋‹นํ•˜๊ณ , ์‹ค์ œ UI ๋ Œ๋”๋ง์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ JavaScript๋กœ ์ฒ˜๋ฆฌํ•œ๋‹ค.

๐Ÿ’กOG ํƒœ๊ทธ๋ž€?

OG(Open Graph) ํƒœ๊ทธ๋Š” ์›น ํŽ˜์ด์ง€๋ฅผ ์†Œ์…œ ๋ฏธ๋””์–ด(์˜ˆ: ์นด์นด์˜คํ†ก, ํŽ˜์ด์Šค๋ถ, ํŠธ์œ„ํ„ฐ ๋“ฑ)์—์„œ ๊ณต์œ ํ•  ๋•Œ ๋งํฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์˜ ์ œ๋ชฉ, ์„ค๋ช…, ์ด๋ฏธ์ง€๋“ฑ์„ ์ œ์–ดํ•˜๋Š” ๋ฉ”ํƒ€ํƒœ๊ทธ์ด๋‹ค.

OG ํƒœ๊ทธ๊ฐ€ ์ ์šฉ๋˜๋Š” ํ๋ฆ„

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์›น ๋งํฌ๋ฅผ ๊ณต์œ 
  2. ํ”Œ๋žซํผ(์นด์นด์˜ค, ํŽ˜๋ถ ๋“ฑ)์ด ํ•ด๋‹น URL์— ์š”์ฒญ
  3. ์„œ๋ฒ„๋Š” HTML ๋ฌธ์„œ ์‘๋‹ต
  4. <head>์˜ OG ํƒœ๊ทธ๋ฅผ ํŒŒ์‹ฑ
  5. ๊ทธ ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งํฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์ƒ์„ฑ

๐Ÿ‘‰ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋™์ ์œผ๋กœ ์‚ฝ์ž…ํ•œ OG๋Š” ๋ฌด์‹œ๋จ

→ ์ด์œ ๋Š” ํฌ๋กค๋Ÿฌ๋Š” JS ์‹คํ–‰์„ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ

โš™๏ธ CSR์—์„œ ๋™์  OGํƒœ๊ทธ ์ด์šฉํ•˜๊ธฐ

CSR ๋ฐฉ์‹(Preact, React, Vue ๋“ฑ)์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋งˆ์šดํŠธ๋˜๊ณ  ๋‚˜์„œ์•ผ JavaScript์— ์˜ํ•ด DOM์ด ๋ณ€๊ฒฝ๋˜๋ฏ€๋กœ OG ํƒœ๊ทธ๊ฐ€ ๋Šฆ๊ฒŒ ์‚ฝ์ž…๋œ๋‹ค.

๐Ÿ‘‰ ์นด์นด์˜ค/ํŽ˜๋ถ/ํŠธ์œ„ํ„ฐ ๊ฐ™์€ ํฌ๋กค๋Ÿฌ(๋ด‡)๋Š” OG ํƒœ๊ทธ๋ฅผ ์ธ์‹ํ•˜์ง€ ๋ชปํ•จ.

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

  1. SSR๋กœ OG ํƒœ๊ทธ๊นŒ์ง€ ํฌํ•จ๋œ HTML์„ ๋ฐ”๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ
  2. ์„œ๋ฒ„์—์„œ <head>๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•ด OG ํƒœ๊ทธ๋ฅผ ์‚ฝ์ž…ํ•ด์„œ ๋ฐ˜ํ™˜

ํ”„๋กœ์ ํŠธ๋ฅผ CSR์—์„œ SSR๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ์—๋Š” ๊ณต์ˆ˜๊ฐ€ ํฌ๊ธฐ ๋•Œ๋ฌธ์— 2๋ฒˆ ๋ฐฉ๋ฒ•์„ ์ด์šฉํ•ด ํ•ด๊ฒฐ์„ ์‹œ๋„ํ•ด๋ณด์•˜๋‹ค.

๐Ÿ› ๏ธ์„œ๋ฒ„์—์„œ <head>๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•ด ๋™์  OG ํƒœ๊ทธ ์ƒ์„ฑํ•˜๊ธฐ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ์š”์•ฝ

  • OG ํƒœ๊ทธ์— ๋“ค์–ด๊ฐˆ ์ •๋ณด๋ฅผ API(๋น„๋™๊ธฐ)๋กœ ์กฐํšŒ
  • ์ „๋‹ฌ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋กœ OG ํƒœ๊ทธ ๋ฌธ์ž์—ด์„ ์ƒ์„ฑ
  • ์ •์ ์ธ HTML ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ(index.html)์„ ์ฝ์Œ
  • <head> ํƒœ๊ทธ ์•ˆ์— ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•œ OG ํƒœ๊ทธ ๋ฌธ์ž์—ด์„ ์‚ฝ์ž…
  • ์‚ฝ์ž…๋œ HTML์„ ctx.body๋กœ ์‘๋‹ตํ•˜์—ฌ ์„œ๋ฒ„์—์„œ OG ๋ฉ”ํƒ€ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ HTML์„ ์ œ๊ณต

์ฝ”๋“œ๋กœ ์„ค๋ช…

1. OG ์ •๋ณด ๋กœ๋”ฉ

  • ์™ธ๋ถ€ API ๋˜๋Š” ๋‚ด๋ถ€ DB์—์„œ OG ์ •๋ณด๋ฅผ ๋น„๋™๊ธฐ๋กœ ๋ถˆ๋Ÿฌ์˜ด
  • ์‹คํŒจ ์‹œ ๊ธฐ๋ณธ OG ์ •๋ณด(defaultTitle, defaultImage) ๋ฐ˜ํ™˜
loadOgInformation: async code => {
  const {title, imgUrl, description} = await getOgTagTittleAndImage(code);
  return {title, imgUrl, description};
}

2. OG ํƒœ๊ทธ ๋ฌธ์ž์—ด ์ƒ์„ฑ

  • ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋กœ OG ํƒœ๊ทธ ๋ฌธ์ž์—ด์„ ์ƒ์„ฑ
createOgMetaTags: ({title, imgUrl, description}) => `
  <meta property="og:title" content="${title}" />
  <meta property="og:image" content="${imgUrl}" />
  <meta property="og:description" content="${description}" />
`,

3. HTML์— OG ํƒœ๊ทธ ์‚ฝ์ž…

  • <head> ํƒœ๊ทธ๋ฅผ ์ฐพ์•„ OG ํƒœ๊ทธ๋ฅผ ์‚ฝ์ž…
injectOgTags: (html, ogTags) => html.replace(/<head[^>]*>/i, match => `${match}\\n${ogTags.trim()}`)

์ „์ฒด์ฝ”๋“œ

  • index.html์€ ๋ฏธ๋ฆฌ ๋ Œ๋”๋œ ํ…œํ”Œ๋ฆฟ HTML ํŒŒ์ผ๋กœ, OG ํƒœ๊ทธ๊ฐ€ ์‚ฝ์ž…๋  ๋ฒ ์ด์Šค ํŒŒ์ผ
  • ํ•„์š”ํ•œ OG ์ •๋ณด(title, image)๋ฅผ ๋ถˆ๋Ÿฌ์™€ <head>์— ์‚ฝ์ž… ํ›„ ํด๋ผ์ด์–ธํŠธ์— ์ „๋‹ฌ
// OG Tag Util
const ogTagUtils = {
  loadOgInformation: async code => {
    try {
      const {title, imgUrl, description} = await getOgTagTittleAndImage(code);
      return {title, imgUrl, description};
    } catch (error) {
      console.error('[ERROR]', error.message);
      return {
        title: config.og.defaultTitle,
        imgUrl: config.og.defaultImage,
        description: config.og.defaultDescription
      };
    }
  },

  createOgMetaTags: ({title, imgUrl, description}) => `
    <meta property="og:title" content="${title}" />
    <meta property="og:image" content="${imgUrl}" />
    <meta property="og:description" content="${description}" />
  `,

  injectOgTags: (html, ogTags) => html.replace(/<head[^>]*>/i, match => `${match}\\n${ogTags.trim()}`),
};
// Server Code
try {
  const { title: ogTitle, imgUrl: ogImageUrl, description: ogDescription } = await ogTagUtils.loadOgInformation(code);

  const ogMetaTags = ogTagUtils.createOgMetaTags({
    title: ogTitle,
    imgUrl: ogImageUrl,
    description: ogDescription
  });

  const bridgeHtmlPath = `${config.static.distDir}/bridge.html`;
  const bridgeHtml = await readFilePromise(bridgeHtmlPath, 'utf8');

  const finalHtml = ogTagUtils.injectOgTags(bridgeHtml, ogMetaTags);

  ctx.body = finalHtml;
} catch (error) {
  console.error('[ERROR] OG ์ •๋ณด ์กฐํšŒ ์‹คํŒจ:', error);
  ctx.status = 500;
}

์œ ์˜ํ•  ์ 

  1. OG ํƒœ๊ทธ๋Š” ๋ฐ˜๋“œ์‹œ SSR ํ˜น์€ HTML ์กฐ์ž‘์œผ๋กœ ์ œ๊ณต๋˜์–ด์•ผ ํฌ๋กค๋Ÿฌ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ธ์‹ ๊ฐ€๋Šฅ
  2. index.html์€ ์ตœ์†Œํ•œ์˜ HTML ๊ตฌ์กฐ (<!DOCTYPE html>, <html>, <head>, <body>)๋ฅผ ํฌํ•จํ•ด์•ผ ํ•จ
  3. <head> ํƒœ๊ทธ๋Š” ๋ฐ˜๋“œ์‹œ ์กด์žฌํ•ด์•ผ ํ•˜๋ฉฐ, ์ •๊ทœํ‘œํ˜„์‹์œผ๋กœ ์น˜ํ™˜๋˜๋ฏ€๋กœ ์˜๋„์น˜ ์•Š์€ HTML ๊ตฌ์กฐ์— ์ฃผ์˜
  4. OG ์ •๋ณด ๋กœ๋”ฉ ์‹œ ์บ์‹ฑ ์ „๋žต ๊ณ ๋ ค ํ•„์š” (์˜ˆ: ๋™์ผ ์ฝ”๋“œ ์š”์ฒญ์— ๋Œ€ํ•ด ๋ฐ˜๋ณต ์กฐํšŒ ๋ฐฉ์ง€)

ํ™•์žฅ ์•„์ด๋””์–ด

  • url, type ๋“ฑ์˜ ํƒœ๊ทธ๋„ ํ•จ๊ป˜ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€
  • user-agent ๊ฒ€์‚ฌ๋กœ ๋ด‡ ์š”์ฒญ ์‹œ์—๋งŒ OG ์‚ฝ์ž… ์ฒ˜๋ฆฌ
  • Redis ๋“ฑ์œผ๋กœ OG ๋ฐ์ดํ„ฐ ์บ์‹ฑ

๋Œ“๊ธ€