Client-side and Server-side Caching in Next.js

07/28 09:14

Profile picture of Yoonsoo Kim

Yoonsoo Kim

AI Research Engineer / Kaggle Grandmaster / Creator Of This Website

If the client fetches data from the database every time the user makes request, user will suffer from slow data loading and your server will suffer from too much requests. To reduce data loading time and server load, we can utilize client side caching and server side caching.

Stale While Revalidate

Stale while revalidate is the concept we will use heavily to cache efficiently and safely. On initial request by the user, the cacher will send the request over the network to fetch the original data, remembers(caches) it, and returns the data. On subsequent requests, it will first return the cached(stale) data, then sends the request over the network to fetch the original data and updates its cache(revalidate). Since it returns the data right away when cached, the user will experience immediate loading. Also since it checks and updates its cache, there will be minimum difference between cache and original source.

illustration of client side and server side caching

Client Side Caching

If you cache data on the client. There will be zero server load (if you do not revalidate) and almost zero loading time. You can cache client-side with SWR. SWR is actually an abbreviation for "Stale while Revalidate", so it works the same as I explained above. Following is the basic usage of swr in the documentation.

import useSWR from 'swr'

const fetcher = (...args) => fetch(...args).then(res => res.json())

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

On initial request by the user, swr will get original data over the network to /api/user endpoint and caches it with key /api/user. When the request to /api/user is triggerd again, it will immediately return cached data while revalidating in the background.

You can also use swr with Next.js SSR(Server Side Rendering), by following the doc.

 export async function getStaticProps () {
  // `getStaticProps` is executed on the server side.
  const article = await getArticleFromAPI()
  return {
    props: {
      fallback: {
        '/api/article': article
      }
    }
  }
}

function Article() {
  // `data` will always be available as it's in `fallback`.
  const { data } = useSWR('/api/article', fetcher)
  return <h1>{data.title}</h1>
}

export default function Page({ fallback }) {
  // SWR hooks inside the `SWRConfig` boundary will use those values.
  return (
    <SWRConfig value={{ fallback }}>
      <Article />
    </SWRConfig>
  )
}

Server Side Caching

Generally there are many fresh user requests; requests that are not included in client side cache. Those requests need to reach the database to get the data. But it isn't efficient to always query the database, when same requests occur many times. That's the reason you need server side caching.

For example, when your server first encounters request of retreiving post 1 from database, it can remember(cache) the response. For subsequent request of retreiving post 1, it can respond directly with the cache, not querying the database. SWR concept can be used here as well, to make the cache remain not too far away from the true database source.

To take step further, you can cache on the CDN(Content Delivery Network), not on your server to reduce network latency. That is automatically handled if you use Vercel edge network.

For Next.js applications, you can set headers in the response at your api pages and getServerSideProps function. For example, you can set like following, as described at the doc.

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

  return {
    props: {},
  }
}

This value is considered fresh for ten seconds (s-maxage=10). So if a request is repeated within the next 10 seconds, the previously cached value will still be fresh. Server will return the cached data and do not bother to revalidate. If the request is repeated before 59 seconds, the cached value will be returned immediately, but the cache is stale(stale-while-revalidate=59). So in the background, a revalidation request will be made to update the cache with a fresh value. If you refresh the page, you will see the new value.

Discussions

I came to study this subject; "client side and server side caching in Next.js" to make this site faster and more efficient.

When you update, create or delete database entry, the caches need to be invalidated. I tried to implement this without page refreshing, but failed in deployment environment. I guess I need to make a request that returns REVALIDATED header but wasn't able to find documents about how to do so.

© 2024 Yoonsoo.