74

I'm looking for solutions for better data fetching in a Next.js app. In this question I'm not just looking for a solution, I'm looking for multiple options so we can look at the pros and cons.

The problem I have

Right now I have a few pages that all include a component that displays som static content and a that have some dynamic content that is fetched from an API. Each page do a fetch() in their getInitialProps() to get their own page data, but also the footer data, which is the same for all pages.

This of course works, but there is a lot of duplicated data fetching. The footer data will always be displayed for all pages and always be the same. It will also rarely be changed in the API, so no need for revalidate the data.

The answers I'm looking for

I'm not just looking to solve this one problem, I'm looking for an overview to learn some new practice for future projects as well. I like writing "obvious" code, so not looking for too hacky solutions, like writing to the window object etc. Simple solutions with less dependancies are preferred. The goal is a fast site. It's not that important to reduce network usage/API calls.

What I have thought so far

This is the possible solutions I've come up with, somewhat sorted from simple/obvious to more complex.

  1. Do a fetch inside the Footer component (client side)
  2. Do a fetch in getInitialProps (server side & client side) on all /pages
  3. Do a fetch in _app.js with a HOC and hooking into it's getInitialProps() and add it to props, so data is available for all pages
  4. Use zeit/swr and data prefetching to cache data
  5. Use redux to store a global state

All of these "work", but most of them will refetch the data unnecessarily, and/or adds a bit more complexity. Here are the pros/cons as I see it (numbers are the same as above):

  1. Simple! Fetch code is only in one place, it's located where it's used. Data is fetched after page is loaded, so the content "jumps" in to view. Data is refetched all the time.
  2. Simple! Data is fetched on the server, so content is available before the page is rendered. Data is refetched for each page. We have to remember to fetch the same footer data for each page in their getInitialProps().
  3. We can do the fetch in one place and add it to all the pages props, so footer data is automatically available for all pages' props. Might be a bit more complex for some to easily understand what's going on, as it requires a bit more understanding of how Next.js/React works. Still refetches the data for all pages. We now do two fetch() calls after each other (first in _app.js to load footer content, then in each page to get custom content), so it's even slower.
  4. Somewhat simple. We can use the prefetching to load data to cache even before the JS is loaded. After first page load, we will have fast data fetching. Can have fetch code directly in footer component. The rel="preload" prefetching technique won't work with all types of fetching (for instance Sanity's client using groq). To not have "jumpy" content where the data is loaded after initial page load, we should provide useSWR() with initialData which still will require us to fetch data in getInitialProps(), but it would be enough to just do this on the server side. Could use the new getServerSideProps().
  5. We can load data once(?) and have it available throughout the application. Fast and less/no refetching. Adds external dependency. More complex as you'll have to learn redux, even to just load one shared data object.

Current solution, using the solution described in bullet point number 2.

const HomePage = (props) => {
  return (
    <Layout data={props.footer}>
       <Home data={props.page} />
    </Layout>
  )
}

// Not actual query, just sample
const query = `{
  "page": *[_type == "page"][0], 
  "footer": *[_type == "footer"][0]
}`

HomePage.getInitialProps = async () => {
  const data = await client.fetch(query)

  return {
    page: data.page
    footer: data.footer
  }
}

export default HomePage

Would love some more insight into this. I'm a missing something obvious?

eivindml
  • 2,197
  • 7
  • 36
  • 68

1 Answers1

45

O'right! I found this thread while I was looking for something else. But since I had to work on similar issues, I can give you some directions, and I will do my best to make it clear for you.

So there are some data which you want to have it share, across your app (pages/components).

  1. Next.js uses the App component to initialize pages. You can override it and control the page initialization. to achieve that simply create _app.js file in root of pages directory. For more information follow this link: https://nextjs.org/docs/advanced-features/custom-app
  2. Just like the way you can use getInitialProps in your pages to fetch data from your API, you can also use the same method in _app.js. So, I would fetch those data which I need to share them across my app and eliminate my API calls.

Well, Now I can think of two ways to share the data across my app

  1. Using of createContext hooks.

1.1. Create a DataContext using createContext hooks. and wrap <Component {...pageProps} /> with your <DataContext.Provider>. Here is a code snippet to give you a better clue:

<DataContext.Provider value={{ userData, footerData, etc... }}>
  <Component {...pageProps} />
</DataContext.Provider>

1.2. Now in other pages/components you can access to your DataContext like following:

const { footerData } = useContext(DataContext); 

And then you are able to do the manipulation in your front-end

  1. populates props using getInitialProps

2.1. getInitialProps is used to asynchronously fetch some data, which then populates props. that would be the same case in _app.js.

The code in your _app.js would be something like this:

function MyApp({ Component, pageProps, footerData }) {
  //do other stuffs
  return (
   <Component {...pageProps} footerData={footerData} />
  ;
}

MyApp.getInitialProps = async ({ Component, ctx }) => {
  const footerRes = await fetch('http://API_URL');
  const footerData = await footerRes.json(); 
  let pageProps = {};
  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx);
  }
  return { pageProps, footerData };
};

2.2. Now in your pages (not in your components) you can access to props including those you have shared from _app.js and you can start to do you manipulation.

Hope I could give you a clue and direction. Have fun exploring.

Hooman
  • 1,775
  • 1
  • 16
  • 15
  • 2
    what if i want to pass this data to the nav component? – SalahAdDin Jul 16 '20 at 16:59
  • 2
    Let's say you have your index page with respective components.
    You have access to the props in your index and now all you wanna do it to pass it to your components. So you can easily handle it like this:
    and inside the Header component you can have child components like Nav bar in which you can extract data from header data now and assign it to your Nav component. So lets say this is how it might be look like in your header component Hope I could give you the clue.
    – Hooman Jul 17 '20 at 08:47
  • The navbar is part of the layout, you mean i should get all this data from the `_app.js` and from here i could pass the data to the components, right? – SalahAdDin Jul 17 '20 at 09:31
  • I just made a question for solving my problem, if you want you can answer there, i will accept it. – SalahAdDin Jul 17 '20 at 12:46
  • This is https://stackoverflow.com/questions/62940363/sharing-fetching-data-per-component-in-nextjs – SalahAdDin Jul 17 '20 at 12:52
  • When the page does not exist(404) or there is an internal server error, `navProps` will be a `{}`, empty object, why? how can i handle it? – SalahAdDin Jul 17 '20 at 16:31
  • I tried to pass it to the `defaultProps`, but it didn't work: https://stackoverflow.com/questions/56299970/nextjs-best-way-to-handle-connect-ownprops-getinitialprops – SalahAdDin Jul 17 '20 at 17:45
  • 21
    The problem with using getInitialProps in _app is that it disables automatic static optimization. So all pages will be server-side rendered, meaning you miss out on the benefits of pages being statically generated. From the Next.js docs: "Caveats If you have a custom App with getInitialProps then this optimization will be turned off in pages without Static Generation." - https://nextjs.org/docs/advanced-features/automatic-static-optimization – Ethan Ryan Feb 13 '21 at 00:43
  • 8
    In response to @EthanRyan, that only disables it in pages without static generation (https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation), not ALL pages. The pages that use `getStaticProps` will still be statically generated. – i-know-nothing Jul 12 '21 at 17:32
  • 2
    you are correct @i-know-nothing, i can't edit my comment but if i could i would amend it to say: "So all pages that are not explicitly using getStaticProps will be server-side rendered." – Ethan Ryan Jul 12 '21 at 23:03
  • It's said in NextJS wiki that `getInitialProps` only takes a single argment: https://nextjs.org/docs/api-reference/data-fetching/getInitialProps#context-object , but why in the code example above it has a `Component` destructuring arg? – avocado Oct 16 '21 at 22:55
  • 1
    It should be said that using `getInitialProps` in _app.js will disable ISR, as per the documents. Yes, your pages with `getStaticProps` will still be statically generated, but they will not update if that content is updated. – J. Adam Connor Jan 18 '22 at 15:11