DEV Community

Cover image for Understanding Next.js Server Actions With Examples
Tapas Adhikary
Tapas Adhikary

Posted on • Originally published at blog.greenroots.info

Understanding Next.js Server Actions With Examples

Next.js is a React-based framework for building performant, optimized, and scalable full-stack applications. As React developers, we create components, manage state, and handle data communications between them. Then Next.js comes on top of it to provide the required optimizations.

Next.js 13.4 provides us with the stable App Router that includes:

  • Foundational React Server Components

  • Nested Routes and Layouts

  • Various ways of data fetching, caching, revalidation

  • Built-in SEO

  • Streaming

  • Suspense

In this article, we will discuss and learn about an exciting data-fetching technique called Server Action. But before we get there, we must spend some time understanding the concept of React Server Components.

The ReactJS team introduced the server components in 2020, and the Next.js app router has been built using the "Thinking in Server Components" mindset. If you are new to this concept, I strongly suggest you view this beginner-friendly video tutorial to get a firm grip on the server component concept.

Server Components and Server Actions are NOT the same things. You need to have basic understanding of Server Components to understand Server Actions easily.

The Story of Forms and Actions in React

As a web developer, you must have implemented HTML forms and form actions countless times! Hence, a code snippet like this should not be new to you.

export default function Cart() {

  function addToCart(e) {
    // Read the Form Data
    // Make the API call
    // Change the Component state
  }

  return (
    <form method="post" onSubmit={addToCart}>
      <label>
        Select your favorite brand:
        <select name="selectedBrand" defaultValue="apple">
          <option value="apple">Apple</option>
          <option value="oppo">Oppo</option>
          <option value="samsung">Samsung</option>
        </select>
      </label>
      <label>
        Enter the Count:
        <input
          type="number"
          name="count"
          placeholder="Specify how many do you want" />
      </label>
      <hr />
      <button type="reset">Reset</button>
      <button type="submit">Submit</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

It is a simple React component with a form that handles adding a product and its quantity into the cart. Now zoom into the addToCart function in the above code. It is supposed to perform some tasks while adding the selected items to the cart.

  • Read the form data to capture the user inputs.

  • Make an API call(using fetch or something equivalent) over the network.

  • Update the component state(or application state) based on the API call response.

Be mindful that all these happen from the client side of your application. It means the component code gets downloaded as part of the bundle on your user's browser. The component makes an API call over the network and waits for it to get complete. The "wait" can be longer based on the latency, network speed, etc.

A bit about React Server Components

With React Server Components(RSC) we have learned(in case you are new to it, I highly recommend you to go through the video tutorial I mentioned above) that we can move our components to the server and colocate it to our data store so that,

  • They have backend access without any network roundtrips.

  • We can avoid network waterfalls.

  • We improve your app's performance with zero bundle size.

Considering user experience, maintainability, and performance cost, these benefits are huge.

When we move the components to the server with React Server Components, they will most likely be colocated to your application's data store or database. You still need to fetch the data to your components from the database, but the data fetching will be way faster than fetching from a client component.

client-server

Next.js App Router has gone the "Thinking in Server Components" way. By default, all the components are server components, and if you want to write a client component, you need to mark them explicitly using the use client directive.

It means a component hierarchy in Next.js can combine server and client components. We can use server components as much as possible and client components wherever we need user interactions or event handling.

Component Hierarchy

What are Server Actions in Next.js?

Next.js provides data fetching techniques and patterns for developers to use. One of the techniques is by using server actions. (While writing this article) server actions are still an experimental alpha feature in Next.js, and you need to enable it explicitly using the serverActions flag in the next.config.js file.

module.exports = {
  experimental: {
    serverActions: true,
  },
}
Enter fullscreen mode Exit fullscreen mode

Server actions are built on React Actions that you can define in server components and/or calls from client components. Server actions are JavaScript async functions that run on the server by the user interactions on the client.

 async function addItemToCart(data) {
    'use server'
    await addToDB(data)
 }
Enter fullscreen mode Exit fullscreen mode

You can invoke server actions using the <form/> element's acion prop in ReactJS.

<form action={addItemToCart}>
...
...
</form>
Enter fullscreen mode Exit fullscreen mode

Alternatively, you can use the formAction prop on the elements(like submit button, input text, etc) inside a <form/>.

<form action={handleSubmit}>
    <input type="text" name="name" formAction={handleName} />
    <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Now, let us understand the usages of server actions with respect to both server and client components.

How to use server actions in Next.js server components?

To use the server actions inside the server component, first create an async function with the use server directive. You must place the directive on top of the function body.

Then, you can call the server function from the action prop of the <form/> element or use the formAction prop on the elements(like the submit button, input text, etc.) inside a <form/> element.

export default function CourseComments() {
  async function addCommentToCourse(comment, courseId) {
    'use server'

     await saveComment({comment, courseId});
  }

  return(
     <form action={addCommentToCourse}>
       <button type="submit">Add Comment</button>
     </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

How to use server actions from client components in Next.js?

As the component hierarchy can have both server and client components, you may want to use server actions inside the client components as well. For that, create your actions in a separate file, say, add-comments.js.

'use server'

import { addCommentToCourse } from '@/data/course'

export async function addComment(data, courseId) {
  const response = await addCommentToCourse(data, course);
  return response;
}
Enter fullscreen mode Exit fullscreen mode

Please note you still need to use the use server directive at the top of the file(even before the imports) to inform Nex.js about the file that includes server actions.

Now, in the client component, it is just about importing the actions and calling them either from the action prop or the formAction prop, as we have seen before.

'use client'

import { addComment } from '@/actions/add-comment';

export default function CourseComment() {
  return (
    <form action={addComment}>
      <button type="submit">Add Comment</button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

How to invoke server actions outside of Forms?

So far, we have learned about two invocation methods to call the server actions:

  • Using the action prop.

  • Using the formAction prop.

Both of these methods are related to the <form/> element. Then, how can we invoke server actions outside of the <form/> elements? You can achieve the custom invocation using startTransition method provided by the useTransition hook.

If you are new to the React 18 useTransition hook, you can learn it with examples from this video tutorial:

Let's assume we have our server action in a separate file like before, and we want to invoke it from a client component without a <form/> element.

'use client'

import { useTransition } from 'react'
import { addComment } from '@/actions/add-comment';

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

  return (
    <button onClick={() => startTransition((comment, courseId) => { 
           addComment(comment, courseId)})}>
      Add Comment
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

Conclusion & What's Next?

That's an introduction to the Next.js server actions. You can read further from the Next.js official documentation to learn more. I hope you understand the concept and the usage of server actions well.

Next, you should explore the following topics:

  • Server mutations with redirect, revalidatePath, and revalidateTag.

  • About the useOptimistic hook and how it helps to make the app more responsive.

  • What is Progressive Enhancements?

  • How useFormStatus hook comes in handy with server actions and progressive enhancements.

I'll cover all these topics on my YouTube channel and this blog in the coming days. So, If you like my explanation and teaching, don't forget to subscribe to my channel and blog newsletter so that you do not miss out on any new content.


Before We End...

That's all. Thanks for reading it. I hope it was insightful. If you liked the article, please post likes and share it in your circles.

Let's connect. I share web development, content creation, Open Source, and career tips on these platforms.

Top comments (10)

Collapse
 
chiragagg5k profile image
Chirag Aggarwal

So traditionally to perform server side code, we created API routes which would be fetched on the client side. To avoid exposing sensitive data we needed to protect these routes with additional codes.

Now with server actions, it seems there are no more exposed API routes. So does this make it more secure? Or are server actions still using API calls behind the surface?

Collapse
 
atapas profile image
Tapas Adhikary

That’s right! It is gonna b secured.

Collapse
 
parzival_computer profile image
Parzival

Fast to read, concise and enchiring, thanks.

Collapse
 
atapas profile image
Tapas Adhikary

Thanks, Odgen! Glad you liked it.

Collapse
 
chrisnowicki profile image
Chris Nowicki

Such a great article and overview of their new server actions. I am using them in my portfolio site to for my contact me form.

Thanks for sharing such great content! πŸ”₯

Collapse
 
atapas profile image
Tapas Adhikary

Great, thanks Chris.

Collapse
 
engineervinay profile image
Vinay Patil

Nice article @atapas !!

Collapse
 
atapas profile image
Tapas Adhikary

Thanks Vinay! Appreciate you looked into it and glad you liked it.

Collapse
 
j_potter_3434 profile image
John Potter

It would be nice to clear the form and let users know that the form was submitted :)

Collapse
 
shubhankarval profile image
Shubhankar Valimbe

What are your thoughts about Gatsby for SSG? Since API routes in Gatsby can be simply done using the File System, and dynamic URLs can be made easily by adding special filenames.