Next Level Next.
js
SSR search
ders + Suspense
Properly use Loa
SQL injections)
ctions (against
Protect server a
1 2 ... 9 10
Index 00
Table Of Contents
Introduction 01
Infinite Scroll 02
Pagination 11
Page Transition Animations 16
Page Transition Animation with Framer Motion 20
General Framer Motion Animations 22
State Management using Context API 29
Search 36
Next Level Next.js 01
Introduction
Welcome to the Next Level Next.js: Master Advanced Features, Powerful
Libraries, and Fortify with Robust Security Practices. Here you’ll find all
the cool advanced features you wanted to implement in the latest
Next.js 14 but weren’t sure how to do it.
For each of these features, you’ll get a detailed step-by-step process
on how to approach it — A thinking system followed by necessary code
snippets, and finally, a full source code that is ready for you to test & try.
Without wasting too many words on the preface, let’s dive straight into
the world of code
P.S., Before starting any of these features, take your time to think how
you would go for it and then see how we do it. It helps improve logical
thinking
Next Level Next.js 02
A highly sought-after feature that developers are eager to experiment
with in Next.js is none other than,
Infinite Scroll
So how can we integrate Instagram-like infinite scroll in Next.js,
making the most of server-side rendering advantages?
(Before moving on to reading the next words, think carefully. Stress your
muscle memory)
What’s infinite scroll anyway?
A pagination that happens automatically the moment we reach the
end of the page
It all starts with thinking in Next.js,
First of all, fetch your data on the server page (say the first 5 elements)
async function Home() {
const response = await fetch(`http://localhost:8000/tasks?_start=0&_end=5`);
const data = await response.json();
return <main>...</main>;
export default Home;
Now to automatically fetch the next 5 elements, we’ll first need to show
some kind of indicator or spinner that will be seen at the end of the list
Next Level Next.js 03
function LoadMore() {
return (
<div ref={ref} className="flex justify-center items-center ...">
<div role="status">
<svg> // Loader SVG - Find it in the source code. </svg>
<span class='sr-only'>Loading...</span>
</div>
</div>
);
export default LoadMore;
(You can get the actual styles and SVGs in the full source code)
import LoadMore from "./LoadMore";
async function Home({ searchParams }) {
const response = await fetch(`http://localhost:8000/tasks?_start=0&_end=5`);
const data = await response.json();
return (
<main>
<div className="container mx-auto p-8 max-w-5xl">
...
<LoadMore />
</div>
</main>
);
export default Home;
To automatically fetch the next page the moment we reach the end of
the current list, we’ll have to keep track of whether we reached the end
of the list or not. Kind of inspection.
Luckily, there is a li rary that can tell if something is in view of the user or
b
not.
Next Level Next.js 04
react-intersection-observer
NPM Link
Since we have placed the spinner at the end of the list, using this react-
intersection-observer we can see if the spinner is in view or not.
If it’s in view, Fetch the next 5 element
If not, Do nothing
"use client";
import React, { useEffect } from "react";
import { useInView } from "react-intersection-observer";
function LoadMore() {
const { ref, inView } = useInView();
const fetchNextPage = () => {};
useEffect(() => {
if (inView && isNext) {
fetchNextPage();
}, [inView]);
return (
<div ref={ref} className="flex justify-center items-center mx-auto my-5">
{inView && (
<div role="status">
<svg>// Loader SVG - Find it in the source code.</svg>
<span class='sr-only'>Loading...</span>
</div>
)}
</div>
);
export default LoadMore;
Next Level Next.js 05
Do I have to specify that this LoadMore component will be a Client
component?
If you have gone through our Next.js course or even watched a few of
our videos, you would know why it’s marked as a client component
Hint: Interactions and Hooks
We have placed everything in place and now all we have to do is fetch
the next items, the next page. Every time, the spinner is detected, we’ll
request the next page containing the next items.
But wait, how to do that?
We want to trigger the fetch call from the LoadMore component, which
is a client child of the server component, i.e., Home.
How do we manage the flow?
Simple — React states. No, you React Head!
It’s time to welcome the “Underrated URL state management”, as said
by the VP of Vercel here.
Got it? Don’t skip watching the video
We’ll manage the page that we’ll fetch in our URL, and from there, we’ll
retrigger the fetch.
In a nutshell,
Next Level Next.js 06
fetchNextPage of LoadMore component will update the URL stating
that we reached the end and it’s time to get new items
fetch call inside the Home component will look out for the URL
changes and then retrigger the fetch to get the next items
And repeat
"use client";
import React, { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { useRouter, useSearchParams } from "next/navigation";
import { formUrlQuery } from "./utils";
function LoadMore({ isNext }) {
const router = useRouter();
const { ref, inView } = useInView();
const searchParams = useSearchParams();
const page = searchParams.get("page");
const fetchNextPage = () => {
const value = page ? parseInt(page) + 1 : 2;
const newUrl = formUrlQuery({
params: searchParams.toString(),
key: "page",
value,
});
router.push(newUrl, { scroll: false });
};
useEffect(() => {
if (inView && isNext) {
fetchNextPage();
}, [inView]);
return();
}
Next Level Next.js 07
export default LoadMore;
In the above code, you’ll see we’re forming a new URL by passing the
value to formUrlQuery. It’s nothing new. Instead of doing
router.push(/?page=2), we’re using a library called query-string to
update the URL carefully for us.
query-string
NPM Link
Why?
Doing it manually, in this case, will work for sure. But imagine if there is
something already in the URL that you don’t know. For example, /?
filter=newest Then doing the direct router.push(/?page=2) would
override what URL has previously.
Sure, we can first check what’s in the URL, append it first, and then add
new things and do it this way - /?filter=newest&page=2 but that’s much
effort. This problem is solved effortlessly by the above library. Now you
know why…
But what does that formUrlQuery look like?
Next Level Next.js 08
import qs from "query-string";
export function formUrlQuery({ params, key, value }) {
const currentUrl = qs.parse(params);
currentUrl[key] = value;
return qs.stringifyUrl(
url: window.location.pathname,
query: currentUrl,
},
{ skipNull: true }
);
Nothing scary. Just setting the URL properly!
In the final step,
All we need to do is get the page number we set in the URL from
LoadMore on the Home page and do a little math
import LoadMore from "./LoadMore";
const MAX_ITEMS = 5;
async function Home({ searchParams }) {
const page = searchParams.page || 1;
const response = await fetch(
`http://localhost:8000/tasks?_start=0&_end=${page * MAX_ITEMS}`
);
const data = await response.json();
return (
<main>
<div className="container mx-auto p-8 max-w-5xl">
...
Next Level Next.js 09
import LoadMore from "./LoadMore";
const MAX_ITEMS = 5;
async function Home({ searchParams }) {
const page = searchParams.page || 1;
const response = await fetch(
`http://localhost:8000/tasks?_start=0&_end=${page * MAX_ITEMS}`
);
const data = await response.json();
return (
<main>
<div className="container mx-auto p-8 max-w-5xl">
...
<LoadMore isNext={data.length >= page * MAX_ITEMS} />
</div>
</main>
);
export default Home;
Over there, you’ll also see we’re passing isNext prop to the LoadMore
component. This is to ensure that we won’t show the spinner all the time
if there is no more data coming from the database.
You can see the full source code here. Feel free to give it a try!
Full source code
GitHub Link
Next Level Next.js 10
P.S., if you don’t want to put the page param in the URL or basically want
to hide it from the user, there is another way too. We recently did a
video where we taught how to do infinite scroll using a different
approach on a real-world API. Check it out here.
Remember, there are many ways of doing the same thing
Next Level Next.js 11
Another commonly asked question,
Pagination
How to proper pagination using Next.js 14?
If we had to think about how it works, it’s almost the same as Infinite
Scroll. Only the UI and event handlers change.
But let’s take it step by step,
Fetch a sample of data, say the first 5 elements, on the server side
async function Home() {
const response = await fetch(`http://localhost:8000/tasks?_start=0&_end=5`);
const data = await response.json();
return <main>...</main>;
export default Home;
Create the pagination component
"use client";
import React from "react";
function Pagination() {
const currentPage = 1 ;
const handleNavigation = (type) => {
const nextPageNumber = type === "prev" ? currentPage - 1 : currentPage + 1 ;
};
Next Level Next.js 12
return (
<div className="my-5 flex justify-center items-center">
<div className="flex gap-3 items-center">
<button
onClick={() => handleNavigation("prev")}
className="flex items-center justify-center px-4 ..."
>
Previous
</button>
<p className="text-black text-xl">{currentPage}</p>
<button
onClick={() => handleNavigation("next")}
className="flex items-center justify-center px-4 ..."
>
Next
</button>
</div>
</div>
);
export default Pagination;
(You can get the actual styles in the full source code)
Yes, there are event handlers and interactions occurring that rely on the
user's browser functions, so it must be a 'client' component.
And then importing it in the Home page,
Next Level Next.js 13
async function Home() {
const response = await fetch(`http://localhost:8000/tasks?_start=0&_end=5`);
const data = await response.json();
return (
<main>
<div className="container mx-auto p-8 max-w-5xl">
...
<Pagination />
</div>
</main>
);
export default Home;
Now how will we manage which page the user has requested?
Yes, no need for any kind of React State or Context API that will be
responsible for holding the paginated page value.
We do the URL State management.
And this isn’t something new in Next.js, FYI. This method has been there
for a long time but it took a new version release of Next.js to make us
aware of it.
Visit your favorite e-commerce site, for example, Amazon, and see what
happens when you search for something or request a new page from
pagination. Keep an eye on “URL” while you’re doing this.
Time to do the same here. Inside the handleNavigation function we
created inside the Pagination component, we’ll update the URL
depending on the page state
Next Level Next.js 14
"use client";
import React from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { formUrlQuery } from "./utils";
function Pagination({ isNext }) {
const router = useRouter();
// Get search parameters from the URL
const searchParams = useSearchParams();
// Extract the current page number from the URL
const page = parseInt(searchParams.get("page"));
const currentPage = page ? page : 1;
const handleNavigation = (type) => {
const nextPageNumber = type === "prev" ? currentPage - 1 : currentPage + 1;
// Convert the page number to string format
const value = nextPageNumber > 1 ? nextPageNumber.toString() : null;
// Form a new URL with updated page parameter
const newUrl = formUrlQuery({
params: searchParams.toString(),
key: "page",
value,
});
router.push(newUrl);
};
// Render the pagination UI
return <div className="my-5 flex justify-center items-center">...</div>;
export default Pagination;
Make sense?
Next Level Next.js 15
And finally, we’ll consume/use this URL page value on our Home page,
which is going to be handling the fetch
import Pagination from "./Pagination";
const MAX_ITEMS = 5;
async function Home({ searchParams }) {
const page = searchParams.page || 1;
const response = await fetch(
`http://localhost:8000/tasks?_start=${(page - 1) * MAX_ITEMS}&_end=${
page * MAX_ITEMS
}`
);
const data = await response.json();
return (
<main className="bg-slate-200 min-h-screen">
....
<Pagination isNext={???} />
</main>
);
export default Home;
A ask for you — How should we decide if there is a Next page or not?
What will be the logic? THINK!
You can see the full source code here. Feel free to give it a go!
Full source code
GitHub Link
Next Level Next.js 16
Up next question that has confused many people is,
Page Transition Animations
How can we make cool page animations in Next.js without losing its
server-side powers?
Yes, doing animations like that would mean we have to do some client
stuff but that doesn't mean that we have to make everything “client”
code and just give up on Next.js server-side capabilities. One just has to
do smart thinking.
At the time when Next.js was rolling out its different features, from
rendering strategies to routing structure, they released something
called template file convention.
If we visit the website and see what that is, you’ll read this:
A template file is similar to a layout in that it wraps each child layout or
page. Unlike layouts that persist across routes and maintain state,
templates create a new instance for each of their children on navigation.
Focus on “templates create a new instance for each of their children
on navigation”.
What that means is when a user navigates between routes that share
a template, a new instance of the component is mounted, DOM
elements are recreated, state is not preserved, and effects are re-
synchronized. (Reference from Next.js Template)
Next Level Next.js 17
Knowing what it does is important so we can make a better choice.
Although the template feature doesn’t preserve the state or recreate
everything, it’s not inherently bad. We should use it carefully. While the
use case is not common, this is where a template might be an easy
way out,
If we wish to record page views or analytics events using useEffect
on a per-page basis, creating a new template instance for each
page enables the independent management of these page-specific
interactions.
When different pages in an application demand unique transition
animations, utilizing a dedicated template simplifies the
implementation of page-specific animations.
If the goal is to display a suspense fallback with every navigation,
using a dedicated template file for that page makes it achievable.
The current behavior only shows the suspense fallback during the
initial layout load and not when switching pages.
Simple, create a template.js/ts/jsx/tsx file inside the app route
(place of routes where you want to implement the page transitions and
use CSS or framer motion to achieve it.
An example of template.js using tailwindcss would be something like
this:
Next Level Next.js 18
"use client";
import { useState, useEffect } from "react";
function RootTemplate({ children }) {
const [transitionStage, setTransitionStage] = useState("slideOut");
useEffect(() => setTransitionStage("slideIn"), []);
return (
<section className="overflow-hidden">
<div
className={`h-full transition-all duration-700 ease-in-out ${
transitionStage === "slideIn" ? "translate-x-0" : "translate-x-full"
}`}
>
{children}
</div>
</section>
);
export default RootTemplate;
And if we place this inside the root of the folder, it’ll apply the animation
for all kinds of page routes you’ll create
Next Level Next.js 19
That’s it. If you want to see how these animations will work, check out
the full source code here
Full source code
GitHub Link
But that’s pure CSS. How can one implement Framer Motion animations
in Next.js?
Next Level Next.js 20
Page Transition Animation with
Framer Motion
How can we make cool page animations in Next.js without losing its
server-side powers that too with Framer Motion?
Simple as above, use template!
"use client";
import { motion } from "framer-motion";
const variants = {
hidden: { x: "100%" },
enter: { x: 0 },
exit: { x: "-100%" },
};
const transition = { duration: 0.6, ease: "easeInOut" };
function Template({ children }) {
return (
<motion.main
variants={variants}
initial="hidden"
animate="enter"
exit="exit"
transition={transition}
>
{children}
</motion.main>
);
export default Template;
That’s it. Really!
Next Level Next.js 21
And now you’re free to do any kind of server side logic you want to
perform on any kind of pages.
Do note that, in the above code, we’re applying page transitions to all
pages as we have kept the template file in the root of the app folder. A
root Template.
But you can create a specific template for a specific bunch of routes
and apply animations there. It’s that customizable!
Do test the animation and full source code here
Full source code
GitHub Link
Next Level Next.js 22
General Framer Motion
Animations
How to do general framer motion animation on specific elements in
Next.js?
We know that if we use any of the framer motion elements directly
inside server components, it’ll throw a cute (of course not) error
Error: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function
Next Level Next.js 23
Not clear enough, but it does say that we’re trying something with
framer motion that isn’t applicable to do in server components.
Why?
Because framer motion depends on browser functionalities to perform
its animations and thus we can’t render them on the server side.
So what should we do?
We play SMART.
Next.js wants Framer Motion to be a client component and we do that
"use client";
import { motion } from "framer-motion";
export const MotionDiv = motion.div;
And now we import this MotionDiv wherever we want to use animations.
Like this,
import Image from "next/image";
import { MotionDiv } from "./MotionElements";
const stagger = 0.5;
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 },
};
Next Level Next.js 24
async function Home() {
const response = await fetch(`http://localhost:8000/photos`);
const data = await response.json();
return (
<main className="flex min-h-screen flex-col items-center ...">
<div className="grid sm:grid-cols-2 grid-cols-1 gap-10">
{data.map((photo, index) => (
<MotionDiv
className="w-96 relative h-96 rounded shadow-lg"
variants={variants}
initial="hidden"
animate="visible"
transition={{
delay: index * stagger,
ease: "easeInOut",
duration: 0.6,
}}
key={index}
>
<Image
src={photo.imageUrl}
alt={photo.title}
fill
className="object-cover rounded"
/>
</MotionDiv>
))}
</div>
</main>
);
export default Home;
What’s happening here?
All we’re doing is this:
<Client> {children} </Client>
Next Level Next.js 25
We extracted the motion element in a separate file which is a client
component, exported it as a component (with another name), and then
used it inside the server component. Basically, we wrapped the motion
in our own client component.
Hmm, but isn’t that an anti-pattern?
Nah!
The unsupported pattern is importing the server component inside the
client component:
"use client";
// You cannot import a Server Component into a Client Component.
import ServerComponent from "./Server-Component";
export default function ClientComponent({ children }) {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ServerComponent />
</>
);
Interestingly what legal is this:
Next Level Next.js 26
"use client";
import { useState } from "react";
export default function ClientComponent({ children }) {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
);
i.e., passing the server component as a prop to a client component. The
above {children} is a common pattern in React which creates a “slot”
inside the client component.
You may have seen a common example of it in the Next.js layout way
often:
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
Next Level Next.js 27
Although it’s not a client component, it’s a common pattern in React for
passing props.
In the above ClientComponent example, it’ll not know what {children} is,
i.e., a client-rendered or server-rendered code. Its sole responsibility is
to determine the eventual location of the children. It doesn’t care to
know what’s inside.
And that’s how we can use server components inside client
components by passing them as a prop or children
<Client>
<ServerComponent />
</Client>
But that’s the only way of creating and exporting motion elements. You
can do this as well:
"use client";
import React from "react";
import { motion } from "framer-motion";
const AnimatedDiv = ({ children, index, variants, stagger }) => {
return (
<motion.div
className="w-96 relative h-96 rounded shadow-lg"
variants={variants} initial="hidden" animate="visible"
transition={{ delay: index * stagger, ease: "easeInOut",duration: 0.6,}}
key={index}
>
{children}
</motion.div>
);
};
export default AnimatedDiv;
Next Level Next.js 28
And then using it like this:
import Image from "next/image";
import { AnimatedDiv } from "./MotionElements";
const stagger = 0.5;
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 },
};
async function Home() {
const response = await fetch(`http://localhost:8000/photos`);
const data = await response.json();
return (
<main className="flex min-h-screen flex-col items-center ...">
<div className="grid sm:grid-cols-2 grid-cols-1 gap-10">
{data.map((photo, index) => (
<AnimatedDiv index={index} variants={variants} stagger={stagger}>
<Image className="object-cover rounded"
src={photo.imageUrl} alt={photo.title} fill />
</AnimatedDiv>
))}
</div>
</main>
);
export default Home;
As long as you stick to the principle, you can do anything.
Feel free to check out the complete code here
Full source code
GitHub Link
Next Level Next.js 29
State Management using Context
API
If we ever wanted to do some state management using Context API in
Next.js, we would follow the same principle of passing server
components as children/prop to client components. Exactly the same
concept
<ContextProvider>
{children}
</ContextProvider>
How to do it exactly?
First and foremost, create the context, which would be a client
component
"use client";
import { createContext, useContext, useState } from "react";
// Create a new context to manage authentication state.
const AuthContext = createContext();
// Define an authentication provider component.
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const login = () => {
// Perform login logic here
setUser("John Doe");
};
const logoff = () => {
// Perform logoff logic here
setUser(null);
};
Next Level Next.js 30
// Provide the AuthContext value to its descendants
return (
<AuthContext.Provider value={{ user, login, logoff }}>
{/* Render the children components */}
{children}
</AuthContext.Provider>
);
};
// Custom hook to conveniently access the AuthContext value.
export const useAuth = () => {
// Use the useContext hook to get the current context value.
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
return context;
};
As you see in the above code where we’re creating an AuthProvider,
we’re returning the values and rendering the children components
within the {children}. Same concept!
Now we can use this AuthProvider inside our global Layout file to wrap
the whole application inside it:
import { Inter } from "next/font/google";
import { AuthProvider } from "./context";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
Next Level Next.js 31
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
);
To spice things up, let’s render the Home page content in such a way
that if there is a user, it’ll show the list of fetched data from the server
otherwise, it’ll show a login button. How would you go about it?
Again, same thing!
Create the Home page where we would like to show the login content
initially (as there would be no user)
import Client from "./Client";
function Home() {
return (
<main className="bg-white min-h-screen p-5">
<Client />
</main>
);
export default Home;
Create the Client component that consumes the context API hook, i.e.,
useAuth
Next Level Next.js 32
"use client";
import AuthButton from "./AuthButton";
import { useAuth } from "./context";
function Client({ children }) {
const { user } = useAuth();
return (
<div>
<div className="bg-white p-8 rounded-md shadow-md max-w-md mx-auto">
<h1 className="text-2xl font-bold mb-4 text-black">Next.js</h1>
<p className="text-gray-600 mb-4">Login to see the content</p>
<AuthButton />
</div>
</div>
);
export default Client;
AuthButton is a simple client component that does the login logout:
"use client";
import React from "react";
import { useAuth } from "./context";
function AuthButton() {
const { user, login, logoff } = useAuth();
const handleAuth = () => {
if (user) {
logoff();
} else {
login();
};
return (
Next Level Next.js 33
return (
<button
onClick={handleAuth}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold ...">
{user ? "Log Out" : "Log In"}
</button>
);
export default AuthButton;
Now we need to change or display the server component content
depending on whether the user is logged in or not. So let’s first create
our Server Component:
import React from "react";
import AuthButton from "./AuthButton";
async function Server() {
const response = await fetch(`http://localhost:8000/tweets`);
const data = await response.json();
return (
<div className="container mx-auto max-w-5xl">
<div className="flex justify-end mb-10">
<AuthButton />
</div>
<h1 className="text-3xl font-semibold mb-4 text-black">
Hot Takes of Next.js
</h1>
<div className="flex flex-col gap-4">
{data.map((tweet) => (
<div key={tweet.id} className="bg-white p-4 rounded shadow">
<p className="text-black mb-2">{tweet.text}</p>
<div className="flex items-center justify-between mt-5 text-sm">
{tweet.author}
<span className='text-gray-500'>
{new Date(tweet.timestamp).toLocaleString()}
Next Level Next.js 34
</span>
</div>
</div>
))}
</div>
</div>
);
export default Server;
So how would we go about showing this Server component such that it
shows up only when the user has logged in?
Simple,
import Client from "./Client";
import Server from "./Server";
function Home() {
return (
<main className="bg-white min-h-screen p-5">
<Client>
<Server />
</Client>
</main>
);
export default Home;
And then modifying the Client component code to render the children's
content according to the condition:
Next Level Next.js 35
"use client";
import AuthButton from "./AuthButton";
import { useAuth } from "./context";
function Client({ children }) {
const { user } = useAuth();
return (
<div>
{user ? (
children
) : (
<div className="bg-white p-8 rounded-md shadow-md max-w-md mx-auto">
<h1 className="text-2xl font-bold mb-4 text-black">Next.js</h1>
<p className="text-gray-600 mb-4">Login to see the content</p>
<AuthButton />
</div>
)}
</div>
);
export default Client;
Might feel hacky, but hey that’s how it works
The complete source code of the above example is here. Feel free to
mess around with it!
Full source code
GitHub Link
Next Level Next.js 36
Search
How to implement proper search functionality using Next.js Server
Side features?
If you think, it’ll be similar to what we did with pagination or even infinite
scroll. It all comes down to the same concept — URL state management.
So let’s do the basics first,
Fetch the list of data on the server side
async function Home() {
const url = "http://localhost:8000/tasks";
const response = await fetch(url);
const data = await response.json();
return (
<main className="container mx-auto p-8 max-w-5xl">
<h1 className="text-3xl font-semibold mb-4">Coding Tasks</h1>
<div className="flex flex-col gap-4">
{data.map((task) => (
<div key={task.id} className="bg-white p-4 rounded shadow">
<h2 className="text-xl font-semibold mb-2">{task.title}</h2>
<p className="text-gray-600">{task.description}</p>
<span className="mt-2 inline-block bg-gray-200 rounded-full>
{task.tag}
</span>
</div>
))}
</div>
</main>
);
export default Home;
Next Level Next.js 37
Now implement the Search component (Do I have to specify where and
what kind of component it will be?)
Of course, you know. Client component it is!
"use client";
import React, { useEffect, useState } from "react";
function Search() {
const [searchTerm, setSearchTerm] = useState("");
return (
<input
type="text"
placeholder="Search..."
value={searchTerm}
className="w-full p-2 mb-4 border-b-2 border-gray-300 focus:outline-none"
onChange={(e) => setSearchTerm(e.target.value)}
/>
);
export default Search;
Now import the client component inside our main Server component,
i.e., Home
import Search from "./Search";
async function Home() {
const url = "http://localhost:8000/tasks";
const response = await fetch(url);
const data = await response.json();
return (
<main className="container mx-auto p-8 max-w-5xl">
<h1 className="text-3xl font-semibold mb-4">Coding Tasks</h1>
Next Level Next.js 38
<Search />
<div className="flex flex-col gap-4">
{data.map((task) => (
<div key={task.id} className="bg-white p-4 rounded shadow">
<h2 className="text-xl font-semibold mb-2">{task.title}</h2>
<p className="text-gray-600">{task.description}</p>
<span className="mt-2 inline-block bg-gray-200 rounded-full>
{task.tag}
</span>
</div>
))}
</div>
</main>
);
export default Home;
So now that we have separated the concerns, i.e., client and server, how
do we pass the data from the Search client component to the Home
server component?
Yep, use URL search params. Same thing!
Now let’s construct the URL whenever the user types something in the
URL, i.e., /query=${searchTerm}
Next Level Next.js 39
"use client";
import React, { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { formUrlQuery } from "./utils";
function Search() {
const [searchTerm, setSearchTerm] = useState("");
const router = useRouter();
const searchParams = useSearchParams();
const query = searchParams.get("query");
// query after 0.3s of no input
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
if (searchTerm) {
const newUrl = formUrlQuery({
params: searchParams.toString(),
key: "query",
value: searchTerm,
});
router.push(newUrl, { scroll: false });
} else {
router.push("/");
}, 300);
return () => clearTimeout(delayDebounceFn);
}, [searchTerm, searchParams, query]);
return (
<input
type="text"
placeholder="Search..."
value={searchTerm}
className="w-full p-2 mb-4 border-b-2 border-gray-300 focus:outline-none"
onChange={(e) => setSearchTerm(e.target.value)}
/>
);
export default Search;
e t Level Next.js
N x 40
Okay, lots of things are happening above. Let’s tackle them one by one,
Debounce
See the useEffect and we’re doing something with setTimeout. It’s
called debouncing.
Imagine a user typing in a search bar, and the search function triggers
with every keystroke. It will fire too many requests for each keystroke
user will do! How scary and costly it would be!
And here comes Debouncing. With this method, the user waits a
moment before searching. This prevents a flood of unnecessary search
requests for each keystroke and ensures the search is performed when
the user pauses, giving the user more relevant results and saving us
from costing our company.
It's about optimizing the search process for a smoother and more
efficient experience.
In the above code, we’re doing the same. We’re setting some value only
after 0.3s has passed
But what are we setting?
URL State Management
We’re setting the value we’re getting in useState searchTerm inside the
URL.
So if searchTerm is coding, then we’ll create a new URL, i.e., /?
query=coding, and push it using the router method.
Next Level Next.js 41
Whatever the value the user types there, it’ll be added in the URL after
0.3s of time span.
formUrlQuery function is the same as you have seen before in the
pagination or infinite scroll example. It simply makes changes to the URL
and sends a new URL form based on the parameters we sent to it
And that’s what we need. Now that we have set the data in the URL in
the form of search parameters, we can easily access it on any page we
want.
So heading back to the home page, all we have to do is:
import Search from "./Search";
async function Home({ searchParams }) {
const query = searchParams.query;
const url = query
? `http://localhost:8000/tasks?q=${query}`
: "http://localhost:8000/tasks";
const response = await fetch(url);
const data = await response.json();
return (
<main className="container mx-auto p-8 max-w-5xl">
<h1 className="text-3xl font-semibold mb-4">Coding Tasks</h1>
<Search />
<div className="flex flex-col gap-4">
{data.map((task) => (
<div key={task.id} className="bg-white p-4 rounded shadow">
<h2 className="text-xl font-semibold mb-2">{task.title}</h2>
<p className="text-gray-600">{task.description}</p>
<span className="mt-2 inline-block bg-gray-200 rounded-full>
{task.tag}
<span>
</div>
))}
</div>
</main>
)}
Next Level Next.js 42
export default Home;
That’s it. Get the search param value and pass it to the API.
Do note that depending on which API you’re using, things might be
different but the main concept of making URL as a state management
will remain the same.
The API (dummy JSON server) I am using has two separate endpoints.
The first endpoint, http://localhost:8000/tasks returns the list of all
items but if you want to search for something, you have to use the other
endpoint, i.e., http://localhost:8000/tasks/?q.
That’s why you see a condition there, i.e., use the first endpoint only
when there is nothing else in the URL!
As usual, you can see the full source code here. Do test it out!
Full source code
GitHub Link
Client Vs. Server 43
Stay tuned for updates!
We're actively working on expanding this guide with additional content
to elevate your Next.js experience even further.
Have burning questions or specific topics you'd like us to cover next?
Share your thoughts with us on discord. We'll prioritize the questions
with the highest votes.
Thank you!