Tips, Good Practices, and Pitfalls with Next.js 15
created at 09/12/2024 •updated at 13/12/2024
Learning a new framework is always an adventure, and Next.js is no exception, especially with the App Router introduced in version 15. While the framework makes building web applications easier, navigating its nuances can be tricky for beginners. If you already have a basic understanding of React and JavaScript, this guide will help you take your Next.js skills to the next level by covering essential tips, good practices, and common pitfalls.
Let’s dive into the key points to keep in mind while working with Next.js 🚀
Tips for Mastering Next.js 💡
1. Understand {children}
in Client Components
When building components in Next.js, it’s crucial to understand the behavior of {children}
. Imagine you're building a LEGO set where client-side and server-side components can snap together like magical building blocks. That's exactly what {children}
in Next.js allows you to do!
Here's the cool secret: Just because a component is a "client component" doesn't mean everything inside it becomes a client component too. It's like having a smart container that can hold different types of building blocks.
Let's break down a practical example:
'use client';// This is a client-side button componentfunction MyButton({ children }) {const handleClick = () => {// Some interactive magic happens hereconsole.log('Button clicked!');};return (<button onClick={handleClick}>{children}</button>);}// You can now put SERVER components inside this CLIENT button!export default function ContainerButton() {return (<MyButton>{/* This could be a server component fetching data */}<ServerComponent /></MyButton>)}
Why is this so awesome?
Your client components can wrap server components
Interactive elements (like buttons) can contain server-rendered content
You keep a clean separation between client and server responsibilities
Real-world scenario: Imagine a dashboard where:
You have a filterable list (client component)
The list items are fetched from a database (server component)
You want smooth interactions without losing server-side rendering benefits
'use client';function FilterableList({ children }) {const [filter, setFilter] = useState('');return (<div><inputplaceholder="Filter items"onChange={(e) => setFilter(e.target.value)}/>{children}</div>);}// Server component can live inside the client component!export default function Dashboard() {return (<FilterableList><ServerFetchedItems /></FilterableList>);}
🔑 Key Takeaways:
Client components don't "infect" their children with client-side rendering
You get maximum flexibility in component composition
Server and client worlds can coexist harmoniously
It's like having a superpower that lets you mix and match different component types without losing their unique strengths !
2. Use Helpful Libraries
When I first started coding, I was hesitant to rely on third-party libraries, believing that building everything from scratch would be more efficient. However, I quickly learned that these tools not only save you a ton of time but also significantly boost your productivity. More often than not, the final code is more robust than what I could have written myself 😥
I suggest you :
Zustand: Whether you're working with React or Next.js, managing state across multiple components can quickly become a nightmare (trust me, I speak from experience 😭). To avoid prop drilling (passing props through multiple components) and if you're not comfortable with React Contexts, this library is perfect for you !
clsx: Imagine you're styling a button in React, and you want its classes to change based on different conditions. Maybe you want it to look different when it's disabled, or have a special style when it's selected. Normally, this would involve some tricky string concatenation or complicated conditional logic. That's where clsx comes to the rescue! With clsx you can easily combine multiple classes, conditional classes become super simple and your code looks clean and readable (No more messy string concatenation or complicated ternary operators) 👌 For example, the ShadCN library, built on Tailwind CSS, makes effective use of this tool.
nuqs: You want to save your page's state in the URL ? Use nuqs ! Think about filters on a product page, search results, or a multi-step form where you want to be able to share or bookmark the exact state of the page. That's exactly what nuqs helps you do, but in a super smooth and type-safe way !
Good Practices for Building with Next.js ✅
1. Avoid Unnecessary useEffect
⚠️
One of the most common pitfalls in React development is overusing useEffect
. Most developers overuse this hook like it's their favorite Swiss Army knife, when in reality, there are often simpler, cleaner ways to solve problems.
Think of useEffect like a powerful but temperamental wizard. You want to call on it only when absolutely necessary!
🚨 Common useEffect Anti-Patterns to Avoid:
Fetching data that can be done server-side
Calculating values that can be derived directly
Updating state based on prop changes (use computed values!)
Running complex logic on every render
Pro Tip: React's official docs have a magical guide called "You Might Not Need an Effect" - it's like a treasure map for avoiding unnecessary hook complexities !
💡 The Golden Rule:
If you can solve something with a simple calculation, direct event handler, or server-side logic, DO THAT instead of reaching for useEffect !
2. Fetch Data on the Server and Use Suspense ⏳
Fetching data on the server is a hallmark of Next.js, enabling faster page loads and better SEO. Combine this with React’s Suspense
for handling asynchronous operations elegantly.
Here’s how you might fetch blog posts on the server and render them with Suspense
:
import { Suspense } from 'react';async function fetchPosts() {const res = await fetch('https://api.example.com/posts');return res.json();}function Posts() {const posts = fetchPosts(); // Automatically cached on the serverreturn (<ul>{posts.map(post => (<li key={post.id}>{post.title}</li>))}</ul>);}export default function BlogPage() {return (<Suspense fallback={<div>Loading posts...</div>}><Posts /></Suspense>);}
Important : Make sure to place Suspense
components directly before the asynchronous child components.
3. Take Advantage of Nested Layouts and Routing 🗺️
Next.js 15’s App Router allows for nested layouts, dynamic routes, parallel routes, and intercepting routes. These features simplify organizing and displaying content dynamically.
Tips: Check out the Next.js App Router Playground for inspiration and examples (It's basically a cheat code for understanding these concepts!).
4. Address Props Drilling
I've totally mentioned this before, but let me say it again (because some things are worth repeating) Avoid Props drilling ! Passing props through multiple levels of components can make your code harder to maintain. Solutions include :
React Context API
Zustand
Props Restructuring: Passing grouped props or utility objects instead of individual props.
Common Pitfalls to Avoid ⚠️
1. Hydration Errors
Hydration errors occur when the server-rendered HTML doesn’t match the client-rendered DOM. What's Actually Happening?
When your web page loads, there are two main steps :
Server renders the initial HTML (like drafting a blueprint)
Client-side JavaScript "hydrates" this HTML (like actually building the structure)
🚨 Hydration Errors Happen When:
Your initial render is unpredictable
You use random or time-based values
The server and client see things differently
Let's see some problematic code:
function RandomNumberComponent() {// ❌ BAD: Will cause hydration errors!const randomValue = Math.random();return <div>Your lucky number: {randomValue}</div>;}
How to Prevent Hydration Nightmares:
// ✅ GOOD: Move randomness to client-side effectsfunction SafeComponent() {const [randomValue, setRandomValue] = useState(null);// Use useEffect to run on the client onlyuseEffect(() => {setRandomValue(Math.random());}, []);return <div>Lucky number: {randomValue}</div>;}
3. Additional Resources
Pro Tip: Grab some coffee, and check out these amazing resources :
Vercel’s article on common mistakes and solutions.
The ByteGrad YouTube channel, especially their video on common Next.js mistakes.
Conclusion
You've just unlocked a treasure chest of web development potential. By embracing these practices, you're not just building apps - you're crafting digital experiences that are lightning-fast, rock-solid, and beautifully maintainable. The journey doesn't stop here. Think of Next.js as your coding playground: Dive into docs, explore community blogs, watch tutorials, experiment fearlessly (There are plenty of cool side projects to work on to improve your skills and lots of tech stacks to explore)
Remember: Every great developer started exactly where you are now. Each line of code is a step toward mastery. So, fire up your code editor, brew some coffee ☕, and let your creativity run wild. The web is your canvas, Next.js is your paintbrush !
Happy coding! 💻😊
written by
@valentinafso