Skip to main content
seofields
docs

Hreflang — International SEO

The hreflangs field lets editors configure language and region alternates directly in Sanity Studio using BCP 47 tags (e.g. en, fr-FR, x-default). The entries are stored on the document and queried via GROQ so your frontend can render the correct <link rel="alternate"> tags.

About hreflang

Google uses hreflang annotations to serve the correct language/region version of a page to the right audience. Each entry maps a BCP 47 locale tag (en-US, de, etc.) to the absolute URL of the alternate page. Always include an x-default entry for the catch-all / language selector URL.

hreflangEntry Schema

Each item in the hreflangs array is an hreflangEntry object with two validated fields:

FieldTypeRequiredDescription
localestringYesBCP 47 tag — e.g. en, fr-FR, x-default. Validated with a regex pattern in Studio.
urlurlYesThe absolute URL of the alternate page (must be http or https).

Plugin Setup

The hreflangs field is part of the seoFields schema and is registered automatically. No extra configuration is needed:

sanity.config.ts
import seofields from 'sanity-plugin-seofields'
import { defineConfig } from 'sanity'

export default defineConfig({
  plugins: [
    seofields(),   // hreflangs array is included automatically
  ],
})

Using with Field Groups

Place hreflangs in its own field group tab for cleaner editing:

sanity.config.ts
seofields({
  fieldGroups: [
    {
      name: 'meta',
      title: 'Meta',
      default: true,
      fields: ['title', 'description', 'metaImage', 'keywords', 'canonicalUrl', 'robots'],
    },
    { name: 'openGraph', title: 'Open Graph', fields: ['openGraph'] },
    { name: 'twitter',   title: 'Twitter Card', fields: ['twitter'] },
    {
      name: 'international',
      title: 'International',
      fields: ['hreflangs'],   // ← dedicated tab
    },
  ],
})

Hiding the field

Hide hreflangs globally or per document type if you don't need international SEO:

sanity.config.ts
seofields({
  // Hide globally
  defaultHiddenFields: ['hreflangs'],

  // — or — hide per document type
  fieldVisibility: {
    internalPage: { hiddenFields: ['hreflangs'] },
  },
})

GROQ Query

GROQ query
*[_type == "page" && slug.current == $slug][0]{
  title,
  seo {
    title,
    description,
    hreflangs[] {
      locale,
      url
    }
  }
}

Rendering in Next.js

Pass the queried hreflang entries to Next.js generateMetadata() via the alternates property:

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

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

  const base = buildSeoMeta({ seo: data?.seo, baseUrl: 'https://example.com', path: `/${slug}` })

  // Build hreflang alternates from Sanity data
  const languages = data?.seo?.hreflangs?.reduce(
    (acc: Record<string, string>, entry: { locale: string; url: string }) => {
      acc[entry.locale] = entry.url
      return acc
    },
    {} as Record<string, string>,
  )

  return {
    ...base,
    alternates: {
      ...base.alternates,
      languages,
    },
  }
}

Manual <link> tags

For non-Next.js frameworks or the Pages Router, render the <link rel="alternate"> tags manually:

app/layout.tsx
export default function Layout({ page }) {
  return (
    <html>
      <head>
        {page.seo?.hreflangs?.map(({ locale, url }) => (
          <link key={locale} rel="alternate" hrefLang={locale} href={url} />
        ))}
      </head>
      <body>...</body>
    </html>
  )
}

Best practice: Always add an x-default entry pointing to your language selector or default language URL. Every page in your hreflang set must also include a reciprocal annotation pointing back — Google ignores one-sided annotations.

TypeScript

TypeScript usage
import type { SeoFields } from 'sanity-plugin-seofields'

// hreflangs is typed on SeoFields
const entries: Array<{ locale: string; url: string }> | undefined =
  document.seo?.hreflangs

Was this page helpful?