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 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...
For more information checkout latest Caching in Nextjs
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
Step 2: Create project
Step 3: Create Schema with new resource and generate data using the slider
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
MOCKAPI = https://XXXXXXXXXXXXXXXXXXXXX.mockapi.io/api/product/:endpoint
Server Side (App Directory)
Step 1: Initialize a Nextjs App using terminal
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:
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
Step 3: Create a new file Card.tsx
within src/app
and paste this code below:
"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>
//</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
Step 4: Remember to make default function async in page.tsx
export default async function Home()
Fetching data on server component with the mockapi endpoint
//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
//</form>
{data.map((place: any) => (
<Card props={place}
/>))}
//</div>
Output
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
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
<form action={serverAction}
className="flex flex-col gap-2 p-1 rounded-lg bg-slate-400/10" >
Thats all, you will see such output
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
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
"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
npm i @tanstack/react-query
Create a file outside app folder provider.tsx
containing following:
"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:
<body className={inter.className}>
<ReactQueryProvider>{children}</ReactQueryProvider>
</body>
Step 4: Using QueryClient fetching data on client side
//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
//</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
// 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.
<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
No Reloads!
Top comments (2)
This post is about caching but you are opting out of caching in your server fetch request and then you call a revalidate too? Why?
Thanks for noticing @danko56666, actually during the development period it took time to revalidate, and for getting the changes I did so, I made a mistake
instead of
cache: "no-cache"
it can be