Skip to main content
seofields
docs

Frontend Integration

Query SEO data via GROQ and render meta tags in your frontend framework. Use the built-in helpers for zero-boilerplate integration.

✨ Built-in helpers (v1.2.4+)

The plugin ships buildSeoMeta() and <SeoMetaTags> so you don't have to map every field manually. Both are exported from sanity-plugin-seofields/next — a dedicated entry point for use in Next.js Server Components and generateMetadata() functions.

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,
      imageType, imageUrl,
      image { asset-> { url }, alt }
    },
    twitter {
      card, site, creator, title, description,
      imageType, imageUrl,
      image { asset-> { url }, alt }
    },
    metaAttributes[] { _key, key, type, value }
  }
}

Next.js App Router — buildSeoMeta()

Use buildSeoMeta() inside generateMetadata(). The return value is structurally compatible with Next.js's Metadata type — no manual field mapping needed.

app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { buildSeoMeta } from 'sanity-plugin-seofields/next'
import { sanityFetch } from '@/sanity/lib/live'
import { urlFor } from '@/sanity/lib/image'
import { SEO_QUERY } from '@/sanity/lib/queries'

export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = await params
  const { data } = await sanityFetch({ query: SEO_QUERY, params: { slug } })

  return buildSeoMeta({
    seo: data?.seo,
    baseUrl: 'https://example.com',
    path: `/blog/${slug}`,
    defaults: {
      title: 'My Site',
      siteName: 'My Site',
      twitterSite: '@mysite',
    },
    // Optional: resolve Sanity image refs to full URLs
    imageUrlResolver: (img) => urlFor(img).width(1200).url(),
  })
}

Next.js App Router — <SeoMetaTags>

Renders <title>, <meta>, and <link rel="canonical"> tags as plain React elements. Next.js hoists them to <head> automatically. Import from sanity-plugin-seofields/next to use it in Next.js Server Components and generateMetadata() functions.

app/blog/[slug]/page.tsx
import { SeoMetaTags } from 'sanity-plugin-seofields/next'
import type { SeoFields } from 'sanity-plugin-seofields'
import { sanityFetch } from '@/sanity/lib/live'
import { urlFor } from '@/sanity/lib/image'
import { BLOG_QUERY } from '@/sanity/lib/queries'

export default async function Page({ params }) {
  const { slug } = await params
  const { data } = await sanityFetch({ query: BLOG_QUERY, params: { slug } })

  return (
    <>
      {data.seo && (
        <SeoMetaTags
          data={data.seo as SeoFields}
          baseUrl="https://example.com"
          path={`/blog/${slug}`}
          defaults={{ title: 'My Site', siteName: 'My Site' }}
          imageUrlResolver={(img) => urlFor(img).width(1200).url()}
        />
      )}
      <main>...</main>
    </>
  )
}

Next.js Pages Router

Place <SeoMetaTags> inside Next.js <Head>.

pages/blog/[slug].tsx
import Head from 'next/head'
import { SeoMetaTags } from 'sanity-plugin-seofields/next'
import { urlFor } from '@/sanity/lib/image'


export default function Page({ data }) {
  return (
    <>
      <Head>
        <SeoMetaTags
          data={data.seo}
          baseUrl="https://example.com"
          path={`/blog/${data.slug}`}
          defaults={{ title: 'My Site', siteName: 'My Site' }}
          imageUrlResolver={(img) => urlFor(img).width(1200).url()}
        />
      </Head>
      <main>...</main>
    </>
  )
}

buildSeoMeta() Options

BuildSeoMetaOptions
interface BuildSeoMetaOptions {
  /** Raw SEO object from Sanity. Pass null/undefined to use only defaults. */
  seo?: SeoFieldsInput | null

  /** Base URL of your site, e.g. "https://example.com". Used for canonical + og:url. */
  baseUrl?: string

  /** Current page path, e.g. "/about". Combined with baseUrl for canonical + og:url. */
  path?: string

  /** Fallback values used when SEO fields are missing. */
  defaults?: {
    title?: string
    description?: string
    siteName?: string
    twitterSite?: string
    twitterCreator?: string
    /** Fallback OG/Twitter image URL when no image is set in Sanity. */
    ogImage?: string
  }

  /** Resolve a Sanity image asset reference to a full URL string. */
  imageUrlResolver?: (image: SanityImage | SanityImageWithAlt) => string | null | undefined
}

React / Gatsby (manual)

For non-Next.js frameworks, use <SeoMetaTags> inside react-helmet or any head management library, or map fields manually:

components/SEO.tsx
import { Helmet } from 'react-helmet'

export function SEO({ seo }) {
  if (!seo) return null
  const robotsContent = [
    seo.robots?.noIndex ? 'noindex' : 'index',
    seo.robots?.noFollow ? 'nofollow' : 'follow',
  ].join(', ')

  return (
    <Helmet>
      {seo.title && <title>{seo.title}</title>}
      {seo.description && <meta name="description" content={seo.description} />}
      {seo.keywords?.length > 0 && <meta name="keywords" content={seo.keywords.join(', ')} />}
      <meta name="robots" content={robotsContent} />
      {seo.canonicalUrl && <link rel="canonical" href={seo.canonicalUrl} />}

      {/* 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} />}

      {/* 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} />}
    </Helmet>
  )
}

Was this page helpful?