How to retrieve Sanity data for meta tags across all Next.js pages?

14 replies
Last updated: Sep 8, 2022
Hello! I'm setting up a NextJS app with Sanity, and am wondering about how to retrieve values for the
<meta>
tags. It looks like Next only allows
getStaticProps
or
getServerSideProps
to be run on individual pages, not the
_app
or
_document
pages. Will I need to run the same query and apply meta tags on every page? Seems like there must be a single place to grab this data and include it on every page 🤔
AI Update

Good question! You're right that this seems inefficient, but there are actually several approaches to handle site-wide metadata in Next.js with Sanity without duplicating queries on every page.

Modern Approach: Next.js App Router with Layouts

If you're using Next.js 13+ with the App Router, you can fetch global metadata in your root layout.tsx file. Unlike _app.tsx in the Pages Router, layouts in the App Router support data fetching directly:

// app/layout.tsx
import { client } from '@/sanity/lib/client'

async function getGlobalSettings() {
  return await client.fetch(`*[_type == "siteSettings"][0]{
    title,
    description,
    ogImage
  }`)
}

export async function generateMetadata() {
  const settings = await getGlobalSettings()
  
  return {
    title: settings.title,
    description: settings.description,
    openGraph: {
      images: [settings.ogImage],
    },
  }
}

export default async function RootLayout({ children }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}

The App Router's React Server Components allow you to fetch data at the layout level, and you can use Next.js's generateMetadata function to set meta tags.

Pages Router Approach: Global Settings Pattern

If you're using the Pages Router (with _app.tsx and _document.tsx), you have a couple of options:

1. Create a "siteSettings" singleton document in Sanity and fetch it on each page, but pass it down through props to avoid re-querying:

// pages/index.tsx
export async function getStaticProps() {
  const [pageData, globalSettings] = await Promise.all([
    client.fetch(`*[_type == "page" && slug.current == "/"][0]`),
    client.fetch(`*[_type == "siteSettings"][0]`)
  ])
  
  return {
    props: { pageData, globalSettings }
  }
}

2. Use React Context to share global settings across components after fetching them once per page load.

3. Fetch in _app.tsx using getInitialProps (though this disables automatic static optimization):

// pages/_app.tsx
MyApp.getInitialProps = async (appContext) => {
  const globalSettings = await client.fetch(`*[_type == "siteSettings"][0]`)
  return { globalSettings }
}

This last approach works but has performance tradeoffs since it opts your entire app out of static generation.

The cleanest approach is typically to:

  1. Create a siteSettings singleton document type in Sanity
  2. Fetch it alongside your page-specific data in each page's getStaticProps/getServerSideProps
  3. Use Next.js's next/head component to set meta tags per page

While it means including the query on each page, Sanity's Content Lake is fast, and with proper caching/revalidation, the overhead is minimal. Plus, you maintain the benefits of static generation.

If you're starting fresh, I'd strongly recommend the App Router approach—it's much more ergonomic for this exact use case!

Show original thread
14 replies
if your meta tags are different for individual pages, you’ll want to use something like Next SEO — this will resolve merge differences in your
<head>
if you just need one set of meta tags, doing this in the _app or _document should be fine 🙂
But can I query Sanity from _app or _document? It seems like I can only do that client-side (no getStaticProps or getServerSideProps), and I was hoping to include these meta tags in the server-rendered page to ensure they're visible to search engines
Will I need to run the same query and apply meta tags on every page?
Yes, if you want to benefit from static builds.
Thanks, fortunately I just have a few pages on this site so it shouldn't be too bad. Just making sure I'm not missing anything obvious for doing it in a single location
you can render server side from
_app
— using
getInitialProps
rather than
getStaticProps
but that’s how I render my homepages for all the websites I build, fetching Sanity data.

https://nextjs.org/docs/api-reference/data-fetching/get-initial-props

For the initial page load,
getInitialProps
will run on the server only.
getInitialProps
will then run on the client when navigating to a different route via the
next/link
component or by using
next/router
. However, if
getInitialProps
is used in a custom
_app.js
, and the page being navigated to implements
getServerSideProps
, then
getInitialProps
will run on the server.
It disables static optimization though. 😔
but only for pages without
getStaticProps
right?
(as far as I understand it anyway)
That's not how I understand it.
the documentation says
Adding a custom
getInitialProps
in your
App
will disable Automatic Static Optimization in pages without Static Generation .
but they don’t go into details about it. I would love to know more from a nextjs developer
I am pretty sure if you enable
getInitialProps
on the
_app.js
level, you lose static optimization entirely. That makes only sense since it‘s a SSR function. If it’s on, then all pages need to use it, so it’s SSR only. At least that’s how I get it.
Hello wonderful people 👀
I was wondering that too at one point and I think giving up automatic static optimisation for meta in
_app
is nor worth it.What I instead do is:
create a
Layout
component, which is the meta wrapper on every site and add a specific
Head
and
title
within that Layout (sometimes I want to change those through some extra 💅 and this is why I have it outside of Layout)

//index.jsx where I query for siteSettings

export default function Index({ data, preview, setModal, modal }) {

const { image, title, description, siteSettings } = data?.page;

  return(
     <Layout preview={preview} description={description} image={image} siteSettings={siteSettings}>
       <Head>
         <title>MY AWESOME SITE - {title}</title>
       </Head>
     {/* ALL MY COMPONENTS */}
   </Layout>
  )
}
...

// in my query 
...,
  'siteSettings': *[_type == 'siteSettings'][0]
...,

siteSettings
are the overall settings for the whole site, which means, that there is some routing included, which kind of menu items the site should have etc. but also the default
meta
information.
What most people forget is that you can pass props down to components but you can also “bubble them up”, which is strangely easy in React/NextJS:


// _App.js

function MyApp({ Component, pageProps }) { ...

return (
    <>
      <Head>
        <meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=yes, user-scalable=yes, viewport-fit=cover' />
      </Head>

      {/*  <Header /> */}
      <MenuBar siteSettings={pageProps.data.page.siteSettings} />


      <Modals modal={modal} setModal={setModal} siteSettings={pageProps.data.page.siteSettings} />

      <Component {...pageProps} modal={modal} setModal={setModal} />
    </>
  )
}
As you can see you can access your queried data, from the
pageProps
, if you set it up that way 🙂
BUT: there are new things coming out in Next JS soon :party_parrot:
https://nextjs.org/blog/layouts-rfc 🎉

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?