I have a menu component that appears globally. What is the best practice for getting data into that component?
I'm trying to take advantage of static generation that Next.js offers but all data fetching guidance from the Next.js team relates to pages. getStaticProps
and getStaticPaths
seem to pertain to page generation, not data for components. Is their SWR
package the right answer, or Apollo Client?
Typically in hooks-based React, I'd just put my data call into useEffect
. I'm not sure how to reason this out being that everything is rendered at build time with Next.
Data fetching in Next. js allows you to render your content in different ways, depending on your application's use case. These include pre-rendering with Server-side Rendering or Static Generation, and updating or creating content at runtime with Incremental Static Regeneration.
You need to update the state after your data returns so that the component can rerender with the fetched data, using something like setTrack(track) . Then in your component you can pass the data from the state.
getStaticProps can only be exported from a page. You can't export it from non-page files.It will not work if you add getStaticProps as a property of the page component.
This is such a tricky problem, I think we need to lay out some background before a solution comes into focus. I'm focusing in the React.js world but a lot of this would apply to Vue/Nuxt I'd imagine.
Gatsby and Next are focused on generating static pages, which vastly improves performance and SEO in React.js sites. There is a lot of technical overhead to both platforms beyond this simple insight but let's start with this idea of a digital machine pumping out fancy HTML pages for the browser.
In the case of Next.js (as of v9.5
), their data fetching mechanism getStaticProps
does most of the heavy lifting for you but it's sandboxed to the /pages/
directory. The idea is that it does the data fetching for you and tells the Next.js page generator in Node about it during build time (instead of doing it component-side in a useEffect
hook - or componentDidMount
). Gatsby does much the same with their gatsby-node.js
file, which orchestrates the data fetching for page building in concert with a Node server.
You can use both Gatsby and Next to produce any kind of website but a huge use case are CMS-driven websites, because so much of that content is static. These tools are an ideal fit to that use case.
In typical CMS sites, you will have elements that are global - header, footer, search, menu, etc. This is where static generation faces a big challenge: how do I get data into dynamic global components at build time? The answer to this question is... you don't. And if you think about this for a minute it makes sense. If you had a 10K page site, would you want to trigger a site-wide rebuild if someone adds a new nav item to a menu?
So how do we get around this? The best answer I have is apollo-client
and to do the fetch client side. This helps us for a number of reasons:
So what does this actually look like? At the component level, it looks just like a regular Apollo-enhanced component would. I usually use styled-components
but I tried to strip that out so you can could better see what's going on.
import React from 'react'
import { useQuery, gql } from '@apollo/client'
import close from '../public/close.svg'
/**
* <NavMenu>
*
* Just a typical menu you might see on a CMS-driven site. It takes in a couple of props to move state around.
*
* @param { boolean } menuState - lifted state true/false toggle for menu opening/closing
* @param { function } handleMenu - lifted state changer for menuState, handles click event
*/
const NAV_MENU_DATA = gql`
query NavMenu($uid: String!, $lang: String!) {
nav_menu(uid: $uid, lang: $lang) {
main_menu_items {
item {
... on Landing_page {
title
_linkType
_meta {
uid
id
}
}
}
}
}
}
`
const NavMenu = ({ menuState, handleMenu }) => {
// Query for nav menu from Apollo, this is where you pass in your GraphQL variables
const { loading, error, data } = useQuery(NAV_MENU_DATA, {
variables: {
"uid": "nav-menu",
"lang": "en-us"
}
})
if (loading) return `<p>Loading...</p>`;
if (error) return `Error! ${error}`;
// Destructuring the data object
const { nav_menu: { main_menu_items } } = data
// `menuState` checks just make sure out menu was turned on
if (data) return(
<>
<section menuState={ menuState }>
<div>
{ menuState === true && (
<div>Explore</div>
)}
<div onClick={ handleMenu }>
{ menuState === true && (
<svg src={ close } />
)}
</div>
</div>
{ menuState === true && (
<ul>
{ data.map( (item) => {
return (
<li link={ item }>
{ item.title }
</li>
)
})}
</ul>
)}
</section>
</>
)
}
export default NavMenu
This is actually really well documented by the Next.js team, which makes me feel like I'm not totally hacking the way this tool is supposed to work. You can find great examples of using Apollo in their repo.
Steps to get Apollo into a Next app:
useApollo
hook that sets up the connection to your data source (I put mine in /lib/apollo/apolloClient.js
within Next's hierarchy but I'm sure it could go elsewhere).import { useMemo } from 'react'
import { ApolloClient, InMemoryCache, SchemaLink, HttpLink } from '@apollo/client'
let apolloClient
// This is mostly from next.js official repo on how best to integrate Next and Apollo
function createIsomorphLink() {
// only if you need to do auth
if (typeof window === 'undefined') {
// return new SchemaLink({ schema })
return null
}
// This sets up the connection to your endpoint, will vary widely.
else {
return new HttpLink({
uri: `https://yourendpoint.io/graphql`
})
}
}
// Function that leverages ApolloClient setup, you could just use this and skip the above function if you aren't doing any authenticated routes
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Restore the cache using the data passed from getStaticProps/getServerSideProps
// combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState })
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
// This is goal, now we have a custom hook we can use to set up Apollo across our app. Make sure to export this!
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
_app.js
in the /pages/
directory of Next. This is basically the wrapper that goes around every page in Next. We're going to add the Apollo provider to this, and now we can globally access Apollo from any component.import { ApolloProvider } from '@apollo/react-hooks'
import { useApollo } from '../lib/apollo/apolloClient'
/**
* <MyApp>
*
* This is an override of the default _app.js setup Next.js uses
*
* <ApolloProvider> gives components global access to GraphQL data fetched in the components (like menus)
*
*/
const MyApp = ({ Component, pageProps }) => {
// Instantiates Apollo client, reads Next.js props and initialized Apollo with them - this caches data into Apollo.
const apolloClient = useApollo(pageProps.initialApolloState)
return (
<ApolloProvider client={ apolloClient }>
<Component {...pageProps} />
</ApolloProvider>
)
}
export default MyApp
And now you can get dynamic data inside of your components using Apollo! So easy right ;) HA!
For global data fetching in NextJS, I use react-query
and there is no need for a global state because it lets you to cache the data. Let's say you have a blog with categories and you want to put the category names in the navbar
as a dropdown menu. In this case you can call the API to fetch the data with react-query
from the navbar
component and cache it. The navbar data will be available for all pages.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With