·4 min read

Speed up your Next.js application with Redis

Noah FischerNoah FischerDevRel @Upstash

Next.js is a very successful web framework which brings together server side rendering and static site generation. SSG speeds up your web site thanks to CDN caching meanwhile SSR helps you with SEO and dynamic data.

Server side rendering is a great feature which helps you to write full stack applications. But if you are not careful, the performance of your Next.js website can be affected easily. In this blog post, I will explain how to leverage Redis to speed up your Next.js API calls. Before that I will briefly mention a simpler way to improve your performance.

Use SWR on your API calls

SWR is a very smart data fetching library. It uses the HTTP cache invalidation strategy (stale-while-revalidate) described by HTTP RFC 5861. When you call an API with SWR, it instantly returns the cached data but asynchronously it fetches the current data and updates your UI. You can also set refreshInterval depending on your tolerance to staleness.

const { data: user } = useSWR('/api/user', { refreshInterval: 2000 })

In the above code, user API will be refreshed every 2 seconds.

Caching with Redis

SWR is very simple and effective. But there are cases you will need a server side caching:

  • Client side caching improves the performance for the clients. But if the number of clients is high, you can experience high load on the server side resources which will eventually affect the client side performance too.
  • If you are consuming an external API with a quota, you will want to control the API usage on the server side. Otherwise, too many clients will consume the API quickly.
  • If you have resources calculated, fetched or processed at the server side using dynamic inputs, client side caching will not be very useful.

Example Project: Covid Tracker

In this project, we will use Javier Aviles’ Covid API and find the top 10 countries with the most number of cases. Check the website and the source code.

We will use Redis to cache the responses from Covid API so:

  • The response will be much faster. If you check the website, you will see that calling the Covid API is hundreds of milliseconds while fetching from Redis is 1-2 milliseconds.
  • We will not overwhelm the Covid API with too many requests.

API Code

The code first checks if we have the API result cached in the Redis. If not, we will get the all country list from the Covid API and sort them by current day’s number of cases and save the top 10 to Redis. While saving to Redis, we set the "EX" 60 parameter which means that Redis will evict the entry in 60 seconds.

import Redis from "ioredis";
 
let redis = new Redis(process.env.REDIS_URL);
 
export default async (req, res) => {
  let start = Date.now();
  let cache = await redis.get("cache");
  cache = JSON.parse(cache);
  let result = {};
  if (cache) {
    console.log("loading from cache");
    result.data = cache;
    result.type = "redis";
    result.latency = Date.now() - start;
    return res.status(200).json(result);
  } else {
    console.log("loading from api");
    start = Date.now();
    return fetch("https://coronavirus-19-api.herokuapp.com/countries")
      .then((r) => r.json())
      .then((data) => {
        data.sort(function (a, b) {
          return b.todayCases - a.todayCases;
        });
        result.data = data.splice(1, 11);
        result.type = "api";
        result.latency = Date.now() - start;
        redis.set("cache", JSON.stringify(result.data), "EX", 60);
        return res.status(200).json(result);
      });
  }
};
import Redis from "ioredis";
 
let redis = new Redis(process.env.REDIS_URL);
 
export default async (req, res) => {
  let start = Date.now();
  let cache = await redis.get("cache");
  cache = JSON.parse(cache);
  let result = {};
  if (cache) {
    console.log("loading from cache");
    result.data = cache;
    result.type = "redis";
    result.latency = Date.now() - start;
    return res.status(200).json(result);
  } else {
    console.log("loading from api");
    start = Date.now();
    return fetch("https://coronavirus-19-api.herokuapp.com/countries")
      .then((r) => r.json())
      .then((data) => {
        data.sort(function (a, b) {
          return b.todayCases - a.todayCases;
        });
        result.data = data.splice(1, 11);
        result.type = "api";
        result.latency = Date.now() - start;
        redis.set("cache", JSON.stringify(result.data), "EX", 60);
        return res.status(200).json(result);
      });
  }
};

UI Code

The UI is a simple React code. We fetch the data from API using SWR.

export default function Home() {
  function refresh(e) {
    e.preventDefault();
    window.location.reload();
  }
  const { data, error } = useSWR("api/data", fetcher);
  if (error) return "An error has occurred.";
  if (!data) return "Loading...";
  return (
    <div className={styles.container}>
      <Head>
        <title>Covid Tracker</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
 
      <main className={styles.main}>
        <h1 className={styles.title}>Covid Tracker</h1>
 
        <p className={styles.description}>
          Top 10 countries with the most cases today
        </p>
 
        <div className={styles.grid}>
          <div className={styles.card} onClick={refresh}>
            <table className={styles.table}>
              <thead>
                <tr>
                  <th>Country</th>
                  <th>Today Cases</th>
                  <th>Today Deaths</th>
                </tr>
              </thead>
              <tbody>
                {data.data.map((item) => (
                  <tr>
                    <td>{item.country}</td>
                    <td>{item.todayCases}</td>
                    <td>{item.todayDeaths}</td>
                  </tr>
                ))}
              </tbody>
            </table>
            <br />
            <em>
              Loaded from {data.type} in <b>{data.latency}</b> milliseconds.
              Click to reload.
            </em>
          </div>
        </div>
      </main>
 
      <footer className={styles.footer}>
        This is a sample project for the blogpost &nbsp;
        <a
          href="https://blog.upstash.com/nextjs-caching-with-redis"
          target="_blank"
          rel="noopener noreferrer"
        >
          Speed up your Next.js application using Serverless Redis for caching.
        </a>
      </footer>
    </div>
  );
}
export default function Home() {
  function refresh(e) {
    e.preventDefault();
    window.location.reload();
  }
  const { data, error } = useSWR("api/data", fetcher);
  if (error) return "An error has occurred.";
  if (!data) return "Loading...";
  return (
    <div className={styles.container}>
      <Head>
        <title>Covid Tracker</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
 
      <main className={styles.main}>
        <h1 className={styles.title}>Covid Tracker</h1>
 
        <p className={styles.description}>
          Top 10 countries with the most cases today
        </p>
 
        <div className={styles.grid}>
          <div className={styles.card} onClick={refresh}>
            <table className={styles.table}>
              <thead>
                <tr>
                  <th>Country</th>
                  <th>Today Cases</th>
                  <th>Today Deaths</th>
                </tr>
              </thead>
              <tbody>
                {data.data.map((item) => (
                  <tr>
                    <td>{item.country}</td>
                    <td>{item.todayCases}</td>
                    <td>{item.todayDeaths}</td>
                  </tr>
                ))}
              </tbody>
            </table>
            <br />
            <em>
              Loaded from {data.type} in <b>{data.latency}</b> milliseconds.
              Click to reload.
            </em>
          </div>
        </div>
      </main>
 
      <footer className={styles.footer}>
        This is a sample project for the blogpost &nbsp;
        <a
          href="https://blog.upstash.com/nextjs-caching-with-redis"
          target="_blank"
          rel="noopener noreferrer"
        >
          Speed up your Next.js application using Serverless Redis for caching.
        </a>
      </footer>
    </div>
  );
}

https://swr.vercel.app/docs/with-nextjs

https://brianlovin.com/writing/caching-api-routes-with-next-js

https://coronavirus-19-api.herokuapp.com/countries

https://github.com/javieraviles/covidAPI