Qamar Zia

Qamar Zia

FullStack E-Commerce Specialist

2026-02-25

Bad Next.js Patterns Are Quietly Killing Your Revenue

The Problem

Most developers use Next.js like React, which kills revenue, SEO, and conversions and causes business loss and customer distrust. They

  • Mark most components with "use client" directive — increased JS bundle size.
  • Fetch the data on the client side — slow page loads due to client-side latency.
  • Do not stream — waiting for long API calls results in slow server responses. which makes their application perform worse than it should. So what is the solution?

Solution

The direct solution to all of the performance overheads is to use Next.js as Next.js.

There are a few main bottlenecks that cause Next.js applications to perform slowly. Let's cover them one by one.

1. "use client" fatigue

  • Many developers add the "use client" directive at the top level of a component because of some client-side functionality. This alone

    • Makes the entire component a client component.
    • All of its children become client components.
    • More JS is sent to the browser.
    • Due to client-side rendering, SEO performance goes down. Like this:
    products.tsx
    1"use client" // upper level component; 2 3import { Button } from "@/components/ui/button"; 4import { 5 Card, 6 CardDescription, 7 CardFooter, 8 CardTitle, 9} from "@/components/ui/card"; 10import ProductCarousel from "./productCarousel"; 11import Link from "next/link"; 12import { motion } from "motion/react"; 13import { useCartStore } from "@/store/CartStore"; 14interface ProductInterface { 15 name: string; 16 id: string; 17 description: string; 18 realPrice: string; 19 salePrice?: string; 20 ratings: string; 21 link: string; 22 images: string[]; 23} 24const MotionCard = motion.create(Card); // lines that need client functionality 25const MButton = motion.create(Button); 26 27function Product({ 28 name, 29 description, 30 id, 31 realPrice, 32 salePrice, 33 images, 34}: ProductInterface) { 35 const popCart = useCartStore((state) => state.addToCart); 36 return ( 37 <MotionCard 38 initial={{ opacity: 0, y: 20, scale: 0.9 }} 39 whileInView={{ opacity: 1, y: 0, scale: 1 }} 40 transition={{ duration: 0.8, damping: 100, stiffness: 15 }} 41 style={{ 42 boxShadow: 43 "inset 2px 2px 3px hsla(0,0,100,0.2), 0 1px 3px hsla(0,0%,0%,50%)", 44 }} 45 className="text-black border-0 bg-white relative" 46 > 47 {salePrice ? ( 48 <p className="absolute right-2 top-2 z-10 rounded-lg bg-green-600/50 text-black font-black text-2xs p-2 flex justify-center items-center"> 49 {Math.round( 50 ((parseFloat(realPrice) - parseFloat(salePrice)) / 51 parseFloat(realPrice)) \* 52 100, 53 )} 54 %OFF 55 </p> 56 ) : ( 57 <></> 58 )} 59 <Link href={`/shop/${id}`}> 60 <ProductCarousel img={images} name={name}></ProductCarousel> 61 </Link> 62 <div className="flex flex-col p-2"> 63 <Link href={`/shop/${id}`} className="cursor-pointer"> 64 <CardTitle className="hover:underline cursor-pointer"> 65 {name} 66 </CardTitle> 67 </Link> 68 <CardDescription 69 className="line-clamp-2" 70 dangerouslySetInnerHTML={{ __html: description }} 71 ></CardDescription> 72 {salePrice ? ( 73 <p> 74 <span>{salePrice} PKR</span> 75 <del className="relative text-gray-500"> 76 {" "} 77 <span>{realPrice}</span> PKR 78 </del> 79 </p> 80 ) : ( 81 <p>{realPrice} PKR</p> 82 )} 83 <CardFooter className="w-full p-0"> 84 <MButton 85 initial={{ scale: 1 }} 86 whileHover={{ 87 scale: 1.2, 88 }} 89 className="text-xl w-full sm:text-2xl md:text-2xl" 90 onClick={() => { 91 popCart(id, 1); 92 }} 93 > 94 Add to Cart 95 </MButton> 96 </CardFooter> 97 </div> 98 </MotionCard> 99 ); 100} 101export default Product;
  • instead what should be done is:

    products.tsx
    1import { CardDescription, CardFooter, CardTitle } from "@/components/ui/card"; 2import ProductCarousel from "./productCarousel"; 3import Link from "next/link"; 4import { useCartStore } from "@/store/CartStore"; 5interface ProductInterface { 6 name: string; 7 id: string; 8 description: string; 9 realPrice: string; 10 salePrice?: string; 11 ratings: string; 12 link: string; 13 images: string[]; 14} 15import MotionCard from "./MCard"; // moved the logic to a separate component 16import MButton from "./MButton"; 17function Product({ 18 name, 19 description, 20 id, 21 realPrice, 22 salePrice, 23 images, 24}: ProductInterface) { 25 const popCart = useCartStore((state) => state.addToCart); 26 return ( 27 <MotionCard 28 initial={{ opacity: 0, y: 20, scale: 0.9 }} 29 whileInView={{ opacity: 1, y: 0, scale: 1 }} 30 transition={{ duration: 0.8, damping: 100, stiffness: 15 }} 31 style={{ 32 boxShadow: 33 "inset 2px 2px 3px hsla(0,0,100,0.2), 0 1px 3px hsla(0,0%,0%,50%)", 34 }} 35 className="text-black border-0 bg-white relative" 36 > 37 {salePrice ? ( 38 <p className="absolute right-2 top-2 z-10 rounded-lg bg-green-600/50 text-black font-black text-2xs p-2 flex justify-center items-center"> 39 {Math.round( 40 ((parseFloat(realPrice) - parseFloat(salePrice)) / 41 parseFloat(realPrice)) * 42 100, 43 )} 44 %OFF 45 </p> 46 ) : ( 47 <></> 48 )} 49 <Link href={`/shop/${id}`}> 50 <ProductCarousel img={images} name={name}></ProductCarousel> 51 </Link> 52 <div className="flex flex-col p-2"> 53 <Link href={`/shop/${id}`} className="cursor-pointer"> 54 <CardTitle className="hover:underline cursor-pointer"> 55 {name} 56 </CardTitle> 57 </Link> 58 <CardDescription 59 className="line-clamp-2" 60 dangerouslySetInnerHTML={{ __html: description }} 61 ></CardDescription> 62 {salePrice ? ( 63 <p> 64 <span>{salePrice} PKR</span> 65 <del className="relative text-gray-500"> 66 {" "} 67 <span>{realPrice}</span> PKR 68 </del> 69 </p> 70 ) : ( 71 <p>{realPrice} PKR</p> 72 )} 73 <CardFooter className="w-full p-0"> 74 <MButton 75 initial={{ scale: 1 }} 76 whileHover={{ 77 scale: 1.2, 78 }} 79 className="text-xl w-full sm:text-2xl md:text-2xl" 80 onClick={() => { 81 popCart(id, 1); 82 }} 83 > 84 Add to Cart 85 </MButton> 86 </CardFooter> 87 </div> 88 </MotionCard> 89 ); 90} 91export default Product; 92

    Here, I have moved the logic for creating motion elements to different components and have SSRed the whole component. It will now render on the server instead of the client.

    2. Fetching Data on the Client Side

    One of the biggest benefits of using Next.js is that we can fetch data on the server side, cache that data, and send it in the form of final HTML to the user.

  • Server fetch → HTML streamed

  • Client fetch → hydration + waterfall Instead of letting a user's device make 10 API calls, we do that heavy lifting on the server and send the data in one go to the user, resulting in faster load times, higher user satisfaction, higher conversion rates, better SEO due to server-rendered content, and better crawler efficiency. But what if we need the data on the client side? Well, that's also not a big deal. Here is a component that needs the data on the client side for sorting and other interactivity.

productsGrid.tsx
1"use client"; 2import { useState } from "react"; 3import Product from "./product"; 4import { 5 Combobox, 6 ComboboxInput, 7 ComboboxEmpty, 8 ComboboxList, 9 ComboboxItem, 10 ComboboxContent, 11} from "./combobox"; 12export interface ProductsGridInterface { 13 id: string; 14 permalink: string; 15 description: string; 16 name: string; 17 images: { name: string; src: string; alt: string }[]; 18 regular_price: string; 19 sale_price: string; 20 average_rating: string; 21} 22function ProductsGrid({ 23 data, // fetched on server and used in a client component 24}: { 25 data: ProductsGridInterface[] | null; 26 sort?: string; 27}) { 28 const [sort, setSort] = useState("name"); 29 return ( 30 <> 31 <div className="flex justify-end w-full"> 32 <Combobox 33 onInputValueChange={(e) => { 34 setSort(e); 35 }} 36 name="Sort" 37 items={["name", "realPrice", "salePrice"]} 38 > 39 <ComboboxInput placeholder="Sort" /> 40 <ComboboxContent> 41 <ComboboxEmpty>Select other options</ComboboxEmpty> 42 <ComboboxList> 43 {(item) => ( 44 <ComboboxItem key={item} value={item}> 45 {item} 46 </ComboboxItem> 47 )} 48 </ComboboxList> 49 </ComboboxContent> 50 </Combobox> 51 </div> 52 <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 max-w-300 m-auto"> 53 {data 54 ?.sort((a, b) => 55 sort === "name" 56 ? a.name.localeCompare(b.name) 57 : sort === "realPrice" 58 ? a.regular_price.localeCompare(b.regular_price, undefined, { 59 numeric: true, 60 }) 61 : sort === "salePrice" 62 ? a.sale_price.localeCompare(b.sale_price, undefined, { 63 numeric: true, 64 }) 65 : a.name.localeCompare(b.name), 66 ) 67 .map((i) => { 68 return ( 69 <Product 70 id={i.id} 71 link={i.permalink} 72 description={i.description} 73 key={i.name} 74 images={i.images.map((i) => i.src)} 75 name={i.name} 76 realPrice={i.regular_price} 77 salePrice={i.sale_price} 78 ratings={i.average_rating} 79 /> 80 ); 81 })} 82 </div> 83 </> 84 ); 85} 86export default ProductsGrid; 87

In this component, I could use useEffect to fetch the data from another place but I preferred fetching data on the server in a parent component and then giving that data to the client component as a prop

What This Did?

Well, I have the best of both worlds: I can fetch the data on the edge (if using Cloudflare) instantly and pass that to a client component without sacrificing client interactivity for sorting things. This makes Next.js a superior choice to many other frameworks.

3. Streaming

Streaming is a very useful feature of React that was introduced in React 18 where React introduced SSR and concurrent rendering. This lets you load the page instantly while letting you fetch the data for slow components on the server. Here is how it works.

SlowAsyncData.tsx
1import fetchProds from "./api/products/fetch"; 2import CategoriesSlide from "../components/ui/categories"; 3import fetchCats from "./api/categories/fetch"; 4import ProductsCollection from "../components/ui/productsCollection"; 5import Footer from "@/footer"; 6import Header from "./header"; 7import { Suspense } from "react"; 8import Loading from "@/components/ui/loading"; 9 10export default function Page() { 11 return ( 12 <main className="w-full"> 13 <Header /> 14 <div className="flex justify-center items-center"></div> 15 <div className="flex flex-col justify-center items-center max-w-screen"> 16 <h2 className="text-center text-4xl py-4">Find Your Favorite Shop</h2> 17 <div> 18 <Suspense fallback={<Loading title="Loading....." />}> // Slow data in suspense 19 <SuspenseData /> 20 </Suspense> 21 </div> 22 <div className="flex justify-center flex-col p-4 items-center max-w-screen"> 23 <Suspense defer fallback={<Loading title="Loading Location" />}> 24 <iframe 25 className="w-full aspect-square" 26 src="https://www.google.com/maps/embed?pb=!1m14!1m12!1m3!1d263.94635612964845!2d74.28046040263706!3d31.655695306418675!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!5e1!3m2!1sen!2s!4v1769208900396!5m2!1sen!2s" 27 width="800" 28 height="600" 29 loading="lazy" 30 ></iframe> 31 </Suspense> 32 </div> 33 </div> 34 <Suspense defer fallback={<Loading title="Loading footer...." />}> 35 <Footer /> 36 </Suspense> 37 </main> 38 ); 39} 40 41async function SuspenseData() {// slow async function 42 const featuredProductsParams = { 43 fields: "name,price,regular_price,sale_price,average_rating,id", 44 per_page: "8", 45 featured: "true", 46 }; 47 const onSaleProductsDataParams = { 48 fields: "name,price,regular_price,sale_price,average_rating,id", 49 per_page: "8", 50 on_sale: "true", 51 }; 52 const [featuredProductsData, onSaleProductsData, categories] = 53 await Promise.all([ 54 fetchProds(featuredProductsParams).catch((err) => { 55 console.log("error in featured products", err); 56 return null; 57 }), 58 fetchProds(onSaleProductsDataParams).catch((err) => { 59 console.log("error in on sale products", err); 60 return null; 61 }), 62 fetchCats().catch((err) => { 63 console.log("error in categories", err); 64 return null; 65 }), 66 ]); 67 68 const fpderr = featuredProductsData ? 0 : 1; 69 const osperr = onSaleProductsData ? 0 : 1; 70 const ctgerr = categories ? 0 : 1; 71 return ( 72 <> 73 <div className="max-w-300"> 74 {ctgerr == 1 ? ( 75 <h1>An error occured in fetching the data</h1> 76 ) : ( 77 <CategoriesSlide data={categories} /> 78 )} 79 {fpderr == 1 ? ( 80 <h1>An error occured in fetching the data</h1> 81 ) : ( 82 <ProductsCollection 83 title="Featured Products" 84 data={featuredProductsData} 85 /> 86 )} 87 {osperr == 1 ? ( 88 <h1>An error occured in fetching the data</h1> 89 ) : ( 90 <ProductsCollection title="On Sale" data={onSaleProductsData} /> 91 )} 92 </div> 93 </> 94 ); 95} 96

What This Did?

This lets your page load without waiting for that slow data to arrive and falls back to the ReactNode provided in the fallback prop. When the data has been fetched or the async function has resolved, it then loads and replaces that component. It uses the flight protocol to get the remaining parts of the components and partially hydrates the content afterward.


By optimizing your application this way you don't solve a technical application performance issue. Instead you:

  • Enhance the user experience
  • Benefits from better SEO and search engine visibility
  • And by the end of the day it drives you more revenue

Final Thought

Next.js is not slow.

Poor architectural decisions are.

If your e-commerce site isn’t converting like it should, it’s not always a marketing problem.

Sometimes it’s a rendering problem.

At QamarQonLabs, we specialize in performance-first Next.js architecture for e-commerce brands.

If you’d like a performance audit, reach out.