๋ฐฐ๊ฒฝ
DaleUI๋ฅผ ์ฌ์ฉ ์ค์ธ ์ฌ๋ก๋ฅผ ๊ธฐ์ฌ์๊ฐ ์๊ธฐ ํ๋ก์ ํธ URL๋ง ๋ฑ๋กํ๋ฉด, ๊ทธ URL์ Open Graph(OG) ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ์ถ์ถํด ์นด๋ ํํ๋ก ๊ทธ๋ ค์ฃผ๋ ์ผ์ผ์ด์ค ํ์ด์ง๋ฅผ ๊ตฌํํฉ๋๋ค. ๋ฐ์ดํฐ ์คํค๋ง๋ DS-02(#1025) ์์์ ์นด๋ props์์ ๋์ถํฉ๋๋ค.
์ฆ OG๋ ๊ธฐ์ฌ์๊ฐ ์์ผ๋ก ์ฑ์ฐ๋ ์
๋ ฅ ํผ์ด ์๋๋ผ, URL์์ ๊ธ์ด์ค๋ ๋ฐ์ดํฐ ์์ค์
๋๋ค. ๊ธฐ์ฌ ์ง์
์ฅ๋ฒฝ์ ๋ฎ์ถ๋ ๊ฒ์ด ํต์ฌ์
๋๋ค.
๋์ ๋ฐฉ์ (๋น๋ ํ์ ์ถ์ถ)
๊ธฐ์ฌ์: showcase ๋ชฉ๋ก์ { url } ๋ฑ๋ก (PR)
โ (predev/prebuild)
๋น๋ ์คํฌ๋ฆฝํธ: ๊ฐ url์ fetch โ <head>์ og:* / twitter:* ํ์ฑ โ JSON ์บ์๋ก ์ ์ฅ
โ
๋ผ์ฐํธ loader: ์์ฑ๋ JSON๋ง ์ฝ์ด ์นด๋ ๋ ๋๋ง (๋ฐํ์ ์ธ๋ถ fetch ์์)
๊ธฐ์กด scripts/generate-og.mts(๋น๋ ํ์์ ๊ฒฐ์ ์ ์ฐ์ถ๋ฌผ์ public/์ ์์ฑ)์ ๋์ผํ ์ฒ ํ์
๋๋ค. ์ ์ ๋ฐฐํฌ ํ๊ฒฝ์์ ๋ฐํ์ CORSยทํ์์์ยท์คํจ ์ฒ๋ฆฌ ๋ถ๋ด์ด ์์ต๋๋ค.
ํธ๋ ์ด๋์คํ: ๋์ ์ฌ์ดํธ๊ฐ OG๋ฅผ ๋ฐ๊ฟ๋ ์ฌ๋น๋ ์ ๊น์ง ๋ฐ์๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์คํค๋ง์ ์๋ ์ค๋ฒ๋ผ์ด๋ ํ๋์ fallback ๊ธฐ๋ณธ๊ฐ์ ํจ๊ป ๋ก๋๋ค.
๋ฐ์ดํฐ ์คํค๋ง
src/content/showcase/schema.ts์ zod๋ก ์ ์ํฉ๋๋ค. ๊ธฐ์ฌ์ ์
๋ ฅ์ ์ต์ํํ๊ณ , ๋๋จธ์ง๋ ๋น๋ ํ์์ ์ฑ์๋๋ค.
๊ธฐ์ฌ์ ์
๋ ฅ (frontmatter)
| ํ๋ |
ํ์
/์ ์ฝ |
ํ์ |
์ค๋ช
|
url |
string.url() |
โ
|
ํ๋ก์ ํธ URL. OG ์ถ์ถ ๋์ |
title |
string.optional() |
|
์๋ ์ค๋ฒ๋ผ์ด๋. ์์ผ๋ฉด og:title ์ฌ์ฉ |
description |
string.optional() |
|
์๋ ์ค๋ฒ๋ผ์ด๋. ์์ผ๋ฉด og:description ์ฌ์ฉ |
image |
string.optional() |
|
์๋ ์ค๋ฒ๋ผ์ด๋. ์์ผ๋ฉด og:image ์ฌ์ฉ |
๋น๋ ํ์์ ์ถ์ถยท๋ณํฉ๋๋ ๊ฐ
๊ฐ ์นด๋๊ฐ ์ค์ ๋ก ๊ทธ๋ฆด ๊ฐ์ ์๋ ์ค๋ฒ๋ผ์ด๋ ?? OG ์ถ์ถ๊ฐ ?? fallback ์์ผ๋ก ๊ฒฐ์ ํฉ๋๋ค.
| ์นด๋ ํ๋ |
์์ค |
title |
frontmatter.title ?? og:title ?? URL ํธ์คํธ๋ช
|
description |
frontmatter.description ?? og:description ?? "" |
image |
frontmatter.image ?? og:image ?? ๊ธฐ๋ณธ ํ๋ ์ด์คํ๋ ์ด๋ฏธ์ง |
siteName |
og:site_name ?? URL ํธ์คํธ๋ช
|
url |
frontmatter.url (๊ทธ๋๋ก) |
์์
๋ด์ฉ
์ฐธ๊ณ
- ๋น๋ ํ์ ์ฐ์ถ๋ฌผ ์์ฑ ํจํด:
scripts/generate-og.mts (manifest ํด์ ์บ์)
- SEO ๋ฉํ ์ปดํฌ๋ํธ:
src/components/SeoMeta.tsx, OG URL ํฌํผ: src/og/ogImage.ts
- ์ฌ์ฌ์ฉํ ์ฝํ
์ธ ์ปฌ๋ ์
ํจํด:
src/content/blog/{schema,loader}.ts
- ํ์ฌ ํ๋ ์ด์คํ๋:
src/routes/showcase._index.tsx
๋ฐฐ๊ฒฝ
DaleUI๋ฅผ ์ฌ์ฉ ์ค์ธ ์ฌ๋ก๋ฅผ ๊ธฐ์ฌ์๊ฐ ์๊ธฐ ํ๋ก์ ํธ URL๋ง ๋ฑ๋กํ๋ฉด, ๊ทธ URL์ Open Graph(OG) ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์๋์ผ๋ก ์ถ์ถํด ์นด๋ ํํ๋ก ๊ทธ๋ ค์ฃผ๋ ์ผ์ผ์ด์ค ํ์ด์ง๋ฅผ ๊ตฌํํฉ๋๋ค. ๋ฐ์ดํฐ ์คํค๋ง๋ DS-02(#1025) ์์์ ์นด๋ props์์ ๋์ถํฉ๋๋ค.
์ฆ OG๋ ๊ธฐ์ฌ์๊ฐ ์์ผ๋ก ์ฑ์ฐ๋ ์ ๋ ฅ ํผ์ด ์๋๋ผ, URL์์ ๊ธ์ด์ค๋ ๋ฐ์ดํฐ ์์ค์ ๋๋ค. ๊ธฐ์ฌ ์ง์ ์ฅ๋ฒฝ์ ๋ฎ์ถ๋ ๊ฒ์ด ํต์ฌ์ ๋๋ค.
๋์ ๋ฐฉ์ (๋น๋ ํ์ ์ถ์ถ)
๊ธฐ์กด
scripts/generate-og.mts(๋น๋ ํ์์ ๊ฒฐ์ ์ ์ฐ์ถ๋ฌผ์public/์ ์์ฑ)์ ๋์ผํ ์ฒ ํ์ ๋๋ค. ์ ์ ๋ฐฐํฌ ํ๊ฒฝ์์ ๋ฐํ์ CORSยทํ์์์ยท์คํจ ์ฒ๋ฆฌ ๋ถ๋ด์ด ์์ต๋๋ค.ํธ๋ ์ด๋์คํ: ๋์ ์ฌ์ดํธ๊ฐ OG๋ฅผ ๋ฐ๊ฟ๋ ์ฌ๋น๋ ์ ๊น์ง ๋ฐ์๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์คํค๋ง์ ์๋ ์ค๋ฒ๋ผ์ด๋ ํ๋์ fallback ๊ธฐ๋ณธ๊ฐ์ ํจ๊ป ๋ก๋๋ค.
๋ฐ์ดํฐ ์คํค๋ง
src/content/showcase/schema.ts์ zod๋ก ์ ์ํฉ๋๋ค. ๊ธฐ์ฌ์ ์ ๋ ฅ์ ์ต์ํํ๊ณ , ๋๋จธ์ง๋ ๋น๋ ํ์์ ์ฑ์๋๋ค.๊ธฐ์ฌ์ ์ ๋ ฅ (frontmatter)
urlstring.url()titlestring.optional()og:title์ฌ์ฉdescriptionstring.optional()og:description์ฌ์ฉimagestring.optional()og:image์ฌ์ฉ๋น๋ ํ์์ ์ถ์ถยท๋ณํฉ๋๋ ๊ฐ
๊ฐ ์นด๋๊ฐ ์ค์ ๋ก ๊ทธ๋ฆด ๊ฐ์
์๋ ์ค๋ฒ๋ผ์ด๋ ?? OG ์ถ์ถ๊ฐ ?? fallback์์ผ๋ก ๊ฒฐ์ ํฉ๋๋ค.titleog:title?? URL ํธ์คํธ๋ชdescriptionog:description?? ""imageog:image?? ๊ธฐ๋ณธ ํ๋ ์ด์คํ๋ ์ด๋ฏธ์งsiteNameog:site_name?? URL ํธ์คํธ๋ชurl์์ ๋ด์ฉ
open-graph-scraper/metascraper/cheerio, ๋๋ ๊ฒฝ๋ ์ ๊ท์ ํ์). ํ์ฌ ๋ ํฌ์ OG ํ์ ์์กด์ฑ ์์url๋ชฉ๋ก์ fetch โ OG ํ์ฑ โpublic/og/showcase.json(๋๋ ์ฝํ ์ธ ์บ์)๋ก ์ ์ฅ.generate-og.mts์ manifest ํด์ ์บ์ ํจํด ์ฐธ๊ณsrc/content/showcase/schema.tszod ์คํค๋ง ์ ์ (์ ํ)showcase._index.tsx)๋ฅผ ์ค์ ๊ตฌํ์ผ๋ก ๊ต์ฒด (ํ์ฌUnderConstruction) โ ์นด๋ ๊ทธ๋ฆฌ๋SeoMeta์ฌ์ฌ์ฉ (showcase์ ์ OG ์ด๋ฏธ์ง)์ฐธ๊ณ
scripts/generate-og.mts(manifest ํด์ ์บ์)src/components/SeoMeta.tsx, OG URL ํฌํผ:src/og/ogImage.tssrc/content/blog/{schema,loader}.tssrc/routes/showcase._index.tsx