How to populate persistent components with Sanity data in _app.js?

19 replies
Last updated: Feb 13, 2021
I'm curious how people here handle populating components at the
_app.js
level with data from Sanity? For example, I have a
Header.js
component that should persist across pages, but I'd also like this Header to use data from Sanity, ideally fetching at build time. But what's the best way to get this data to the Header? Any thoughts would be greatly appreciated
AI Update

Good question! Since Next.js doesn't support getStaticProps in _app.js, you have a few solid patterns for handling shared data like headers across pages.

For Pages Router

The most common approach is to fetch your header data in each page's getStaticProps and pass it to the shared component. While this seems repetitive, you can create a reusable helper function:

// lib/getSharedData.js
import { client } from './sanity'

export async function getSharedData() {
  const headerData = await client.fetch(`*[_type == "siteSettings"][0]{
    logo,
    navigation
  }`)
  return { headerData }
}

Then in each page:

export async function getStaticProps() {
  const { headerData } = await getSharedData()
  const pageData = await client.fetch(/* your page query */)
  
  return {
    props: {
      headerData,
      pageData
    }
  }
}

Pass headerData to your Header component from each page's props. Yes, it's a bit repetitive, but it keeps everything statically generated at build time.

For App Router (Next.js 13+)

If you're using the modern App Router, this gets much cleaner. You can fetch data directly in your root layout.tsx using React Server Components:

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

async function getHeaderData() {
  return await client.fetch(`*[_type == "siteSettings"][0]`)
}

export default async function RootLayout({ children }) {
  const headerData = await getHeaderData()
  
  return (
    <html>
      <body>
        <Header data={headerData} />
        {children}
      </body>
    </html>
  )
}

The layout fetches once per request and persists across page navigations. You can add export const revalidate = 3600 to control caching behavior.

Alternative: Global Singleton Pattern

Another approach is to structure your Sanity schema with a global settings singleton document that you query consistently. This makes your queries more predictable and your data structure clearer - you can read more about approaches to pulling global settings from Sanity into a Next.js app.

The App Router approach is definitely the most elegant if you're able to upgrade, but the helper function pattern works great for Pages Router projects and keeps your code DRY.

Show original thread
19 replies
I’ve added a script that runs before next build which fetches the data and saves it as a JSON file in the public folder. Then I just read the data from the JSON file.
The downside is that you lose preview that way, but unless you fetch it on every page I don’t see a solution to that.
I wanted to keep preview so i made next-data-hooks but unfortunately it does re-fetch on every page. This is how my layout component looks
user S
I was actually thinking about creating a next.js plugin/webpack-loader that would pre-evaluate an async function during the build and serve it as a json file.
Yeah I think you have to make that compromise if you want to keep preview. Your data hooks are a pretty neat solution to it to avoid repeating yourself on every page.
The JSON file works pretty well, but every now and then I forget that it doesn’t update automatically, even in dev mode, and wonder what I’m doing wrong. Then I remember that I have to restart the dev server
😅
My initial solution was to use the webpack step in the build, but it didn’t work in Vercel for some reason so I had to run the script before the build altogether.
Another gotcha with the webpack step as defined in next.config.js was that TS isn’t supported. So I had to write a JS script for that while everything else is TS.
I just created this next.js package to solve this kind of problem. I would love feedback if any of y’all would like to try it: https://github.com/ricokahler/next-plugin-preval
Thanks both for the info. Funnily enough Rico, I follow you on Github and saw this earlier today. Looking forward to trying both of these methods out at some point this week
I need to test this!
This looks fantastic
user X
– I’m currently using
val-loader
to accomplish this, but this looks like a great alternative
user G
yeah i saw that prior to making this plugin but i couldn’t use typescript with it easily and it didn’t have enough integration into the next world so i decided to go more specific.
a lot of the time spent getting this working was actually the babel config and tsconfig paths integration (since next.js supports them)
If any of y’all try it or have any suggestions, feel free to send any feedback my way. I’m looking to promote this on reddit, hacker news, etc around friday-ish
Def post it here (and those linked threads) too! https://github.com/vercel/next.js/discussions/14949
Anyone got next-plugin-preval to work? I keep getting
Error: Next Plugin Preval could not find webpack rules. This may be an unsupported version of Next.js.
following the README w/ next v10.0.6 (
user X
)
user M
can you file an issue if you haven’t already? (that’s not supposed to happen 😅)
Including your next.config.js and your package.json
The bug has been squashed. Thanks for reporting it!

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?