Improve performance of nextjs app

Nextjs is one of the most popular front end framework for Server side rendering. It comes with great defaults which helps build a webapp with good performance. However, there are a few common mistakes made which result in increased load times.

1. Send only required fields from getServerSideProps #

Nextjs makes SSR easy, just get the data inside getServerSideProps and return data to React component as Props. getServerSideProps function runs on server so data fetching is often faster than fetching on client.

When fetching data from REST api or other sources where we dont have control over which fields are needed, So we often get all data and send it to the component. In case some fields are not being used then also data will be sent to the user browser resulting in increased bundle size. Problem impact becomes even bigger.

export default function UserList({ users }) {
  return (
    <div>
      {users.map((user) => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

export const getServerSideProps = async () => {
  const users = await getUsers();
  /*
  users = [{
    id: 1,
    name: "John",
    email: "john@gmail.com",
    phone: "987xxxxxxxx",
    address: "xxxxx",
    fbId: "xxxxxx"
  }]
*/
  return {
    props: { users },
  };
};
How to fix #

The fix is really simple, before returning the data from getServerSideProps just remove the fields which are not required on the client side.

export const getServerSideProps = async () => {
  const users = await getUsers();
  /*
  users = [{
    id: 1,
    name: "John",
    email: "john@gmail.com",
    phone: "987xxxxxxxx",
    address: "xxxxx",
    fbId: "xxxxxx"
  }]
*/
  return {
    props: { users: users.map((user) => ({ id: user.id, name: user.name })) }, // send fields which are used in client
  };
};

(Using Prisma)
Prisma allows us to select only fields which are required,

export const getServerSideProps = async () => {
  const users = await prisma.user.findMany({
    select: { // Select only required fields
      id: true,
      name: true,
    },
  });
  return {
    props: { users },
  };
};

2. Fetch data in parallel when possible #

When fetching data from two async sources, fetching data synchronously may result in an unexpected page load delay.

export const getServerSideProps = async () => {
  const foo = await getFoo(); // 500ms
  const bar = await getBar(); // 300ms
  // Total wait: 800ms

  return {
    props: { foo, bar },
  };
};
How to fix #

Fixing this is quite simple, Instead of having multiple await promise all promises can be executed in parallel using Promise.all([promise1, promise2]).

export const getServerSideProps = async () => {
  const [foo, bar] = await Promise.all([getFoo(), getBar()]); // 500ms
  // Total wait: 500ms
  return {
    props: { foo, bar },
  };
};

3. Serve pages with the correct caching strategy #

Nextjs support both Static site generation(SSG) and Server side rendering(SSR). If the page data does not change often then SSG should be used and otherwise SSR. In addition to this cache-control headers should be used to efficiently cache pages on client side. MDN provides great summary of all cache-control options.

export async function getServerSideProps({ req, res }) {
  res.setHeader(
    'Cache-Control',
    'public, s-maxage=10, stale-while-revalidate=59'
  )

  return {
    props: {
      data: new Date().toISOString(),
    },
  }
}

Since you've made it this far, sharing this article on your favorite social media network would be highly appreciated ! For feedback, please ping me on Twitter.

Published