Published on

Understanding Server Actions in Next.js: A Practical Guide

Authors
  • Mehdi Akiki avatar
    Name
    Mehdi Akiki
    Twitter

Server Actions in Next.js provide a modern way to handle data mutations directly within server or client components. By marking functions with the "use server" directive, developers can streamline server-side operations without creating separate API routes. This guide explores what Server Actions are, their benefits, and how to integrate them effectively into your applications.

What Are Server Actions?

Server Actions are asynchronous functions that run on the server. They are ideal for handling data mutations such as form submissions or database updates. Unlike traditional API routes, Server Actions can be imported and used directly in client or server components, offering a seamless way to bridge client-server interactions.

Basic Example

Consider a simple form that submits user input to create a post:

// actions/createPost.js
"use server";

export const createPost = async (formData) => {
  const data = {
    title: formData.get("title"),
    content: formData.get("content"),
  };

  if (!data.title || !data.content) {
    throw new Error("All fields are required");
  }

  // Simulate a database save
  return { message: "Post created successfully" };
};

And the client-side component:

"use client";
import { createPost } from "../actions/createPost";

const PostForm = () => {
  return (
    <form action={createPost}>
      <label htmlFor="title">Title:</label>
      <input name="title" id="title" />

      <label htmlFor="content">Content:</label>
      <textarea name="content" id="content" />

      <button type="submit">Submit</button>
    </form>
  );
};

Why Use Server Actions?

Server Actions simplify server-side logic by reducing the need for dedicated API routes. Here are some benefits:

  • Less Boilerplate: Write fewer lines of code by avoiding separate API route files.
  • Type Safety: Maintain type safety when interacting between client and server.
  • Direct Server Calls: Import Server Actions into client components without exposing server logic.

Practical Use Cases

  1. Form Submissions: Simplify form handling by directly passing form data to Server Actions.
  2. Data Mutations: Update databases or external APIs without setting up API routes.
  3. Progressive Enhancement: Server Actions can execute even if JavaScript is disabled, enhancing accessibility.

Bonus: How to (Not) Reset a Form After a Server Action

Handling form states after a server action can improve user experience, especially if the submission fails.

Code Sample

To prevent the form from resetting after a failed server action, you can return the form data as part of the action state and use it to repopulate the form:

"use server";

type ActionState = {
  message: string;
  payload?: FormData;
};

export const createPost = async (
  _actionState: ActionState,
  formData: FormData
) => {
  const data = {
    title: formData.get("title"),
    content: formData.get("content"),
  };

  if (!data.title || !data.content) {
    return {
      message: "Please fill in all fields",
      payload: formData,
    };
  }

  // Simulate saving to a database
  return { message: "Post created successfully" };
};

On the client-side:

"use client";
import { useActionState } from "react";
import { createPost } from "../actions/createPost";

const PostForm = () => {
  const [actionState, action] = useActionState(createPost, {
    message: "",
  });

  return (
    <form action={action}>
      <label htmlFor="title">Title:</label>
      <input
        name="title"
        id="title"
        defaultValue={(actionState.payload?.get("title") || "") as string}
      />

      <label htmlFor="content">Content:</label>
      <textarea
        name="content"
        id="content"
        defaultValue={(actionState.payload?.get("content") || "") as string}
      />

      <button type="submit">Submit</button>
      {actionState.message && <p>{actionState.message}</p>}
    </form>
  );
};

With this approach, form data persists on the client after a failed submission, allowing users to correct and resubmit without losing input.

FAQ

1. What makes Server Actions different from traditional API routes?

  • Server Actions reduce the boilerplate associated with API route creation and allow direct integration within React components.

2. Can Server Actions be used for data fetching?

  • While technically possible, Server Actions are mainly intended for data mutations. Data fetching is typically handled by server components or API routes.

3. Do Server Actions work without JavaScript enabled?

  • Yes, Server Actions support progressive enhancement, allowing forms to submit even if JavaScript is disabled in the browser.

4. Is there any downside to using Server Actions?

  • Server Actions can only handle POST requests. They should be used with careful consideration for security and user experience.

Last Words

Server Actions in Next.js offer a powerful way to handle server-side logic seamlessly within React applications. They simplify code, enhance type safety, and provide a better user experience through progressive enhancement. By understanding how to use and handle form submissions effectively, developers can leverage Server Actions to build robust, modern applications.

I build and scale reliable production systems. Open to full-time and freelance work with U.S.-based teams that value ownership and execution.

Got something in mind?

Book a Discovery Call