Frontend Integration
Query SEO data via GROQ and render meta tags in your frontend framework.
GROQ Query
Fetch SEO fields from any document:
GROQ query
*[_type == "page" && slug.current == $slug][0]{
title,
seo {
title,
description,
canonicalUrl,
metaImage { asset-> { url } },
keywords,
robots { noIndex, noFollow },
openGraph {
title, description, url, siteName, type,
image { asset-> { url } }
},
twitter {
card, site, creator, title, description,
image { asset-> { url } }
},
metaAttributes[] {
_key, attributeKey, attributeType, attributeValueString,
attributeValueImage { asset-> { url } }
}
}
}Next.js
components/SEOHead.tsx
import Head from 'next/head'
export function SEOHead({ seo }) {
if (!seo) return null
return (
<Head>
{seo.title && <title>{seo.title}</title>}
{seo.description && (
<meta name="description" content={seo.description} />
)}
{/* Open Graph */}
{seo.openGraph?.title && (
<meta property="og:title" content={seo.openGraph.title} />
)}
{seo.openGraph?.description && (
<meta property="og:description" content={seo.openGraph.description} />
)}
{seo.openGraph?.url && (
<meta property="og:url" content={seo.openGraph.url} />
)}
{seo.openGraph?.siteName && (
<meta property="og:site_name" content={seo.openGraph.siteName} />
)}
{seo.openGraph?.type && (
<meta property="og:type" content={seo.openGraph.type} />
)}
{seo.openGraph?.image?.asset?.url && (
<meta property="og:image" content={seo.openGraph.image.asset.url} />
)}
{/* X (Twitter) Card */}
{seo.twitter?.card && (
<meta name="twitter:card" content={seo.twitter.card} />
)}
{seo.twitter?.site && (
<meta name="twitter:site" content={seo.twitter.site} />
)}
{seo.twitter?.creator && (
<meta name="twitter:creator" content={seo.twitter.creator} />
)}
{seo.twitter?.title && (
<meta name="twitter:title" content={seo.twitter.title} />
)}
{seo.twitter?.description && (
<meta name="twitter:description" content={seo.twitter.description} />
)}
{/* Robots */}
{(seo.robots?.noIndex || seo.robots?.noFollow) && (
<meta
name="robots"
content={[
seo.robots.noIndex && 'noindex',
seo.robots.noFollow && 'nofollow',
].filter(Boolean).join(', ')}
/>
)}
{/* Canonical URL */}
{seo.canonicalUrl && (
<link rel="canonical" href={seo.canonicalUrl} />
)}
{/* Keywords */}
{seo.keywords?.length > 0 && (
<meta name="keywords" content={seo.keywords.join(', ')} />
)}
</Head>
)
}Next.js App Router (Metadata API)
app/[slug]/page.tsx
import type { Metadata } from 'next'
import { client } from '@/sanity/client'
export async function generateMetadata({ params }): Promise<Metadata> {
const page = await client.fetch(
`*[_type == "page" && slug.current == $slug][0]{ seo }`,
{ slug: params.slug }
)
const seo = page?.seo
return {
title: seo?.title,
description: seo?.description,
keywords: seo?.keywords,
alternates: {
canonical: seo?.canonicalUrl,
},
openGraph: {
title: seo?.openGraph?.title,
description: seo?.openGraph?.description,
url: seo?.openGraph?.url,
siteName: seo?.openGraph?.siteName,
type: seo?.openGraph?.type,
images: seo?.openGraph?.image?.asset?.url
? [{ url: seo.openGraph.image.asset.url }]
: undefined,
},
twitter: {
card: seo?.twitter?.card,
site: seo?.twitter?.site,
creator: seo?.twitter?.creator,
title: seo?.twitter?.title,
description: seo?.twitter?.description,
},
robots: {
index: !seo?.robots?.noIndex,
follow: !seo?.robots?.noFollow,
},
}
}React / Gatsby
SEO.tsx
import { Helmet } from 'react-helmet'
export function SEO({ seo }) {
return (
<Helmet>
<title>{seo?.title}</title>
<meta name="description" content={seo?.description} />
{seo?.keywords && (
<meta name="keywords" content={seo.keywords.join(', ')} />
)}
{/* Open Graph */}
<meta property="og:title" content={seo?.openGraph?.title} />
<meta property="og:description" content={seo?.openGraph?.description} />
<meta property="og:url" content={seo?.openGraph?.url} />
<meta property="og:type" content={seo?.openGraph?.type || 'website'} />
{/* X (Twitter) Card */}
{seo?.twitter?.card && (
<meta name="twitter:card" content={seo.twitter.card} />
)}
{seo?.twitter?.site && (
<meta name="twitter:site" content={seo.twitter.site} />
)}
{seo?.twitter?.creator && (
<meta name="twitter:creator" content={seo.twitter.creator} />
)}
</Helmet>
)
}