2

I'm building a Next.js headless application, where I'm getting the data via API calls to an Umbraco backend. Im using getServerSideProps to load the data for each of my pages, which then is passed as "data" into the functional component and into the page.

The issue I have is that I have separate endpoints for the header / footer portion of the website, and is shared across all pages. Thus, it is a shame, and bad practice to do 3 calls per page (header, data, footer).

What could be done, in order to get header / footer once, then keep it across multiple pages, while maintaining SSR? (important). I've tried using cookies, but they cannot hold so much data. Below is some code:

Page Fetching data:

export async function getServerSideProps({ locale }) {
    const footer = await Fetcher(footerEndPoint, locale);

    return {
        props: {
            locale: locale,
            footer: footer
        }
    }
}

Layout

const Layout = (props) => {
    const { children, footer } = props;

    return (
        <>
            <Header />
            <main>
                {children}
            </main>
            <Footer footer={footer} />
        </>
    );
};

export default Layout;
2
  • 1
    Have you thought about creating a context to share this data globally? Commented May 7, 2021 at 12:58
  • 1
    I agree with @Gabriel22233 you can create a context Commented May 7, 2021 at 13:28

1 Answer 1

10

I see three options to achieve SSR-only data fetching once for things that won't ever change between page transitions:

1. getInitialProps() in _app.ts

You can just use getInitialProps() in _app.tsx. This runs on the server first and you can just cache the response value in a variable. Next time getInitialProps() is executed, it will just serve the cached value instead of firing another request. To make this work client-side, you have to rehydrate the cache variable in an useEffect:

// pages/_app.tsx
let navigationPropsCache

function MyApp({ Component, pageProps, navigationProps }) {
  
  useEffect(
    ()=>{
      navigationPropsCache = navigationProps
    }, 
    []
  )
    
    return <>
      <Navigation items={navigationProps}/>
      <Component {...pageProps} />
  </>
}

MyApp.getInitialProps = async () => {
  if(navigationPropsCache) {
    return {navigationProps: navigationPropsCache}
  }

  const res = await fetch("http://localhost:3000/api/navigation")
  const navigationProps = await res.json()
  navigationPropsCache = navigationProps

  return {navigationProps}
}

Note that getInitialProps() is a deprecated feature since next 9.3. Not sure how long this will be supported in the the future. See: https://nextjs.org/docs/api-reference/data-fetching/getInitialProps

See https://github.com/breytex/firat500/tree/trivial-getInitialProps for full code example.

2. Use a custom next server implementation

This solution is based on two ideas:

  1. Use a custom server.ts to intercept the nextjs SSR feature. Fetch all the data you need, render the navbar and footer serverside, inject the component HTML into the SSR result.
  2. Rehydrate the DOM based on stringified versions of the fetched data you also attached to the DOM as a <script>.
// server/index.ts

  server.all("*", async (req, res) => {
    const html = await app.renderToHTML(req, res, req.path, req.query);
    const navigationProps = await getNavigationProps()

    const navigationHtml = renderToString(React.createElement(Navigation, {items: navigationProps}))

    const finalHtml = html
      .replace("</body>", `<script>window.navigationProps = ${JSON.stringify(navigationProps)};</script></body>`)
      .replace("{{navigation-placeholder}}", navigationHtml)

    return res.send(finalHtml);
  });

// components/Navigation.tsx

export const Navigation: React.FC<Props> = ({items})=>{
  const [finalItems, setFinalItems] = useState(items ?? [])

  useEffect(
    ()=>{
      setFinalItems((window as any).navigationProps)
    },
    []
  )

  if(!Array.isArray(finalItems) || finalItems.length === 0) return <div>{"{{navigation-placeholder}}"}</div>

  return (
    <div style={{display:"flex", maxWidth: "500px", justifyContent: "space-between", marginTop: "100px"}}>
      {finalItems.map(item => <NavigationItem {...item}/>)}
    </div>
  )
}

I'd consider this a pretty dirty example for now, but you could build something powerful based on this.

See full code here: https://github.com/breytex/firat500/tree/next-link-navigation

3. Use react-ssr-prepass to exec all data fetching server side

  • This uses a custom made fetch wrapper which has some kind of cache
  • The React component tree is traversed server side, and all data fetching functions are executed. This populates the cache.
  • The state of the cache is sent to the client and rehydrates the client side cache
  • On DOM rehydration all data is served from that cache, so no request is sent a second time

This example is a little bit longer and based on the outstanding work of the urql project: https://github.com/FormidableLabs/next-urql/blob/master/src/with-urql-client.tsx

See full example here: https://github.com/breytex/firat500/tree/prepass

Conclusion:

I'd personally would go with option #1 as long as its feasible. #3 looks like an approach with a good developer experience, suitable for bigger teams. #2 needs some love to actually be useful :D

Sign up to request clarification or add additional context in comments.

2 Comments

The first option worked like a charm. Thank you very much :)
Please avoid custom servers as much as possible, this is really an anti-pattern with barely any use case except emergency coding of an unsupported feature if it's a matter of life and death.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.