Code Review Best Practices: Improving Code Quality and Collaboration in Next.js πŸ› οΈ

VivekVivek
5 min read

Code reviews are a crucial part of any development process. They ensure that the code is not only functional but also clean, efficient, and maintainable. In the context of Next.js, where server-side rendering, API routes, and dynamic routing are common, code reviews can significantly improve the quality of the application. In this post, we'll dive into practical examples of how code reviews can improve Next.js applications using the App Router, focusing on real-world scenarios and corrections. πŸš€


1. Reviewing and Improving API Routes πŸ”

Original Code

Let's say you have a simple API route for fetching user data:

// app/api/users/[id]/route.js
import { getUserById } from '@/lib/db';

export async function GET(request, { params }) {
  const user = await getUserById(params.id);
  return new Response(JSON.stringify(user), { status: 200 });
}

Review Feedback

  • Error Handling: The API does not handle cases where the user is not found or when there is a database error.

  • Security: The response does not sanitize or validate the id parameter, which could lead to security vulnerabilities like SQL injection.

Improved Code

// app/api/users/[id]/route.js
import { getUserById } from '@/lib/db';

export async function GET(request, { params }) {
  try {
    const user = await getUserById(params.id);

    if (!user) {
      return new Response(JSON.stringify({ error: 'User not found' }), { status: 404 });
    }

    return new Response(JSON.stringify(user), { status: 200 });
  } catch (error) {
    return new Response(JSON.stringify({ error: 'Internal Server Error' }), { status: 500 });
  }
}

Key Takeaways

  • Error handling is crucial in API routes to ensure that the application can gracefully handle unexpected situations.

  • Validation and sanitization of input parameters protect against security vulnerabilities.


2. Optimizing Server-Side Rendering (SSR) Logic πŸ–₯️

Original Code

Here's an example of a server-side rendering (SSR) function in Next.js:

// app/page.js
import { getServerSideProps } from '@/lib/ssr';

export default async function HomePage() {
  const data = await getServerSideProps();
  return (
    <div>
      <h1>Home Page</h1>
      <p>{data.message}</p>
    </div>
  );
}

Review Feedback

  • Data Fetching: The getServerSideProps function name is misleading, as it implies a Next.js-specific API rather than a custom function.

  • Performance: The function could benefit from caching to reduce server load on repeated requests.

Improved Code

// app/page.js
import { fetchHomePageData } from '@/lib/ssr';

export default async function HomePage() {
  const data = await fetchHomePageData();
  return (
    <div>
      <h1>Home Page</h1>
      <p>{data.message}</p>
    </div>
  );
}
// lib/ssr.js
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 60 }); // Cache for 1 minute

export async function fetchHomePageData() {
  const cachedData = cache.get('homePageData');

  if (cachedData) {
    return cachedData;
  }

  const data = await fetch('https://api.example.com/home');
  const jsonData = await data.json();

  cache.set('homePageData', jsonData);
  return jsonData;
}

Key Takeaways

  • Meaningful naming helps maintain clarity in your codebase.

  • Implementing caching can greatly improve performance, especially for pages with high traffic.


3. Enhancing Component Reusability with Props 🎨

Original Code

A typical component might be hardcoded, making it less reusable:

// components/Button.js
export default function Button() {
  return (
    <button className="bg-blue-500 text-white px-4 py-2">
      Click Me
    </button>
  );
}

Review Feedback

  • Hardcoded Values: The button's text and styles are hardcoded, limiting its reusability across different parts of the application.

Improved Code

// components/Button.js
export default function Button({ text, color = 'blue', onClick }) {
  return (
    <button
      onClick={onClick}
      className={`bg-${color}-500 text-white px-4 py-2`}
    >
      {text}
    </button>
  );
}
// Usage example in app/page.js
import Button from '@/components/Button';

export default function HomePage() {
  return (
    <div>
      <h1>Home Page</h1>
      <Button text="Submit" color="green" onClick={() => alert('Submitted!')} />
      <Button text="Cancel" color="red" onClick={() => alert('Cancelled!')} />
    </div>
  );
}

Key Takeaways

  • Props are essential for creating reusable components.

  • Avoid hardcoding values that may need to change depending on the context in which a component is used.


4. Refining State Management in Components 🧠

Original Code

Consider a component managing state for form input:

// components/Form.js
import { useState } from 'react';

export default function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = () => {
    // Submit logic
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <input value={email} onChange={(e) => setEmail(e.target.value)} />
      <button type="submit">Submit</button>
    </form>
  );
}

Review Feedback

  • State Management: Separate state handling logic for each field increases code complexity and can lead to duplication.

Improved Code

// components/Form.js
import { useState } from 'react';

export default function Form() {
  const [formData, setFormData] = useState({ name: '', email: '' });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({ ...prevData, [name]: value }));
  };

  const handleSubmit = () => {
    // Submit logic
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={formData.name} onChange={handleChange} />
      <input name="email" value={formData.email} onChange={handleChange} />
      <button type="submit">Submit</button>
    </form>
  );
}

Key Takeaways

  • Consolidate state management to reduce duplication and simplify your code.

  • Use dynamic property names in your state updates to handle multiple inputs efficiently.


5. Improving Performance with Memoization ⚑

Original Code

Sometimes, developers overlook performance optimizations in components:

// components/UserList.js
import { useState, useEffect } from 'react';

export default function UserList({ users }) {
  const [filteredUsers, setFilteredUsers] = useState([]);

  useEffect(() => {
    const activeUsers = users.filter((user) => user.active);
    setFilteredUsers(activeUsers);
  }, [users]);

  return (
    <ul>
      {filteredUsers.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Review Feedback

  • Performance: The filtering operation runs on every render, even if the list of users hasn’t changed.

Improved Code

// components/UserList.js
import { useMemo } from 'react';

export default function UserList({ users }) {
  const filteredUsers = useMemo(() => {
    return users.filter((user) => user.active);
  }, [users]);

  return (
    <ul>
      {filteredUsers.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Key Takeaways

  • Memoization with useMemo can prevent unnecessary re-calculations and improve performance, especially in components that perform expensive computations.

Code reviews in a Next.js application are not just about catching errors; they’re about improving the overall quality and maintainability of the codebase. By applying these practical examples, you can ensure that your code is clean, efficient, and aligned with best practices. Remember to focus on key aspects such as error handling, performance optimization, reusability, and state management. Happy coding! πŸŽ‰


Feel free to share your own code review tips and experiences in the comments below! πŸ’¬

10
Subscribe to my newsletter

Read articles from Vivek directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vivek
Vivek

Curious Full Stack Developer wanting to try hands on ⌨️ new technologies and frameworks. More leaning towards React these days - Next, Blitz, Remix πŸ‘¨πŸ»β€πŸ’»