Data Fetching: Why Server Actions Outperform Route Handlers

Rishi BakshiRishi Bakshi
4 min read

One of the common beginner mistakes I’ve encountered is misunderstanding the difference between when to use route handlers and when to use server actions for data handling in Next.js. With the introduction of server actions and server components in Next.js, it's important to know the right tool for each job.

In this article, we’ll break down the mistake of using route handlers unnecessarily for data fetching and mutation when server actions or direct GET requests within server components would suffice. We'll cover the purpose of route handlers, when to use them, and how to avoid this common mistake.


Understanding Route Handlers and Server Actions

In Next.js, there are two primary ways to handle server-side data:

  1. Server Components with Server Actions:

    • Server components can directly fetch data (GET requests) from databases or APIs.

    • Server actions can handle data mutations like POST, PUT, DELETE, PATCH requests.

  2. Route Handlers:

    • Used for more complex server-side operations, like handling webhooks or other custom server routes, where fine-grained control over the request/response lifecycle is needed.

The mistake occurs when developers use route handlers in the app/api/ folder for simple operations like fetching data or making mutations, which could be handled directly within server actions or server components. Let’s take a closer look.


Example: When Not to Use a Route Handler

Imagine you're building a user profile page, and you need to update the user's name and email. The incorrect way would be to use a route handler in the app/api/ folder for this operation when a server action would be simpler.

Incorrect Approach:

// app/api/updateUser/route.ts
export async function POST(req) {
    const data = await req.json();
    const { name, email } = data;

    // Simulate updating the user in the database
    await updateUserInDB({ name, email });

    return new Response(JSON.stringify({ success: true }), { status: 200 });
}

In this case, we’re creating a full route handler just to update the user profile. This introduces unnecessary overhead because creating an API route in Next.js spins up a serverless function or server environment each time the route is called. This approach can lead to performance issues, especially when simple tasks like updating user data can be handled more efficiently with server actions.

Serverless functions incur overhead due to their isolated nature and cold starts, which could slow down response times and impact scalability if used unnecessarily.


Correct Approach: Using Server Actions

Next.js server actions allow you to mutate data directly inside server components, without the need for creating separate API routes. This streamlines your application and reduces unnecessary code duplication.

Here’s how you can use server actions for the same functionality.

// profile/page.js
import { updateUserInDB } from './db';

export const updateUserProfile = async (formData) => {
    'use server'; // This directive ensures this is a server action
    const { name, email } = formData;

    // Simulate updating the user in the database
    await updateUserInDB({ name, email });

    return { success: true };
};

const UserProfile = async () => {

    const handleSubmit = async (data) => {
        // Use the server action to update profile
        await updateUserProfile(data); 
    };

    return (
        <form action={handleSubmit}>
            <input type="text" name="name" placeholder="Name" />
            <input type="email" name="email" placeholder="Email" />
            <button type="submit">Update Profile</button>
        </form>
    );
};

export default UserProfile;

Explanation:

  1. Server Action (updateUserProfile): The updateUserProfile function is marked with 'use server' to indicate that it will only run on the server. This allows it to perform server-side operations like database updates securely.

  2. Form Submission: The form submission uses the handleSubmit function, which calls the updateUserProfile server action. This bypasses the need for an API route.

  3. Efficient and Simple: By handling the data mutation directly in a server action, we eliminate the need for a separate API route, reducing both complexity and the chance of errors.


When to Use Route Handlers

While server actions are great for most use cases, there are still times when you need route handlers. For example:

  • Webhook handling: When integrating third-party services like payment gateways (e.g., Stripe), you may need to handle webhooks, where a service sends POST requests to your server. In this case, using the API folder for a dedicated route handler is the correct approach.

  • Complex APIs: If you’re building a more complex API that other clients might consume, or you need full control over the request/response lifecycle, route handlers are the way to go.


Conclusion

Next.js provides powerful server-side features, but knowing when to use server actions and route handlers is key to building efficient, maintainable applications. The key takeaway here is:

  • Use server components and server actions for most server-side operations, like data fetching and mutations.

  • Use route handlers in the app/api/ folder for more complex needs like webhook handling or external API routes.

By following these guidelines, you can avoid the mistake of over-complicating your Next.js applications with unnecessary route handlers and instead make the most of its built-in capabilities.

This ensures that your app is optimized, easier to maintain, and follows best practices. Happy coding!

1
Subscribe to my newsletter

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

Written by

Rishi Bakshi
Rishi Bakshi

Full Stack Developer with experience in building end-to-end encrypted chat services. Currently dedicated in improving my DSA skills to become a better problem solver and deliver more efficient, scalable solutions.