Make Header and Footer APIs Load Once in a Next.js App with getServerSideProps

If we are using getServerSideProps in Next.js for data fetching, it will call all the APIs for a single page including the header and footer APIs. But in most cases, we only need to call the header and footer APIs once when are loading the app for the first time. So here in this article, we are discussing a way to make header and footer APIs load once in a Next.js app with getServerSideProps.

Prerequisites

To continue with this article, the user should be aware of the following things:-

  • Basic knowledge of Next.js
  • Usage of getServerSideProps and getStaticProps in Next.js
  • Creating API routes in Next.js (Optional). You can also use the external APIs as well.

What we will learn

Here in this article, we will learn to:-

  • Create a Next.js app with TypeScript configuration.
  • Install and configure the react-ssr-prepass package in our app.
  • Create a Navigation component.
  • Create two pages Home page and the About page
  • Configure endpoints to serve data for the Navigation, Home page, and About page.
  • Using react-ssr-prepass, we will call Navigation API once and store the response in a cache.
  • Using getServerSideProps, call the APIs to get the data for the Home page and About page in each load.

The workflow of our app will be the same as below. There are two navigation links and clicking each will direct us to its corresponding page.

The important thing we need to note that is, the only APIs loading are for pages Home and About. The Navigation API is loading once and the data is stored in the cache.

Create a Next.js app with TypeScript configuration

Using the NPX tool, we can easily create a new Next.js app with the TypeScript configuration using the below command.

npx [email protected] --ts

Install the react-ssr-prepass package

Here we are using a package react-ssr-prepass to load the API once and store the response in the cache storage. This will prevent multiple calls for the APIs. This is the exact idea for this article.

To install the react-ssr-prepass package, use the below command.

npm i react-ssr-prepass

Create a Navigation component

This is an integral part of our article. We will display 2 links in the Navigation bar, Home and About that will direct to the corresponding pages.

But these link text and the path are not hardcoded. We are fetching these data from the API.

We will code the API route later. Our aim is to limit this API call once.

Inside the Navigation component, we will code the logic for fetching the data for the Navigation component.

The important thing to be noted is that this is done on the server-side itself.

Using react-ssr-prepass package, we will limit this call once for our app. We will do this in the upcoming steps.

Now let us start with the Navigation component first.

In this component, we are fetching the API that returns the link text and the href to be displayed on the header of our app.

const fetchNavigationData = () => myFetch("http://localhost:3000/api/navigation")

We can see that it is not a normal fetch request. It’s calling a function myFetch(). We will discuss this function later.

The entire Navigation.tsx component will look the same as below.

import React, { useState, useEffect } from "react"
import Link from 'next/link';
import { myFetch } from "../src/myFetch";

interface NavigationItem {
  children: String;
  href: String;
}

interface Props {
  items?: NavigationItem[]
}

type SSRComponent = React.FC<Props> & { getSsrData: (e: string) => Promise<any> }

const fetchNavigationData = () => myFetch("http://localhost:3000/api/navigation")

export const Navigation: SSRComponent = () => {
  const [navigationProps, setNavigationProps] = useState([])

  useEffect(
    () => {
      fetchNavigationData().then(data => {
        setNavigationProps(data)
      })
    },
    []
  )

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

Navigation.getSsrData = fetchNavigationData

const NavigationItem = (props) => (
  <Link href={props.href}>
    <a>{props.children}</a>
  </Link>
)

Limiting the Navigation API call to once

We are importing a function myFetch() inside the Navigation component to fetch the navigation API. Here inside this function, where we are adding the logic of not calling the API multiple times.

So let us code the file /src/myFetch.ts which I have described below. It also includes a function hydrateFetchStore() which we will use inside the _app.tsx file later.

export const fetchStore = {}

export const cacheResponse = (key, data)=>{
  fetchStore[key] = data;
}

export const hydrateFetchStore = (ssrFetchStore: object)=>{
  Object.assign(fetchStore, ssrFetchStore)
}

export const myFetch = async (endpoint)=>{
  
  // Dont fetch data again which is already in the cache
  if(fetchStore[endpoint]){
    return fetchStore[endpoint]
  } 
  
  const res = await fetch(endpoint);
  const data = await res.json();
  cacheResponse(endpoint, data);
  
  return data
}

The withPrepass function

Inside the src/withPrepass.tsx file, we will add the code for passing the cache storage to our app. The data that is to be shown in the Navigation component is already stored in the cache storage fetchStore.

The complete code will look as below.

import { NextPage, NextPageContext } from 'next';
import NextApp, { AppContext } from 'next/app';
import React from 'react';
import ssrPrepass from 'react-ssr-prepass';
import { fetchStore } from './myFetch';

function getDisplayName(Component: React.ComponentType<any>) {
  return Component.displayName || Component.name || 'Component';
}

export function withPrepass() {
  return (AppOrPage: NextPage<any> | typeof NextApp) => {
    const withPrepass = (props: any) => {
      return (
        <AppOrPage {...props} />
      );
    };

    withPrepass.displayName = `withPrepass(${getDisplayName(AppOrPage)})`;

    withPrepass.getInitialProps = async (appOrPageCtx: AppContext | NextPageContext) => {
      const { AppTree } = appOrPageCtx;

      // Determine if we are wrapping an App component or a Page component.
      const isApp = !!(appOrPageCtx as AppContext).Component;
      const ctx = isApp
        ? (appOrPageCtx as AppContext).ctx
        : (appOrPageCtx as NextPageContext);

      // Run the wrapped component's getInitialProps function.
      let pageProps;
      if (AppOrPage.getInitialProps) {
        pageProps = await AppOrPage.getInitialProps(appOrPageCtx as any);
      }

      // Check the window object to determine whether or not we are on the server.
      // getInitialProps runs on the server for initial render, and on the client for navigation.
      // We only want to run the prepass step on the server.
      if (typeof window !== 'undefined') {
        return pageProps ?? {};
      }

      const props = { ...pageProps };
      const appTreeProps = isApp ? props : { pageProps: props };

      // Run the prepass step on AppTree. This will run all urql queries on the server.
      await ssrPrepass(<AppTree {...appTreeProps} />, (element, instance) => {
        const getSsrData = (element as any)?.type?.getSsrData
        if (typeof getSsrData === "function") {
          getSsrData()
        }
      });

      return {
        ...pageProps,
        fetchStore
      };
    };

    return withPrepass;
  };
}

Integrating withPrePass function and hydrateFetchStore into our app

We have already declared the function withPrePass and hydrateFetchStore. Now let us integrate it into our app. This is done inside the _app.tsx file.

The code for the complete _app.tsx file is described below.

import { Navigation } from '../components/Navigation';
import '../styles/globals.css';
import { withPrepass } from '../src/withPrepass';
import { hydrateFetchStore } from '../src/myFetch';

function MyApp({ Component, pageProps, fetchStore }) {

  hydrateFetchStore(fetchStore)

  return <>
    <Navigation />
    <Component {...pageProps} />
  </>
}

export default withPrepass()(MyApp)

Home page and About page with getServerSideProps method

In this app, we are fetching data to be shown on the Home page and About page using the getServerSideProps method. Let us code both pages.

The code for the home page will look the same as below.

import styles from '../styles/Home.module.css'

export default function Home(props: { description: string }) {
  return (
    <div className={styles.container}>
      <h2>Home page</h2>
      <p>{props?.description}</p>
    </div>
  )
}

export async function getServerSideProps() {
  const res = await fetch(`http://localhost:3000/api/home`)
  const data = await res.json()

  return { props: { description: data.description } }
}

The code for the About page will look the same as below.

import styles from '../styles/Home.module.css'

export default function About(props: { description: string }) {
  return (
    <div className={styles.container}>
      <h2>About page</h2>
      <p>{props?.description}</p>
    </div>
  )
}

export async function getServerSideProps() {
  const res = await fetch(`http://localhost:3000/api/about`)
  const data = await res.json()

  return { props: { description: data.description } }
}

Define the API routes

For the Navigation component, Home page, and About page, we are calling the APIs for getting the data.

We know that Next.js can be used as a full-stack framework so that we can code the API routes in our app itself.

So let us code these three API routes. Note that we are hardcoding the data inside the route and not involving any database connection.

To get the links for the Navigation component, we are passing the link text and href as the API response. this can be coded as below.

export default (req, res) => {
  res.status(200).json([
    {children: "Home", href: "/"},
    {children: "About", href: "/about"},
  ])
}

We need to create routes for the home page API also. Calling the API will simply return a header text as JSON.

export default (req, res) => {
  res.status(200).json({
    description: "Home page data coming from the API"
  })
}

In the same manner, we will create the About page API also. This will also return a simple text in JSON format.

export default (req, res) => {
  res.status(200).json({
    description: "About page data coming from the API"
  })
}

Codesandbox

Refer to the CodeSandbox link to view the live app. You can clone this project to your CodeSandbox account and edit the code also.

https://codesandbox.io/s/romantic-microservice-nexu3q

GitHub

You can always refer to the GitHub repository to clone this project, refer to the code and work on top of it.

https://github.com/techomoro/header-footer-load-once-nextjs-demo-app

Summary

So here in this article, we learned to make Header and Footer APIs load once in a Next.js app with the getServerSideProps method. The package react-ssr-prepass helped us to achieve this goal.

Be the first to reply

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.