Sanity SEO 올바르게 구현하기: Open Graph, JSON-LD, fallbacks
Source: Dev.to
Sanity SEO 제대로 하기: Open Graph, JSON‑LD, Fallbacks
소개
Sanity를 사용해 콘텐츠를 관리하면서 SEO를 최적화하는 방법을 알아보겠습니다.
Open Graph 메타 태그와 JSON‑LD 스키마를 활용하고, 데이터가 없을 때를 대비한 fallback 전략까지 다룹니다.
1. Sanity 스키마에 SEO 필드 추가하기
// schemas/seo.js
export default {
name: 'seo',
title: 'SEO',
type: 'object',
fields: [
{ name: 'title', type: 'string', title: 'Meta Title' },
{ name: 'description', type: 'text', title: 'Meta Description' },
{ name: 'image', type: 'image', title: 'Social Image' },
{ name: 'canonical', type: 'url', title: 'Canonical URL' },
// Open Graph 전용 필드
{ name: 'ogTitle', type: 'string', title: 'OG Title' },
{ name: 'ogDescription', type: 'text', title: 'OG Description' },
{ name: 'ogImage', type: 'image', title: 'OG Image' },
// JSON‑LD 전용 필드
{ name: 'jsonLd', type: 'object', title: 'JSON‑LD', fields: [...] }
]
}
위와 같이 seo 객체를 정의하고, 페이지 혹은 포스트 스키마에 embed합니다.
2. 페이지 쿼리에서 SEO 데이터 가져오기
*[_type == "post" && slug.current == $slug][0]{
title,
body,
"seo": seo{
title,
description,
image{
asset->{
url
}
},
canonical,
ogTitle,
ogDescription,
ogImage{
asset->{
url
}
},
jsonLd
}
}
쿼리 결과는 seo 객체 안에 모든 메타 정보를 담고 있어, 템플릿에서 바로 사용할 수 있습니다.
3. Next.js (또는 Remix)에서 메타 태그 렌더링
import Head from 'next/head'
export default function Post({ data }) {
const { seo } = data
const fallbackTitle = data.title
const fallbackDescription = data.body?.[0]?.children?.[0]?.text?.slice(0, 160)
return (
<>
<Head>
{/* 기본 메타 */}
<title>{seo.title || fallbackTitle}</title>
<meta name="description" content={seo.description || fallbackDescription} />
{seo.canonical && <link rel="canonical" href={seo.canonical} />}
{/* Open Graph */}
<meta property="og:title" content={seo.ogTitle || fallbackTitle} />
<meta property="og:description" content={seo.ogDescription || fallbackDescription} />
{seo.ogImage?.asset?.url && (
<meta property="og:image" content={seo.ogImage.asset.url} />
)}
{/* JSON‑LD */}
{seo.jsonLd && (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(seo.jsonLd) }}
/>
)}
</Head>
{/* 페이지 본문 */}
{/* ... */}
</>
)
}
Fallback 전략
- 제목:
seo.title이 없으면 콘텐츠의title을 사용합니다. - 설명:
seo.description이 없으면 본문 앞 160자를 추출합니다. - 이미지: Open Graph 이미지가 없을 경우, 기본
seo.image혹은 콘텐츠 내 첫 번째 이미지로 대체합니다.
4. JSON‑LD 예시
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Sanity SEO Done Right",
"image": ["https://example.com/og-image.jpg"],
"author": {
"@type": "Person",
"name": "Jon Oroboto"
},
"datePublished": "2023-08-15",
"publisher": {
"@type": "Organization",
"name": "Dev.to",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/logo.png"
}
},
"description": "Open Graph와 JSON‑LD를 활용해 Sanity 기반 사이트의 SEO를 최적화하는 방법을 소개합니다."
}
위 JSON‑LD는 Article 스키마를 사용했으며, 필요에 따라 BlogPosting, NewsArticle 등으로 교체할 수 있습니다.
5. 전체 흐름 요약
- Sanity에 SEO 전용 객체를 정의한다.
- 페이지 쿼리에서 해당 객체를 포함해 데이터를 가져온다.
- 프레임워크(Next.js, Remix 등)에서 메타 태그와 JSON‑LD를 렌더링한다.
- 데이터가 없을 경우 fallback 로직을 적용해 검색 엔진과 SNS가 항상 유의미한 정보를 받도록 한다.
결론
Sanity와 같은 헤드리스 CMS를 사용할 때, SEO를 별도 플러그인에 의존하지 않고 데이터 레이어에서 직접 관리하면 다음과 같은 장점이 있습니다.
- 일관성: 모든 페이지가 동일한 메타 구조를 갖는다.
- 유연성: 필요에 따라 Open Graph, Twitter Card, JSON‑LD 등을 자유롭게 조합한다.
- 성능: 서버 사이드에서 한 번에 메타 정보를 삽입하므로 클라이언트 측 스크립트 로드가 최소화된다.
위 가이드를 따라 구현하면, Sanity 기반 사이트에서도 검색 엔진 최적화와 소셜 미디어 공유가 한층 강화됩니다. 🚀
Source: …
모든 Sanity 프로젝트를 위한 SEO 기본 설정
우리가 배포하는 모든 Sanity 프로젝트는 동일한 SEO 기본 설정으로 시작합니다:
- 최상위 제목 및 설명 – 페이지 헤딩/요약으로 사용되며 기본 메타 태그 역할도 합니다.
- SEO 탭 – 필요할 때 최상위 필드를 덮어씁니다.
- Open Graph 필드 – 소셜 카드 오버라이드를 위해 한 단계 아래에 위치합니다.
모든 필드에는 합리적인 폴백이 있어, 편집자가 필드를 채우지 않더라도 페이지가 항상 무언가를 렌더링합니다.
이 패턴이 존재하는 이유
Sanity는 무엇이든 모델링할 수 있습니다 – 블로그, 전자상거래 카탈로그, 실시간 열차 시간표 화면 등.
플랫폼이 의도적으로 비의견적이기 때문에 SEO 패턴을 자동으로 선택해 주지 않습니다.
의견은 여러분이 제시하고, Sanity는 스키마를 제공합니다.
필드 레이아웃
| 레이어 | 필드 | 목적 |
|---|---|---|
| 최상위 | title, description, seoImage (폴백) | 기본 카피 및 기본 메타. |
| SEO 탭 | seoTitle, seoDescription, seoImage, seoNoIndex, seoHideFromLists | 검색 엔진용 오버라이드. |
| Open Graph (중첩) | ogTitle, ogDescription, ogImage, twitterTitle, twitterDescription, twitterImage 등 | 소셜 카드 전용 값. |
- 일관성 – 편집자는 페이지 카피를 한 번만 작성하고(최상위) 필요에 따라 SEO 탭에서 메타 데이터를 오버라이드합니다.
- 폴백 – 최상위 필드가 기본값 역할을 하여 빈 렌더링을 방지합니다.
seoHideFromLists
무엇을 하나요?
이 플래그는 편집자가 페이지를 인덱싱 가능하게 유지하면서 목록 페이지(예: 블로그 인덱스, 제품 카탈로그)에서는 제외하도록 합니다.
noIndex를 설정하지 않고 숨긴 페이지를 필터링하려면 GROQ 쿼리에서 이 값을 사용하세요.
구조화된 데이터 생성
작성자, 제목, 설명, 발행일이 이미 Sanity에 구조화되어 있다면, 이를 JSON‑LD 형태로 바로 Next.js 페이지에 매핑합니다:
import Head from 'next/head';
import { PortableText } from '@portabletext/react';
export default function Post({ data }) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "Article",
headline: data.title,
description: data.description,
author: { "@type": "Person", name: data.author?.name },
datePublished: data.publishDate,
image: data.seoImage?.url,
};
return (
<>
{data.seoTitle || data.title}
{JSON.stringify(jsonLd)}
## {data.title}
</>
);
}
편집자는 JSON‑LD를 직접 건드리지 않으며, 콘텐츠와 동기화된 상태를 유지합니다.
이 내용을 설명하는 방법 (영상 스크립트 개요)
“Hey bud, how you doing? I get asked this a lot, so I’m putting it on YouTube…”
- Sanity의 비주관적 특성 – 간단한 블로그부터 전체 전자상거래 시스템까지 모든 것을 모델링할 수 있습니다.
- 왜 SEO 의견이 필요한가 – 플랫폼이 대신 결정해 주지는 않으며, 여러분이 패턴을 정의해야 합니다.
- TurboArt 구현 – 공식 Sanity Learn 문서를 그대로 반영하지만 빠른 소비를 위해 간소화되었습니다.
- 기본 필드 –
title,description, 선택적 대체seoImage. - SEO 탭 오버라이드 –
seoTitle,seoDescription,seoImage,seoNoIndex,seoHideFromLists. - Open Graph 레이어 – Facebook, Twitter, LinkedIn 등용 별도 OG 필드.
- 검증 (선택 사항) – 문자 제한, 필수 필드 등은 필요에 따라 추가할 수 있습니다.
TL;DR
- Top‑level
title&description= 기본 메타. - SEO 탭 = 오버라이드 (
seoTitle,seoDescription,seoImage,seoNoIndex,seoHideFromLists). - Open Graph = 소셜 카드용 심화 레이어.
- Fallbacks는 아무 것도 비어 있지 않도록 보장합니다.
- **
seoHideFromLists**는 페이지를 내부 목록에서 숨기면서도 인덱싱은 가능하게 합니다.
Turbostart Sanity에서 이 패턴을 복사하면 모든 프로젝트에 견고하고 재사용 가능한 SEO 기반을 확보할 수 있습니다.
JSON‑LD & “Set‑and‑Forget” SEO
You can put JSON‑LD anywhere you like – in the , in a component, etc. JSON‑LD는 원하는 어디에든 넣을 수 있습니다 – 안에, 컴포넌트 안에 등.
A quick note on JSON‑LD: I much, much prefer a “set‑and‑forget” approach.
JSON‑LD에 대한 간단한 메모: 저는 ‘설정하고 잊어버리기’ 방식을 훨씬, 훨씬 선호합니다.
If you’re already entering titles, descriptions, authors, etc. in Sanity, let those fields automatically generate JSON‑LD.
이미 Sanity에 제목, 설명, 저자 등을 입력하고 있다면, 해당 필드가 자동으로 JSON‑LD를 생성하도록 하세요.
That way you only have to fill in the data once, and the correct JSON‑LD appears without any extra work.
이렇게 하면 데이터를 한 번만 입력하면 되고, 별도의 작업 없이 올바른 JSON‑LD가 나타납니다.
“You just enter the data inside of Sanity, and a like‑for‑like pair comes up with JSON‑LD. It isn’t that complicated these days with AI – you can even do it relatively quickly.”
“Sanity에 데이터를 입력하기만 하면, 동일한 내용의 JSON‑LD가 자동으로 생성됩니다. 요즘 AI 덕분에 그리 복잡하지 않으며, 비교적 빠르게 할 수 있습니다.”
모든 Sanity 사이트를 위한 기본 SEO
다음 내용을 Sanity 기반 웹사이트의 시작점으로 사용하세요 (다른 유형의 프로젝트에서는 설정이 다를 수 있습니다).
-
Turbo‑Art Sanity 스타터를 Sanity Exchange 또는 GitHub에서 가져오세요.
-
저장소를 열고 두 개의 핵심 파일에 집중하세요:
query.js(또는 유사 파일) – 우리가 GROQ 쿼리(Sanity의 쿼리 언어)를 처리하는 방식을 보여줍니다.seo.js(orseoFields.js) – 재사용 가능한 SEO 필드 정의를 포함합니다.
우리가 사용하는 SEO 필드
| Field | Purpose |
|---|---|
seoTitle | SEO에 최적화된 제목 |
seoDescription | 메타 설명 |
seoImage | Open‑Graph / Twitter 이미지 |
seoNoIndex | Boolean – 검색 엔진에 페이지 인덱싱을 하지 않도록 알림 |
seoHideFromLists | Boolean – 생성된 목록(예: 블로그 인덱스)에서 문서를 제거 |
seoNoIndex와 seoHideFromLists를 모두 사용하는 이유
seoNoIndex– 크롤링을 원하지 않는 페이지에 유용합니다(예: 클릭당 비용 광고 랜딩 페이지).seoHideFromLists– 페이지는 검색 가능하게 유지하면서 내부 목록에는 표시되지 않게 하고 싶을 때 유용합니다(예: “초안” 블로그 포스트).
seoHideFromLists를 사용하면 다음과 같은 GROQ 필터를 작성할 수 있습니다:
*[_type == "post" && !seoHideFromLists]{
...,
}
이는 숨김으로 표시된 게시물을 제외한 모든 게시물을 반환합니다 – 깔끔하고 유지 보수가 쉬운 솔루션.
검증 및 다음 단계
- 시작 템플릿에는 각 SEO 필드에 대한 기본 검증(필수, 최대 길이 등)이 포함되어 있습니다.
- 문제가 발생하거나 추가 안내가 필요하면 언제든지 알려 주세요 – 기꺼이 도와드리고 올바른 방향을 제시해 드리겠습니다.
읽어 주셔서 감사합니다!
좋은 하루 보내세요.
건강히 지내세요.