Server Actions in Next.js – Simplifying Server Side Logic

Server Actions in Next.js – Simplifying Server Side Logic

A comprehensive guide on Next.js Server Actions and the amazing things that you can do with them

Next.js has become a React framework of choice for many developers, as it's continually making it easier to develop SEO and performance-optimized web applications through its new features. The most significant being the release of the new App Router, which encompasses many features such as React Server Components, support for nested routes and layouts, caching and revalidation, and more.

Another exciting feature is the introduction of new server actions which are, as the name might suggest, functions that run on the server but can also be called and executed from the client, eliminating the need for creating and managing API endpoints.

Through this blog, we'll try to understand server actions, ways to define and invoke them, and their benefits and usage.

Server Actions:

Server Actions in Next.js are RPC (Remote Procedure Call) endpoints, i.e., they are programs or functions that execute on a remote server or a different address space, but they can be called just like local functions or method calls.

We can use these to perform all kinds of server-related tasks like data retrieval, form handling, authentication, and more, directly from the client side without making any API requests to certain routes.

At the time of writing this blog, Server Actions are still an experimental alpha feature in Next.js, and they need to be enabled explicitly in the next.config.js file by setting the serverActions flag to true.

module.exports = {
  experimental: {
    serverActions: true,
  },
}

The concept of Server Actions is not exclusive to Next.js. It is a built-in feature of React that has been seamlessly integrated into Next.js. We'll learn more about this later on.

Server and Client Components:

Given that Server Actions are defined and invoked differently in Server and Client components, it's important to understand the difference between these to get a better grasp of things as a whole.

Client components in Next.js are rendered on the client side, just like traditional React components, whereas React Server components are rendered on the server.

In Next.js v13, all the components or pages are server-rendered by default. In simpler words, they are located close to the server and have access to the server ecosystem.

To make a component render on the client side, we have to use the "use client" directive on top of it.

I would encourage you to read this blog to get a more in-depth understanding of Server and Client components in Next.js.

Defining Server Actions:

We have the option to define Server Actions in two ways:

  1. Directly within the component that utilizes them:

This is limited to Server Components only. Below is a valid server action:

async function myServerAction() {
  'use server';
  // some server-related task
};

We just have to add the use server directive on top of the function, and Next.js will recognize and execute it as a server action.

  1. In a separate file:

This applies to both Client and Server Components. We can define and export multiple server actions from a file, by just adding the use server directive at the top of it.

'use server';

export async function myServerAction() {
  // some server-related task
};

export async function anotherServerAction() {
  // some server-related task
};

Note that we cannot define Server Actions directly within Client Components. However, Client Components can import Server Actions from an external file and use them from there.

We can also pass a Server Action as a prop from a Server Component to a Client Component.

Use Cases of Server Actions:

Before we learn to invoke and use Server Actions, let's explore some of their potential use cases:

  1. Database Interactions: Next.js Server Actions allow us to write data directly to a database from the client side without creating separate API endpoints, which enhances efficiency.

  2. Server-Side Logic: Server Actions serve as a centralized location for handling server-side logic. They can execute various server-related business operations, such as sending emails and generating files, etc.

  3. External API Calls: We can seamlessly make calls to external APIs directly from Server Actions, eliminating the need to set up and manage additional API routes. This simplifies the integration of external services into our application.

In summary, Next.js Server Actions simplify server tasks, eliminating the need for separate API endpoints.

Invoking Server Actions:

There are three ways of invoking Server Actions:

Using action:

We can invoke a server action by using React's action prop, which is triggered when a form is submitted.

export const page = () => {

    async function addUser(data: FormData): Promise<void>{
        'use server'
        await addToDB(data)
    };

  return (
    <form action={addUser}>
        <label htmlFor="name">Name:</label>
        <input type="text" id="name" name="name" required></input>
        <button type="submit">Submit</button>
    </form>
  )
};

Using formAction:

We can also invoke server actions by leveraging React's formAction Prop, which allows us to handle elements like <button>, <input type="submit">, and <input type="image"> within a <form>.

export const page = () => {

    async function addUser(data: FormData): Promise<void>{
        'use server'
        await addToDB(data)
    };

  return (
    <form>
        <label htmlFor="name">Name:</label>
        <input type="text" id="name" name="name" required></input>
        <button formAction={addUser}>Submit</button>
    </form>
  )
};

Using startTransition:

Both of the invocation methods listed above are related to the <form/> element.

We can also invoke server actions outside the <form/> element by custom invocation using the startTransition method which is provided by the useTransition hook.

Let's assume that our server action is defined in a separate file, and we have to invoke it from a client component that doesn't contain a <form/> element. We can achieve it like this:

'use client';

import { useTransition } from 'react';
import { addUser } from '@/actions/add-user';

export default function User() {
  let [isPending, startTransition] = useTransition();

  return (
    <button onClick={() => startTransition((data: FormData) => { 
           addUser(data: FormData)})}>
      Add User
    </button>
  )
};

Read more about the useTransition hook in React here.

Conclusion:

This blog aimed to give you a thorough introduction to Next.js Server Actions. I would encourage you to investigate the topic more on your own from the Next.js official documentation.

You should try to explore the following topics next:

  • Server mutations with redirect, revalidatePath, and revalidateTag

  • useOptimistic

  • Progressive Enhancement

  • useFormStatus

I'll cover some of these topics myself in my upcoming blogs. Until then, I'll be glad to answer your queries, if any.

Thanks for reading!