Caching & Revalidate: ServerSide vs ClientSide
Tags: nextjs, reactjsdevelopment, webdev, productivity
Published: 2023-11-29 19:49:50

A major feature which is often missed out by many web application especially dealing with data sharing and database like ecommerce websites, is **data revalidation**.

Like this one, where data is not always fetch from data source (database) but cached on the server to access latest revalidated data faster, also revalidating server component with a live UI update on client side as well as revalidating cache on server side for all clients.

![data caching and revalidation demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3m4yd92qottsvkipyh4.gif)


## Data Revalidation

The process of reviewing, verifying, and ensuring the accuracy and completeness of data in a database or dataset. This is a crucial step in **maintaining** data quality and integrity. The revalidation process may involve checking for errors, inconsistencies, and **outdated information**.

Here outdated information is often referred to cached data on the servers. This is how things work under the hood though...


![Nextjs caching and revalidation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9b57eiy43irhedf1329.png)

For more information checkout latest [Caching in Nextjs](https://nextjs.org/docs/app/building-your-application/caching)

In short, Caching just boost your page performance while maintaining the SEO and with the help of revalidation client always gets fresh data.

## Practice Project

Let's make a mini demonstration project using these features.

### Requirement

**Step 1:** Create an account on [mockapi.io](https://mockapi.io/)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mn3l3xyen9v64d5k8mex.png)

**Step 2:** Create project

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhy65azpjb1lu7a3t6yu.png)

**Step 3:** Create Schema with new resource and generate data using the slider

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jnew0pzjl8bwzcgspoul.png)

You will be left with a url like this, replace the endpoint with your schema name

```
https://XXXXXXXXXXXXXXXXXXXXX.mockapi.io/api/product/:endpoint
```

Create a `.env.local` file in the root directory
```.env
MOCKAPI = https://XXXXXXXXXXXXXXXXXXXXX.mockapi.io/api/product/:endpoint
```

### Server Side (App Directory)

**Step 1:** Initialize a Nextjs App using terminal
```powershell
npx create-next-app server-demo
√ Would you like to use TypeScript? ... Yes
√ Would you like to use ESLint? ... Yes
√ Would you like to use Tailwind CSS? ... Yes
√ Would you like to use `src/` directory? ... Yes
√ Would you like to use App Router? (recommended) ... Yes
? Would you like to customize the default import alias (@/*)? » No

cd server-demo
```

**Step 2** Inside `src/app/page.tsx` replace with this code below:
```tsx
export default function Home() {
  return (
    <main className="grid max-w-3xl mx-auto p-4 gap-4">
      <h1 className="text-4xl font-extrabold max-lg:text-3xl">Homepage</h1>
      <div className="grid grid-cols-3 gap-3">
        <form
          action=""
          className="flex flex-col gap-2 p-1 rounded-lg bg-slate-400/10"
        >
          <input
            name="name"
            placeholder="Name"
            className="p-2 rounded-md outline-none"
            type="text"
          />
          <input
            name="image"
            placeholder="Image"
            className="p-2 rounded-md outline-none"
            type="text"
          />
          <button
            type="submit"
            className="bg-orange-600 font-extrabold uppercase text-white/70 p-2 rounded-md"
          >
            Submit
          </button>
        </form>
      </div>
    </main>
  );
}
```

**Output**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm0lvpqspscmfhsb7gad.png)

**Step 3:** Create a new file `Card.tsx` within `src/app` and paste this code below:
```tsx
"use client";
import Image from "next/image";
import React from "react";

type Props = {
  props: {
    id: string;
    name: string;
    image: string;
    createdAt: string;
  };
};

const Card = ({ props }: Props) => {
  return (
    <div className="flex flex-col gap-1 p-1 rounded-lg bg-slate-400/10">
      <div className="relative overflow-hidden flex-1 rounded-md bg-slate-400/30">
        <Image
          src={props.image}
          alt={props.name}
          fill
          loader={() => props.image}
          className="object-cover"
        />
      </div>
      <span className="bg-zinc-800 font-extrabold uppercase text-white/70 p-2 rounded-md text-center">
        {props.name}
      </span>
    </div>
  );
};

export default Card;
```

Now import `Card.tsx` into the `page.tsx` just below `</form>`
```tsx
//</form>
//Card Component
<Card props={{
      id: "1",
      name: "Bellgium",
      image: "https://loremflickr.com/640/480/nature",
      createdAt: "2023-11-29T05:14:05.972Z",
}} />
//</div>
```

**Output**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oi33ex866jykvxj2gwe5.png)

**Step 4:** **Remember** to make default function async in `page.tsx`
```tsx
export default async function Home()
```

Fetching data on server component with the mockapi endpoint
```tsx
//export default async function Home() {
  const res = await fetch(process.env.MOCKAPI!, {
    next: {
      tags: ["place"],
      revalidate: 60 * 60,
    },
  });
  const data = await res.json();
  //return (
```

Using fetched data below by wrapping `Card` component inside array map function
```tsx
//</form>
{data.map((place: any) => (
<Card props={place} 
/>))}
//</div>
```

**Output**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhtohabb3rheorcbll5w.png)

**Step 5** **Important** Now coming to the serverAction part, where our revalidation will take place after adding a new data. 

Create an async function before `return` in `page.tsx`
```tsx
async function serverAction(e: FormData) {
    "use server";
    let name = e.get("name")?.toString();
    let image = e.get("image")?.toString();

    if (!name && !image) return

    const newPlace = {
      name,
      image
    }

    await fetch(process.env.MOCKAPI!, {
      method: "post",
      body: JSON.stringify(newPlace),
      headers: { "Content-Type": "application/json" },
    });

    revalidateTag('place')
  }
```

Add the function's name to form `action`
```tsx
<form action={serverAction} 
className="flex flex-col gap-2 p-1 rounded-lg bg-slate-400/10" >
```

Thats all, you will see such output

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cxnwikprzs66x2wjmozg.gif)

### Client Side (TanStack React Query)

Using **Tanstack React Query** provides you awesome control over data fetching and manipulation on the Client Side UI.

**Step 1:** Initialize a Nextjs App using terminal
```powershell
npx create-next-app client-demo
√ Would you like to use TypeScript? ... Yes
√ Would you like to use ESLint? ... Yes
√ Would you like to use Tailwind CSS? ... Yes
√ Would you like to use `src/` directory? ... Yes
√ Would you like to use App Router? (recommended) ... Yes
? Would you like to customize the default import alias (@/*)? » No

cd client-demo
```

**Step 2:** Replace this code from `page.tsx`
```tsx
"use client";

const ClientPage = () => {
  return (
    <main className="grid max-w-3xl mx-auto p-4 gap-4">
      <h1 className="text-4xl font-extrabold max-lg:text-3xl">Client Page</h1>
      <div className="grid grid-cols-3 gap-3">
        <form className="flex flex-col gap-2 p-1 rounded-lg bg-slate-400/10">
          <input
            name="name"
            placeholder="Name"
            className="p-2 rounded-md outline-none"
            type="text"
          />
          <input
            name="image"
            placeholder="Image"
            className="p-2 rounded-md outline-none"
            type="text"
          />
          <button
            type="submit"
            className="bg-purple-600 hover:bg-purple-600/90 font-extrabold uppercase text-white/70 p-2 rounded-md"
          >
            Submit
          </button>
        </form>
      </div>
    </main>
  );
};

export default ClientPage;
```

**Step 3:** Steup Tanstack React Query

```powershell
npm i @tanstack/react-query
```

Create a file outside app folder `provider.tsx` containing following:
```tsx
"use client";

import React, { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const ReactQueryProvider = ({ children }: { children: React.ReactNode }) => {
  const [queryClient] = useState(() => new QueryClient());
  return (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};

export default ReactQueryProvider;
```

Inside `/src/app` edit `layout.tsx` wrap `children` with exported ReactQureyProvider:

```tsx
<body className={inter.className}>
        <ReactQueryProvider>{children}</ReactQueryProvider>
</body>
```

**Step 4:** Using QueryClient fetching data on client side
```tsx
//const ClientPage = () => {
const queryClient = useQueryClient();

  // Queries
  const { data, isLoading } = useQuery({
    queryKey: ["place"],
    queryFn: async () => {
      const res = await fetch(process.env.MOCKAPI!);
      const fetchedData = await res.json();
      return fetchedData;
    },
  });

//return (
```
Adding loading state to the page after form field
```tsx
//</form>
        {isLoading ? (
          <div className="flex flex-col gap-1 p-1 rounded-lg bg-slate-400/10">
            <div className="relative overflow-hidden min-h-[90px] flex-1 rounded-md bg-slate-400/30"></div>
            <span className="bg-zinc-800 font-extrabold uppercase text-white/70 p-2 rounded-md text-center animate-pulse">
              Loading
            </span>
          </div>
        ) : (
          data.map((e: any) => <Card key={e.id} props={e} />)
        )}
//</div>
```

**Step 5:** Revalidating client side data after successfully fetching data

```tsx
// Mutations
  const { mutate, isPending } = useMutation({
    mutationFn: async (newData) => {
      const res = await fetch(process.env.MOCKAPI!,
        {
          method: "post",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(newData),
        }
      );
      return res.json();
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["place"] });
    },
  });

//return (
```
Adding form actions function for getting data and post it.
```tsx
<form action={(e) => {
            const name = e.get("name")?.toString();
            const image = e.get("image")?.toString();

            if (!name && !image) return;

            mutate({ name, image });
          }}
          className="flex flex-col gap-2 p-1 rounded-lg bg-slate-400/10">
```

**Output**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6gxspb9foxv5ssk5mfr.gif)

No Reloads!