All 29 Next.js(13+) Mistakes Beginners Make

video

By default all pages and component are server component

server component

  • we can call api without using useEffect(()=>{},[])

  • build on server side and html files is send to client

1: "use client" too high →to make client component

  • → if we use "use client" in root page → all imported component will also become client component

  • → "use client" should always be used in leaf node

  • if you want to use any hook → then we have to make a client component →"use client"

2: Not refactoring for "use client"

  • when whole page is server component , we need to add any interactivity to that component → so we needed to create a new client component , to separate out logic and interactivity

  • we dont want to make root page client component , it will effect all imported file to become client component

3: Thinking a component is a server component because it does not have "use client"

  • if parent is client component then its children will also be client component

  • it would be better to make child , → client component so that it dont really on its parent to indirectly make him a client component

4: Thinking that a server component becomes a client component if you wrap it inside a client component

<ClientComponent>
    <ServerComponent/>
</ClientComponent>
  • ServerComponent → not become the client component , when wrapped by client comonent

  • “use client“ → effect all imported file and then imported file become client component

5: Using state management (Context API, Zustand, Redux) in server components

  • big no, all hooks and state management can only be used by Client Component → “use client“

6: Using ‘use server’ to create a server component

  • “use server“ → create server actions

  • by default all components are server component, that reside on app directory

  • ‘server-only’ → prevent server component , to be imported in client component →gives error

7: Accidentally leaking sensitive data from server to client

8: Thinking that client components only run in the client

  • client components run in both →client and server(pre-render to HTML)

  • server components run in both →server

9: Using browser API’s (e.g. localStorage) incorrectly

  • client components run in both →client and server → so window is not found on server side

  • 3 way to solve this issue

      if(typeof window!== "undefined"){
          window.localstorage.getItem("isFavourite")
      }
    
useEffect(()={
     window.localstorage.getItem("isFavourite")
},[])
dynamic(()=>import(),{ssr:false})

10: Getting hydration errors

→suppressHydrationWarning

11: Incorrectly dealing with third-party components

2 way to deal with it

create separate component for 3rd party library and make it client component →”use client”

// Carousel.tsx
"use client"
import Carousel from "react-amazing-carousel"
export deafult Carousel

dynamic import → to prevent running in server → since client run on both client and server

dynamic(()=>import(),{ssr:false})

12: Using route handlers for getting data

  • server components → GET

  • server actions → POST/PUT/DELETE → “use server“

  • route handlers(API) → webhooks

13: Thinking it’s a problem to get the same data in different places

  • it is ok to call same api in different server component to get data → in nextjs there is a concept called “caching“

  • in same render pass , if there is 2 server component calling same api to get data → so with the help of caching → api is called only once and stored it in cache

  • if any orm(prisma) is used for data fetching then it will be different scene

14: Getting a ‘waterfall’ effect when fetching data

  • waterfall effect is caused due to → sequential data fetching is done

    • await getProduct() → 2sec

    • await getRating() → 2sec

      • so both take total 4sec → getRating() will start only after data fetching of getProduct()
  • to avoid waterfall effect → parallel data fetching is done

    • Promise.all([getProduct(),getRating()]) → 2sec

      • so both take total 2sec → getRating() and getProduct() both fetch data simultaneously

      • but any one promise fail → none of them will get executed

    • Promise.allSetteled([getProduct(),getRating()]) → 2sec

15: Submitting data to server component or route handler

  • for any mutation (POST/PUT/DELETE) → server actions

  • “use server“ → to make a file server actions compatible → only function is written → no endpt is required

  • <form onSubmit\={}></form> → not correct way

  • <form action\={addProduct}></form>

async function addProduct(formData: FormData){
"use server"
    await prisma.product.create({
        data:{
            title: formData.get("title") as string // "title" -> field name
        }
    })
}

revalidatePath(“/products“)

16: Getting confused when the page doesn’t reflect data mutation

  • revalidatePath(“/products“) → to overcome caching update issue → product page → to update all latest changes

17: Thinking that server actions can only be used in server components

  • can be called as any other fn in client component also

18: Forgetting to validate & protect server actions

  • authenticate → only authenticated user can do →update,put,delete

  • zod →to check data is coming from formData

19: Adding ‘server-only’ to make sure something stays on the server

  • import ‘server-only’ → prevent exposing api and also prevent component to run on client side

20: Misunderstanding dynamic routes (params & searchParams)

  • app/product/[id]/page.tsx → /product/123?color=green

  • params and searchParams can only be accessed on page.tsx

[id] = params.id → if i change the name (id to no) →[no] =params.no

// app/product/[id]/page.tsx 
export default function ProductPage({params,searchParams}){
  return (
    <>
        <>{params.id}</>  // 123
        <>{searchParams.color}</>  // green
    </>
 )
}

21: Incorrectly working with searchParams

  • /product?color=green →now there is 3 button (red,green,blue)→click on any of this button will change url

      const router = useRouter()
      red ->onClick={()=>router.push(`/product?color=red`)}
      green->onClick={()=>router.push(`/product?color=green`)}
      blue->onClick={()=>router.push(`/product?color=blue`)}
    

    changing a url is not an issue here → reading the url is issue here

  • searchParams → can only be used in page component → page component is a server component → so when url changes it will send a network request → so that value get updated in searchParams

  • now to over come this issue → useSearchParams()

useSearchParams() can only be used in client component , → so it should only be use in leaf component

"use client"
export default function ProductPage({params,searchParams}){
  const searchParams = useSearchParams()
return (
    <>
        <>{searchParams.get('color')}</>  // green
    </>
 )
}

22: Forgetting to deal with loading state

  • /app/product/loading.tsx → this loading file will be displayed till product page is loaded

  • loading.tsx → out of the box nextjs feature , but it loads complete page , but i dont want that

23: Not being granular with Suspense

<>
    <h1></h1>
    <Suspense fallback="loading..">
        <Product/> // data feching is only done there
    </Suspense>
    <FavriteBtn />
</>
  • so loading will be shown only till data fetch is completed

  • h1,loading…,FavriteBtn → h1,Product,FavriteBtn

24: Adding Suspense in the wrong place

  • we have to wrap whole component with suspense →<Product/>

  • suspense will not work if we wrap all tags of <Product/> , by going inside it

25: Forgetting ‘key’ prop for Suspense

  • fetch the data when id changes in url

  • so till data is been fetch , loading will be visible

  • so with the help of key , Suspense → knew that id is changed → fetch data based on id →till data is been fetch , loading will be visible

// app/product/[id]/page.tsx 
export default function ProductPage({searchParams}){
  return (
    <>
        <>{searchParams.id}</>  
         <Suspense key={searchParams.id} fallback="loading..">
            <Product id={searchParams.id}/> // data feching is only done there
          </Suspense>
    </>
 )
}

26: Accidentally opting a page out of static rendering

→ npm run build → to check each page → whether it is static or dynamic

27: Hardcoding secrets

  • .env.local → to keep the secrets away from leaking

  • this file is also git ignored

28: Not making a distinction between client and server utils

  • fetch data fn on separate utils file → server-utils.tsx → import “server-only“

  • if that fn is called on client it will give error due to →“server-only“

29: Using redirect() in try / catch

  • redirect() → only work on server side

  • redirect() → should be used outside the try/catch

Server Actions (revalidatePath, useFormStatus & useOptimistic)

  • useFormStatus → status of form (submitting, successful, or has errors.)
import { useFormStatus } from '@nextjs/react';

function MyForm() {
  const { isSubmitting, isSuccess, isError } = useFormStatus();

  return (
    <form>
      {/* Form fields */}
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
      {isSuccess && <p>Form submitted successfully!</p>}
      {isError && <p>An error occurred.</p>}
    </form>
  );
}