๐Ÿ” 03. head & meta & SEO: ๊ตฌ๊ธ€์ด ๋‚ด ํŽ˜์ด์ง€๋ฅผ ์ดํ•ดํ•˜๋Š” ๋ฐฉ๋ฒ•

2026๋…„ 3์›” 5์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

meta charset, viewport, description, OG ํƒœ๊ทธ, favicon, Next.js Metadata API๊นŒ์ง€ โ€” head ์•ˆ์˜ ๋ชจ๋“  ๊ฒƒ์„ 5๋…„ ์ฐจ์˜ ์–ธ์–ด๋กœ ํ•ด์„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 20๋ถ„ / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 12๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„

[head์˜ ์—ญํ• ] โ†’ [ํ•„์ˆ˜ meta ํƒœ๊ทธ] โ†’ [SEO meta ํƒœ๊ทธ] โ†’ [OG/Twitter ์นด๋“œ] โ†’ [Next.js Metadata API]

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • <head> ์•ˆ์— ๋ญ˜ ๋„ฃ์–ด์•ผ ํ•˜๋Š”์ง€, ์™œ ๋„ฃ๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • SEO ๊ธฐ๋ณธ meta ํƒœ๊ทธ์™€ OG(Open Graph) ํƒœ๊ทธ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Next.js์˜ Metadata API๋กœ ๋™์  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • <head> ์•ˆ์˜ ํƒœ๊ทธ ์ˆœ์„œ๊ฐ€ ์„ฑ๋Šฅ์— ๋ฏธ์น˜๋Š” ์˜ํ–ฅ์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ๐Ÿฃ ์˜์ฒ  ( ์‹ ์ž… ): "์˜ํ˜ธ ๋‹˜, ์นด์นด์˜คํ†ก์œผ๋กœ ์šฐ๋ฆฌ ์‚ฌ์ดํŠธ ๋งํฌ ๊ณต์œ ํ–ˆ๋”๋‹ˆ ๊ทธ๋ƒฅ URL๋งŒ ๋‚˜์™€์š”. ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋“ค์€ ์ธ๋„ค์ผ ์ด๋ฏธ์ง€๋ž‘ ์ œ๋ชฉ์ด ์˜ˆ์˜๊ฒŒ ๋‚˜์˜ค๋˜๋ฐ... ๊ทธ๋ฆฌ๊ณ  ์˜์ˆ˜ ๋‹˜์ด '๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ์„ค๋ช…์ด ์•ˆ ๋‚˜์˜จ๋‹ค'๊ณ  ํ•˜๋Š”๋ฐ, <head> ์•ˆ์— ๋ญ˜ ๋” ๋„ฃ์–ด์•ผ ํ•˜๋Š” ๊ฑฐ์˜ˆ์š”?"
  • ๐Ÿฆ ์˜ํ˜ธ ( ๋ฆฌ๋“œ ): "๋งž์•„์š”, <head> ๋Š” ์‚ฌ์šฉ์ž ๋ˆˆ์—” ์•ˆ ๋ณด์ด์ง€๋งŒ, ๊ตฌ๊ธ€ยท์นด์นด์˜คยท์Šฌ๋ž™ ๊ฐ™์€ ํฌ๋กค๋Ÿฌ๋“ค์ด ์ œ์ผ ๋จผ์ € ์ฝ๋Š” '๋ช…ํ•จ'์ด์—์š”. ๋ช…ํ•จ์„ ์ œ๋Œ€๋กœ ์•ˆ ๋งŒ๋“ค๋ฉด ๊ฒ€์ƒ‰์—๋„ ์•ˆ ๊ฑธ๋ฆฌ๊ณ , ๊ณต์œ ํ–ˆ์„ ๋•Œ ๋ฏผ๋‚ฏ URL๋งŒ ๋–กํ•˜๋‹ˆ ๋‚˜์˜ค๋Š” ๊ฑฐ์ฃ ."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ๋งˆ์ผ€ํŒ…ํŒ€์ด ํ™๋ณด๋ฅผ ์‹œ์ž‘ํ–ˆ๋‹ค. ์˜์ฒ ์ด๊ฐ€ ๋งŒ๋“  ์‚ฌ์ดํŠธ ๋งํฌ๋ฅผ ์นด์นด์˜คํ†ก ๋‹จ์ฒด๋ฐฉ์— ๊ณต์œ ํ–ˆ๋Š”๋ฐ...

๐ŸŽจ ์˜์ˆ™(๋””์ž์ด๋„ˆ): "์˜์ฒ  ๋‹˜, ๋งํฌ ๊ณต์œ ํ•˜๋ฉด ์ด๋ฏธ์ง€๋ž‘ ์ œ๋ชฉ์ด ๋‚˜์™€์•ผ ํ•˜๋Š”๋ฐ ์™œ URL๋งŒ ๋‚˜์˜ค์ฃ ? ๋‹ค๋ฅธ ์ปค๋ฎค๋‹ˆํ‹ฐ๋Š” ์ธ๋„ค์ผ์ด ์˜ˆ์˜๊ฒŒ ๋ถ™๋˜๋ฐ..."

๐Ÿ‘” ์˜์ˆ˜(PM): "Google์—์„œ '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ฆฌ์•กํŠธ ๊ฒŒ์‹œํŒ' ์ณ๋„ ์šฐ๋ฆฌ ์‚ฌ์ดํŠธ๊ฐ€ ์ฒซ ํŽ˜์ด์ง€์— ์•ˆ ๋‚˜์™€์š”. SEO ์ž‘์—… ์•ˆ ํ•œ ๊ฑฐ์˜ˆ์š”?"

์˜์ฒ ์ด๊ฐ€ ๋งŒ๋“  <head> ์˜ ํ˜„์žฌ ์ƒํƒœ:

<!-- โŒ ์˜์ฒ ์ด์˜ ์ตœ์ดˆ head: ์‚ฌ์‹ค์ƒ ์•„๋ฌด๊ฒƒ๋„ ์—†์Œ -->
<head>
  <title>์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ</title>
  <link rel="stylesheet" href="styles.css" />
</head>

์ด ์ƒํƒœ๊ฐ€ ๋ฌธ์ œ์ธ ์ด์œ :

  • ๊ตฌ๊ธ€์— ๊ฒ€์ƒ‰ ์„ค๋ช…(description)์ด ์—†์Œ โ†’ ํฌ๋กค๋Ÿฌ๊ฐ€ ๋ณธ๋ฌธ์—์„œ ์ž„์˜๋กœ ์ž˜๋ผ ์”€
  • OG ํƒœ๊ทธ ์—†์Œ โ†’ ์นด์นด์˜ค/์Šฌ๋ž™์—์„œ URL๋งŒ ํ‘œ์‹œ
  • charset ์—†์Œ โ†’ ํ•œ๊ธ€ ๊นจ์งˆ ์ˆ˜ ์žˆ์Œ
  • viewport ์—†์Œ โ†’ ๋ชจ๋ฐ”์ผ์—์„œ ํ™”๋ฉด ํฌ๊ธฐ ์—‰๋ง

๐Ÿ“‹ 1. head์˜ ์—ญํ•  ๊ตฌ๋ถ„

<head> ์•ˆ์— ๋“ค์–ด๊ฐ€๋Š” ์š”์†Œ๋“ค์€ ๋”ฑ ๋‘ ๊ฐ€์ง€ ์—ญํ• ๋กœ ๋‚˜๋‰˜์–ด:

์—ญํ• ๋Œ€ํ‘œ ํƒœ๊ทธ๋ˆ„๊ตฌ๋ฅผ ์œ„ํ•œ๊ฐ€
๋ Œ๋”๋ง ํ•„์ˆ˜ ์ •๋ณดcharset, viewport, CSS <link>๋ธŒ๋ผ์šฐ์ €
๋ฉ”ํƒ€๋ฐ์ดํ„ฐ/SEO/์†Œ์…œtitle, description, OG ํƒœ๊ทธ, favicon๊ฒ€์ƒ‰์—”์ง„ยทํฌ๋กค๋ŸฌยทSNS

๐Ÿ”ง 2. ํ•„์ˆ˜ meta ํƒœ๊ทธ 4์ข… ์„ธํŠธ

2-1. charset โ€” ๋ฌธ์ž ์ธ์ฝ”๋”ฉ

<!-- ๋ฐ˜๋“œ์‹œ <head>์˜ ์ฒซ ๋ฒˆ์งธ ์ค„์—, ์ฒซ 1024 ๋ฐ”์ดํŠธ ์ด๋‚ด์— ์žˆ์–ด์•ผ ํ•จ -->
<meta charset="UTF-8" />

UTF-8์ด ์—†์œผ๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ธ์ฝ”๋”ฉ์„ ์ถ”์ธกํ•ด์•ผ ํ•ด. ์ž˜๋ชป ์ถ”์ธกํ•˜๋ฉด ํ•œ๊ธ€์ด ????? ๋กœ ๊นจ์ ธ. ์œ„์น˜๊ฐ€ ์ค‘์š”ํ•ด: ๋ธŒ๋ผ์šฐ์ €๋Š” ํŒŒ์ผ์„ ์ฝ์œผ๋ฉด์„œ ์ธ์ฝ”๋”ฉ์„ ์ฆ‰์‹œ ์•Œ์•„์•ผ ํ•˜๋ฏ€๋กœ, ๋ฌธ์„œ ์ตœ์ƒ๋‹จ 1024๋ฐ”์ดํŠธ ์ด๋‚ด์— ์žˆ์–ด์•ผ ํ•œ๋‹ค.

2-2. viewport โ€” ๋ชจ๋ฐ”์ผ ๋Œ€์‘

<meta name="viewport" content="width=device-width, initial-scale=1.0" />
  • width=device-width: ๋ทฐํฌํŠธ ๋„ˆ๋น„๋ฅผ ๊ธฐ๊ธฐ ์‹ค์ œ ๋„ˆ๋น„์— ๋งž์ถค
  • initial-scale=1.0: ์ดˆ๊ธฐ ์คŒ ๋ ˆ๋ฒจ 1๋ฐฐ (ํ™•๋Œ€/์ถ•์†Œ ์—†์Œ)

์ด๊ฒŒ ์—†์œผ๋ฉด ๋ชจ๋ฐ”์ผ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฐ์Šคํฌํƒ‘ ์‚ฌ์ด์ฆˆ(980px)๋กœ ๋ Œ๋”๋งํ•œ ํ›„ ์ถ•์†Œํ•ด์„œ ๋ณด์—ฌ์ค˜. ๊ธ€์”จ๊ฐ€ ์—„์ฒญ ์ž‘๊ฒŒ ๋ณด์ด๋Š” ๊ทธ๊ฒƒ.

2-3. title โ€” ํƒญ ์ œ๋ชฉ + ๊ฒ€์ƒ‰๊ฒฐ๊ณผ ์ œ๋ชฉ

<title>๋ฆฌ์•กํŠธ ์ƒํƒœ๊ด€๋ฆฌ ๋น„๊ต | ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ</title>
  • ๊ฒ€์ƒ‰๊ฒฐ๊ณผ์—์„œ ํŒŒ๋ž€์ƒ‰ ํด๋ฆญ ๋งํฌ๊ฐ€ ๋˜๋Š” ๊ฐ€์žฅ ์ค‘์š”ํ•œ SEO ์š”์†Œ
  • ์ ์ • ๊ธธ์ด: 50~60์ž ์ด๋‚ด (์ด ์ด์ƒ์€ ๊ตฌ๊ธ€์—์„œ ... ๋กœ ์ž˜๋ฆผ)
  • ํ˜•์‹: ํŽ˜์ด์ง€ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ | ์‚ฌ์ดํŠธ๋ช… ๊ถŒ์žฅ

2-4. description โ€” ๊ฒ€์ƒ‰๊ฒฐ๊ณผ ์„ค๋ช…

<meta
  name="description"
  content="Zustand, Jotai, Redux Toolkit์„ ์‹ค๋ฌด ๊ด€์ ์œผ๋กœ ๋น„๊ต๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. 150์ž ์ด๋‚ด์˜ ํ•ต์‹ฌ ์š”์•ฝ." />
  • ๊ฒ€์ƒ‰๊ฒฐ๊ณผ์—์„œ ์ œ๋ชฉ ์•„๋ž˜ ํšŒ์ƒ‰์œผ๋กœ ๋‚˜์˜ค๋Š” 2~3์ค„ ์„ค๋ช…
  • CTR(ํด๋ฆญ๋ฅ )์— ์ง์ ‘ ์˜ํ–ฅ์„ ์คŒ
  • ์ ์ • ๊ธธ์ด: 120~160์ž ์ด๋‚ด

๐Ÿ“ข 3. OG (Open Graph) ํƒœ๊ทธ โ€” ์†Œ์…œ ๊ณต์œ ์˜ ๋ช…ํ•จ

OG ํƒœ๊ทธ๋Š” ์นด์นด์˜คํ†ก, ์Šฌ๋ž™, ํŽ˜์ด์Šค๋ถ, ํŠธ์œ„ํ„ฐ์—์„œ ๋งํฌ๋ฅผ ๊ณต์œ ํ–ˆ์„ ๋•Œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์นด๋“œ ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ํ‘œ์ค€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์•ผ.

<!-- OG ๊ธฐ๋ณธ 4์ข… (ํ•„์ˆ˜) -->
<meta property="og:title" content="๋ฆฌ์•กํŠธ ์ƒํƒœ๊ด€๋ฆฌ ๋น„๊ต โ€” Zustand vs Jotai" />
<meta property="og:description" content="์‹ค๋ฌด ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋น„๊ตํ•œ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„" />
<meta property="og:image" content="https://ysdeveloper.community/og-images/react-state.png" />
<meta property="og:url" content="https://ysdeveloper.community/posts/react-state" />
<meta property="og:type" content="article" />  <!-- website, article, video ๋“ฑ -->
 
<!-- Twitter ์นด๋“œ (Twitter/X ์ „์šฉ) -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="๋ฆฌ์•กํŠธ ์ƒํƒœ๊ด€๋ฆฌ ๋น„๊ต โ€” Zustand vs Jotai" />
<meta name="twitter:description" content="์‹ค๋ฌด ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ๋น„๊ตํ•œ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ" />
<meta name="twitter:image" content="https://ysdeveloper.community/og-images/react-state.png" />

OG ์ด๋ฏธ์ง€ ์ŠคํŽ™:

  • ๊ถŒ์žฅ ํฌ๊ธฐ: 1200 ร— 630px (๊ฐ€๋กœ 2:1 ๋น„์œจ)
  • ์ตœ์†Œ ํฌ๊ธฐ: 600 ร— 315px
  • ํŒŒ์ผ ํ˜•์‹: PNG/JPEG (๋„ˆ๋ฌด ํฌ๋ฉด ๋กœ๋”ฉ ์ง€์—ฐ)

๐Ÿ”— Next.js ์—ฐ๊ฒฐ ๊ณ ๋ฆฌ: app/api/og/route.tsx
Next.js์˜ ImageResponse API๋กœ OG ์ด๋ฏธ์ง€๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”.
์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ์ฒ˜๋Ÿผ ๊ฒŒ์‹œ๊ธ€๋งˆ๋‹ค ๋‹ค๋ฅธ OG ์ด๋ฏธ์ง€๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ๋งค์šฐ ์œ ์šฉํ•ด์š”.


๐Ÿ–ผ๏ธ 4. ๊ธฐํƒ€ head ํ•„์ˆ˜ ์š”์†Œ

<!-- favicon: ๋ธŒ๋ผ์šฐ์ € ํƒญ์— ํ‘œ์‹œ๋˜๋Š” ์•„์ด์ฝ˜ -->
<link rel="icon" href="/favicon.ico" />
<!-- ๊ณ ํ•ด์ƒ๋„๋ฅผ ์œ„ํ•œ PNG ์•„์ด์ฝ˜ -->
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<!-- Apple ๊ธฐ๊ธฐ์˜ ํ™ˆํ™”๋ฉด ์ €์žฅ ์•„์ด์ฝ˜ -->
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
 
<!-- ์ •๊ทœํ™” URL: ๊ฐ™์€ ์ฝ˜ํ…์ธ ๊ฐ€ ์—ฌ๋Ÿฌ URL์— ์žˆ์„ ๋•Œ ์›๋ณธ ์ง€์ • (์ค‘๋ณต SEO ํŒจ๋„ํ‹ฐ ๋ฐฉ์ง€) -->
<link rel="canonical" href="https://ysdeveloper.community/posts/react-state" />
 
<!-- preload: ๊ณง ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฏธ๋ฆฌ ๋‹ค์šด๋กœ๋“œ (LCP ์ด๋ฏธ์ง€, ํฐํŠธ ๋“ฑ) -->
<link rel="preload" href="/fonts/Pretendard.woff2" as="font" type="font/woff2" crossorigin />

โš›๏ธ 5. Next.js Metadata API โ€” ๋™์  SEO ์ž๋™ํ™”

๐Ÿฃ ์˜์ฒ : "๊ทธ๋Ÿผ Next.js์—์„œ๋Š” ๋งค ํŽ˜์ด์ง€๋งˆ๋‹ค <head> ๋ฅผ ์ง์ ‘ ์งœ์•ผ ํ•˜๋‚˜์š”?"

๐Ÿฆ ์˜ํ˜ธ ๋ฆฌ๋“œ: "Next.js 13+ App Router์—์„œ๋Š” Metadata ๊ฐ์ฒด๋‚˜ generateMetadata ํ•จ์ˆ˜๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์š”. ์ค‘๋ณต ์—†์ด ์ž๋™์œผ๋กœ <head> ์— ์ฃผ์ž…ํ•ด์ฃผ๊ฑฐ๋“ ์š”."

// app/layout.tsx โ€” ์‚ฌ์ดํŠธ ์ „์—ญ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ธฐ๋ณธ๊ฐ’
import type { Metadata } from "next";
 
export const metadata: Metadata = {
  title: {
    default: "์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ",
    // ํ•˜์œ„ ํŽ˜์ด์ง€์—์„œ %s ๊ฐ€ ํŽ˜์ด์ง€ ์ œ๋ชฉ์œผ๋กœ ์น˜ํ™˜๋จ
    template: "%s | ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ",
  },
  description: "ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž๋“ค์˜ ์Šคํ„ฐ๋”” ๋งค์นญ ์ปค๋ฎค๋‹ˆํ‹ฐ",
  openGraph: {
    type: "website",
    locale: "ko_KR",
    url: "https://ysdeveloper.community",
    siteName: "์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ",
  },
};
// app/posts/[id]/page.tsx โ€” ๊ฒŒ์‹œ๊ธ€ ํŽ˜์ด์ง€ ๋™์  ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
import type { Metadata } from "next";
 
// generateMetadata: ์„œ๋ฒ„์—์„œ ์‹คํ–‰, DB/API ์กฐํšŒ ๊ฐ€๋Šฅ
export async function generateMetadata({
  params,
}: {
  params: { id: string };
}): Promise<Metadata> {
  const post = await fetchPost(params.id);
 
  return {
    // template ์ ์šฉ โ†’ "๋ฆฌ์•กํŠธ ์ƒํƒœ๊ด€๋ฆฌ ๋น„๊ต | ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ"
    title: post.title,
    description: post.summary,
    openGraph: {
      title: post.title,
      description: post.summary,
      // ๊ฒŒ์‹œ๊ธ€๋ณ„ ๋™์  OG ์ด๋ฏธ์ง€ (Next.js ImageResponse ํ™œ์šฉ)
      images: [`/api/og?title=${encodeURIComponent(post.title)}`],
      type: "article",
      publishedTime: post.createdAt,
    },
  };
}

๐Ÿฆ ์˜ํ˜ธ ๋ฆฌ๋“œ: "Next.js Metadata API๊ฐ€ ์ข‹์€ ์ด์œ ๋Š” ์ค‘๋ณต ํƒœ๊ทธ ์ž๋™ ์ œ๊ฑฐ, SSR์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์ดˆ๊ธฐ HTML ์ƒ์„ฑ, TypeScript ์ž๋™์™„์„ฑ๊นŒ์ง€ ์ง€์›ํ•ด์„œ ์‹ค์ˆ˜๋ฅผ ์ค„์—ฌ์ค˜์š”."


๐Ÿ ์™„์„ฑ๋œ head ํ…œํ”Œ๋ฆฟ

<!doctype html>
<html lang="ko">
  <head>
    <!-- [1] ์ธ์ฝ”๋”ฉ (๊ฐ€์žฅ ๋จผ์ €) -->
    <meta charset="UTF-8" />
 
    <!-- [2] ๋ทฐํฌํŠธ (๋ชจ๋ฐ”์ผ ํ•„์ˆ˜) -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
    <!-- [3] ํ•ต์‹ฌ SEO -->
    <title>๋ฆฌ์•กํŠธ ์ƒํƒœ๊ด€๋ฆฌ ๋น„๊ต | ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ</title>
    <meta name="description" content="Zustand, Jotai๋ฅผ ์‹ค๋ฌด ๊ด€์ ์œผ๋กœ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค." />
    <link rel="canonical" href="https://ysdeveloper.community/posts/react-state" />
 
    <!-- [4] OG ์†Œ์…œ ๊ณต์œ  -->
    <meta property="og:title" content="๋ฆฌ์•กํŠธ ์ƒํƒœ๊ด€๋ฆฌ ๋น„๊ต" />
    <meta property="og:description" content="์‹ค๋ฌด ๊ฐœ๋ฐœ์ž ๋น„๊ต ๋ถ„์„" />
    <meta property="og:image" content="https://ysdeveloper.community/og/react-state.png" />
    <meta property="og:url" content="https://ysdeveloper.community/posts/react-state" />
    <meta property="og:type" content="article" />
 
    <!-- [5] ๋ Œ๋”๋ง ๋ฆฌ์†Œ์Šค (CSS: ๋ Œ๋” ๋ธ”๋กœํ‚น์ด๋ฏ€๋กœ ์ตœ๋Œ€ํ•œ ์ƒ๋‹จ, preload ๋จผ์ €) -->
    <link rel="preload" href="/fonts/Pretendard.woff2" as="font" type="font/woff2" crossorigin />
    <link rel="stylesheet" href="/styles.css" />
 
    <!-- [6] ์•„์ด์ฝ˜ -->
    <link rel="icon" href="/favicon.ico" />
 
    <!-- [7] JS: defer๋กœ Non-blocking -->
    <script src="/app.js" defer></script>
  </head>
  <body>...</body>
</html>


๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ๋‹ค์Œ ์ค‘ ์นด์นด์˜คํ†ก ๋งํฌ ๊ณต์œ  ์‹œ ์ธ๋„ค์ผ ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ meta ํƒœ๊ทธ๋Š”?

  • A) <meta name="description" content="...">
  • B) <meta property="og:image" content="...">
  • ๊ฐ€) <meta name="viewport" content="width=device-width">
  • ๋ผ) <meta charset="UTF-8">

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: ์นด์นด์˜คํ†ก, ์Šฌ๋ž™, ํŽ˜์ด์Šค๋ถ ๋“ฑ์˜ SNS๋Š” ๋งํฌ๋ฅผ ๊ณต์œ ํ•  ๋•Œ ํ•ด๋‹น URL์˜ HTML์„ ์„œ๋ฒ„์—์„œ ํฌ๋กค๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ <head> ์˜ OG(Open Graph) ํƒœ๊ทธ๋ฅผ ์ฝ์–ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์นด๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š”๋ฐ, ์ด๋ฏธ์ง€๋Š” og:image ๊ฐ€ ๋‹ด๋‹นํ•ด์š”.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: description ์€ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์šฉ์ด๊ณ , viewport ๋Š” ๋ชจ๋ฐ”์ผ ๋ Œ๋”๋ง์šฉ, charset ์€ ์ธ์ฝ”๋”ฉ ์„ ์–ธ์ด์—์š”.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๊ฒ€์ƒ‰ ์„ค๋ช… = description, SNS ์นด๋“œ ์ด๋ฏธ์ง€ = og:image"

Q2. Next.js App Router์—์„œ ๊ฒŒ์‹œ๊ธ€๋ณ„ ๋‹ค๋ฅธ ์ œ๋ชฉ์„ <title> ์— ์ž๋™์œผ๋กœ ๋„ฃ์œผ๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•˜๋Š”๊ฐ€?

โœ… ์ •๋‹ต: page.tsx ์—์„œ generateMetadata ํ•จ์ˆ˜๋ฅผ export ํ•˜๊ณ , params ๋กœ ๋ฐ›์€ ๊ฒŒ์‹œ๊ธ€ ID๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•œ ๋’ค { title: post.title } ์„ ๋ฐ˜ํ™˜

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: Next.js App Router์˜ generateMetadata ๋Š” ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์ฒ˜๋Ÿผ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜์–ด SSR ์‹œ์ ์— ์˜ฌ๋ฐ”๋ฅธ <title> ์„ <head> ์— ์ฃผ์ž…ํ•ด์ค˜์š”. layout.tsx ์—์„œ title.template: "%s | ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ" ๋ฅผ ์„ค์ •ํ•ด๋‘๋ฉด, ํ•˜์œ„ ํŽ˜์ด์ง€์—์„œ ๋ฐ˜ํ™˜ํ•œ title ์ด ์ž๋™์œผ๋กœ %s ์ž๋ฆฌ์— ๋“ค์–ด๊ฐ€ ์™„์„ฑ๋œ ์ œ๋ชฉ์ด ๋ผ์š”.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "์ •์  ๋ฉ”ํƒ€ = export const metadata, ๋™์  ๋ฉ”ํƒ€ = export async function generateMetadata"

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„

์˜์ˆ˜(PM)๊ฐ€ ์š”์ฒญํ•œ๋‹ค. "๊ตฌ๊ธ€์—์„œ '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ฆฌ์•กํŠธ ์Šคํ„ฐ๋””' ๊ฒ€์ƒ‰ํ•˜๋ฉด ์šฐ๋ฆฌ ์‚ฌ์ดํŠธ๊ฐ€ 1ํŽ˜์ด์ง€์— ๋‚˜์™”์œผ๋ฉด ์ข‹๊ฒ ์–ด์š”. ๊ทธ๋ฆฌ๊ณ  ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— '๋ฆฌ์•กํŠธ ๊ณต๋ถ€ ์ปค๋ฎค๋‹ˆํ‹ฐ โ€” ๋งค์นญ๋ถ€ํ„ฐ ์Šคํ„ฐ๋”” ์šด์˜๊นŒ์ง€' ๋ผ๋Š” ์„ค๋ช…์ด ๋‚˜์™€์•ผ ํ•ด์š”."

์˜์ฒ ์ด๊ฐ€ ๊ตฌํ˜„ํ•ด์•ผ ํ•  ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋‘ ๊ฐ€์ง€ meta ํƒœ๊ทธ๋Š”?

โœ… ์ •๋‹ต: โ‘  <title> ์— ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ ํฌํ•จ, โ‘ก <meta name="description"> ์— ์š”์ฒญ ๋ฌธ๊ตฌ ์ž‘์„ฑ

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰๊ฒฐ๊ณผ์—์„œ ํŒŒ๋ž€ ์ œ๋ชฉ ๋งํฌ๋Š” <title> ์—์„œ, ๊ทธ ์•„๋ž˜ ํšŒ์ƒ‰ ์„ค๋ช… ํ…์ŠคํŠธ๋Š” <meta name="description"> ์—์„œ ๊ฐ€์ ธ์™€์š”. description์ด ์—†์œผ๋ฉด ๊ตฌ๊ธ€์ด ๋ณธ๋ฌธ์—์„œ ์ž„์˜๋กœ ์ž˜๋ผ ์“ฐ๋Š”๋ฐ, ์˜์ˆ˜ ๋‹˜์ด ์›ํ•˜๋Š” ๋ฌธ๊ตฌ๊ฐ€ ๋ณด์žฅ๋˜์ง€ ์•Š์•„์š”.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: ์˜์ฒ  ๋‹˜, OG ํƒœ๊ทธ๋Š” SNS ๊ณต์œ ์šฉ์ด๋ผ ๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์„ค๋ช…์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์•„์š”. ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ = title + description ์ด์—์š”.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๊ตฌ๊ธ€ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์˜ ์ œ๋ชฉ + ์„ค๋ช… = <title> + <meta name='description'>"

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜ OG ํƒœ๊ทธ ์ถ”๊ฐ€ํ•˜๊ณ  ์นด์นด์˜คํ†ก์—์„œ ๋งํฌ ํ…Œ์ŠคํŠธํ–ˆ์„ ๋•Œ ์ธ๋„ค์ผ์ด ๋”ฑ ๋‚˜์˜จ ์ˆœ๊ฐ„... ์ง„์งœ ์†Œ๋ฆ„ ๋‹์•˜๋‹ค. ๊ทธ๋™์•ˆ <title> ๋งŒ ๋‹ฌ๋ž‘ ์žˆ๋˜ ๊ฒŒ ๋ถ€๋„๋Ÿฌ์šธ ์ •๋„. <head> ๋Š” ๋ˆˆ์— ์•ˆ ๋ณด์ด๋Š” ๋ถ€๋ถ„์ด๋ผ ์†Œํ™€ํžˆ ํ–ˆ๋Š”๋ฐ, ์‚ฌ์‹ค ์ด๊ฒŒ ๊ตฌ๊ธ€ ๋ด‡์ด ์ œ์ผ ๋จผ์ € ์ฝ๋Š” ๋ช…ํ•จ์ด๋ผ๋Š” ๊ฒŒ ์ด์ œ ํ”ผ๋ถ€๋กœ ๋А๊ปด์ง„๋‹ค.

๐Ÿ’ก "<head> ๋Š” ์‚ฌ์šฉ์ž ๋ˆˆ์— ์•ˆ ๋ณด์ด์ง€๋งŒ, ๊ตฌ๊ธ€ยท์นด์นด์˜คยท์Šฌ๋ž™์ด ์ œ์ผ ๋จผ์ € ์ฝ๋Š” ๋ช…ํ•จ์ด๋‹ค. ๋ช…ํ•จ์ด ๋ถ€์‹คํ•˜๋ฉด ์•„๋ฌด๋ฆฌ ์ข‹์€ ์‚ฌ์ดํŠธ๋„ ์„ธ์ƒ์— ์ œ๋Œ€๋กœ ์•Œ๋ ค์ง€์ง€ ์•Š๋Š”๋‹ค."

์˜์ˆ˜ ๋‹˜ํ•œํ…Œ SEO ๊ฐœ์„  ์ดํ›„ ๊ตฌ๊ธ€ ์„œ์น˜ ์ฝ˜์†” ์ง€ํ‘œ ๋ณด์—ฌ๋“œ๋ ธ๋”๋‹ˆ "์ด๊ฒŒ ๋˜๋„ค?" ํ•˜๊ณ  ๊นœ์ง ๋†€๋ผ์…จ๋‹ค. ๊ทธ ํ‘œ์ • ๋ณด๋ ค๊ณ  ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ ๊ฐ™์•„์„œ ์˜ค๋Š˜๋”ฐ๋ผ ๋ฟŒ๋“ฏํ•˜๋‹ค. ํ‡ด๊ทผํ•˜๊ณ  ์ง‘์—์„œ Next.js Metadata API๋กœ OG ์ด๋ฏธ์ง€๊นŒ์ง€ ๋™์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ• ๋” ํŒŒ๋ด์•ผ๊ฒ ๋‹ค.


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ