user12457151
user12457151

Reputation: 1021

How to do server side data fetching only once for pages sharing a persistent layout in Next.js?

I have a persistent layout that needs data to be fetched on the server side. Since you can only call getServerSideProps from a page, my solution is to fetch the data from the page and then pass it to my layout. The issue is that the layout should persist across pages, and this requires me to refetch the data on each page change.

Folder structure:



├── /collection

│   ├──[contractaddress]

│   │  ├── dashboard.tsx
│   │  ├── items.tsx
│   │  ├── analytics.tsx

Example of a URL: "/collection/0xed5af388653567af2f388e6224dc7c4b3241c544/dashboard"

Dashboard, analytics, and items are all different views of a collection, and share a CollectionLayout.

I've been able to follow Adam Wathan's guide on persistent layouts, which has worked great. Components in CollectionLayout which are shared across the views persist state across the different views.

I'm using getServerSideProps to fetch data about the collection and then I pass it back to CollectionLayout like so:

Dashboard.getLayout = function getLayout(page: ReactElement) {
  return (
    <CollectionLayout collection={page.props.collection}>
      {page}
    </CollectionLayout>
  );
};

I'm running into a few problems:

My ideal state is to make 1 request per [contractaddress]. That data is then passed to CollectionLayout which persists across views.

I did see that there are updates coming to layouts (https://nextjs.org/blog/layouts-rfc). The ability to call getServerSideProps in a layout file would solve all of my problems.

In the meantime, is there any way for me to get around this in Next 12.2.2?

dashboard.tsx

import { GetServerSideProps } from "next";
import { NextSeo } from "next-seo";
import type { ReactElement } from "react";
import { CollectionLayout } from "../../../../layouts/CollectionLayout";
import type { NextPageWithLayout } from "../../../_app";

type Props = {
  collection: {
    contractAddress: string;
    name: string;
    description: string;
    image: string;
  };
};

const Dashboard: NextPageWithLayout<Props> = ({ collection }) => {
  return (
    <div className="w-full">
      <NextSeo title={collection.name} description={collection.description} />
      <div className="bg-purple-400 h-[400px] w-[500px]">Dashboard</div>
    </div>
  );
};

// This gets called on every request
export const getServerSideProps: GetServerSideProps = async () => {
  // This data should only be fetched once and then persisted across views
  const res = await fetch("http://localhost:3001/0xed5af388653567af2f388e6224dc7c4b3241c544");
  const collection = await res.json();
  return { props: { collection: collection } };
};

Dashboard.getLayout = function getLayout(page: ReactElement) {
  return (
    <CollectionLayout collection={page.props.collection}>
      {page}
    </CollectionLayout>
  );
};

export default Dashboard;

Upvotes: 2

Views: 1501

Answers (1)

Chukwuemeka Maduekwe
Chukwuemeka Maduekwe

Reputation: 8586

There are different ways to persist state in React, the best would be to use Redux or better still, to pass your data as props from to that way every direct Child Component of will have access to the data coming from the Parent component. See this template.

Pass 'Component' prop to Layout Component from app.js

app.jsx

const App = ({ Component, pageProps, seoDataFromGetServerSideProps }) => (
  <Layout {...{ pageProps, Component, seoDataFromGetServerSideProps }} />
);

Layout.jsx

const Layout = ({ Component, pageProps, seoDataFromGetServerSideProps }) => (
  <>
    <Head>
      {/* pass your SEO data here from seoDataFromGetServerSideProps */}
      <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
      <meta httpEquiv="Content-Type" content="text/html; charSet=utf-8" />

      <meta name="theme-color" content={seoDataFromGetServerSideProps.theme} />
      <meta name="keywords" content={seoDataFromGetServerSideProps.title} />
      <meta httpEquiv="Content-Type" content="text/html; charSet=utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta name="description" content={seoDataFromGetServerSideProps.title} />
    </Head>
    <HeaderContainer />
    <Component {...pageProps} seoDataFromGetServerSideProps={seoDataFromGetServerSideProps } />
    <FooterContainer />
  </>
);

export default Layout;
const HomePage = ({seoDataFromGetServerSideProps }) => (
  // return home page 
)

const AboutPage = ({seoDataFromGetServerSideProps }) => (
  // return about page 
)

Both HomePage and AboutPage will have whatever you've passed Layout

I hope you get the Idea.

Upvotes: -1

Related Questions