0% found this document useful (0 votes)
335 views45 pages

Next Level Nextjs

The document discusses implementing infinite scroll in Next.js. It explains that infinite scroll automatically loads more content when the user reaches the bottom of the page. It provides code to initially fetch data on the server page and display a loading indicator at the bottom. It then uses the react-intersection-observer library to check if the loading indicator is visible and fetch more data from the server. It manages fetching the next page by updating the URL state and re-rendering the page component with the new data.

Uploaded by

satyendra.bold
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
0% found this document useful (0 votes)
335 views45 pages

Next Level Nextjs

The document discusses implementing infinite scroll in Next.js. It explains that infinite scroll automatically loads more content when the user reaches the bottom of the page. It provides code to initially fetch data on the server page and display a loading indicator at the bottom. It then uses the react-intersection-observer library to check if the loading indicator is visible and fetch more data from the server. It manages fetching the next page by updating the URL state and re-rendering the page component with the new data.

Uploaded by

satyendra.bold
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 45

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!

You might also like