All 29 Next.js(13+) Mistakes Beginners Make
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>
);
}