Fetching Sanity site config in Next.js _app.tsx returns empty in pageProps
pageProps.config = client.fetch(siteConfigQuery).then((config) => {
return config;
}); const siteConfigQuery = groq`
*[_type == "site-config"][0] {
...,
logo {asset->{extension, url}},
mainNavigation[] -> {
...,
"title": page->title
},
footerNavigation[] -> {
...,
"title": page->title
}
}
`;
export default function MyApp(props: AppProps) {
const { Component, pageProps } = props;
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement?.removeChild(jssStyles);
}
}, []);
const router = useRouter();
// Initiate GTM
useEffect(() => {
const handleRouteChange = (url: string) => GTMPageView(url);
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, []);
// Add site config from sanity
pageProps.config = client.fetch(siteConfigQuery).then((config) => {
return config;
});
return (<>
{/* <CacheProvider value={cache}> */}
<Head>
<title>The Togetherworks</title>
<meta
name="viewport"
content="initial-scale=1, width=device-width"
/>
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles />
<Component {...pageProps} />
</ThemeProvider>
{/* </CacheProvider> */}
</>
);
}The issue you're facing is that you're assigning a Promise to pageProps.config instead of the resolved data. When you do client.fetch(siteConfigQuery).then(...), that returns a Promise, not the actual config data.
The solution is to use getInitialProps in your _app.tsx to fetch the data server-side before rendering, just like that landing pages template does. Here's how to fix it:
import App, { AppContext, AppProps } from 'next/app';
export default function MyApp(props: AppProps) {
const { Component, pageProps } = props;
// Your existing useEffect hooks...
return (
<>
<Head>
<title>The Togetherworks</title>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<GlobalStyles />
<Component {...pageProps} />
</ThemeProvider>
</>
);
}
MyApp.getInitialProps = async (appContext: AppContext) => {
// Fetch site config
const config = await client.fetch(siteConfigQuery);
// Call page-level getInitialProps if it exists
const appProps = await App.getInitialProps(appContext);
// Merge config into all pages' props
return {
...appProps,
pageProps: {
...appProps.pageProps,
config
}
};
};Important caveat: Adding getInitialProps to _app will disable Automatic Static Optimization for all your pages. This means pages using getStaticProps will lose some of their static optimization benefits, as Next.js will switch to server-side rendering for every request instead of serving pre-built static HTML.
Better alternatives for modern Next.js:
- Fetch in each page's
getStaticProps/getServerSideProps: This keeps static optimization working. You can create a helper function to fetch config and reuse it across pages:
// lib/sanity.ts
export async function getConfig() {
return client.fetch(siteConfigQuery);
}
// pages/index.tsx
export async function getStaticProps() {
const config = await getConfig();
return { props: { config } };
}Use React Context with initial data: Fetch config in individual pages and pass it through context to your layout/navigation components.
For Next.js 13+ App Router: Use React Server Components to fetch config in your root layout, which is much cleaner and doesn't have the same trade-offs.
Since you're on Next.js 10, if you need the config globally and the SSR trade-off is acceptable for your use case, the getInitialProps approach will work. Just be aware that it means every page will be server-rendered on each request rather than being statically generated at build time.
Show original thread6 replies
Was this answer helpful?
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.