Courses
Next.js
Server Actions

Server Actions

Server Actions are an alpha feature in Next.js, built on top of React Actions. They enable server-side data mutations, reduced client-side JavaScript, and progressively enhanced forms. They can be defined inside Server Components and/or called from Client Components:

With Server Components:

app/add-to-cart.js
import { cookies } from "next/headers";
 
// Server action defined inside a Server Component
export default function AddToCart({ productId }) {
  async function addItem(data) {
    "use server";
 
    const cartId = cookies().get("cartId")?.value;
    await saveToDb({ cartId, data });
  }
 
  return (
    <form action={addItem}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}

With Client Components:

app/actions.js
"use server";
 
export async function addItem(data) {
  const cartId = cookies().get("cartId")?.value;
  await saveToDb({ cartId, data });
}
app/add-to-cart.js
"use client";
 
import { addItem } from "./actions.js";
 
// Server Action being called inside a Client Component
export default function AddToCart({ productId }) {
  return (
    <form action={addItem}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}

Rather than needing to manually create an API endpoint, Server Actions automatically create an endpoint for Next.js to use behind the scenes. When calling a Server Action, Next.js sends a POST request to the page you're on with metadata for which action to run.

Server Actions inherit the same runtime (either Node.js or Edge) defined by the page or layout they're used on. Currently, if a route uses a Server Action, it is required to render dynamically.

  • Using Server Actions will opt into running the React experimental channel.

  • React Actions, useOptimistic, and useFormStatus are not Next.js or React Server Components specific features.

  • Next.js integrates React Actions into the Next.js App Router, bundler, and caching system, including adding data mutation APIs like revalidateTag and revalidatePath.

Convention

You can enable Server Actions in your Next.js project by enabling the experimental serverActions flag.

next.config.js
module.exports = {
  experimental: {
    serverActions: true,
  },
};

Creation

Server Actions can be defined in two places:

  • Inside the component that uses it (Server Components only)

  • In a separate file (Client and Server Components), for reusability. You can define multiple Server Actions in a single file.

With Server Components

Create a Server Action by defining an asynchronous function with the "use server" directive at the top of the function body. This function should have serializable arguments and a serializable return value based on the React Server Components protocol.

export default function ServerComponent() {
  async function myAction() {
    "use server";
    // ...
  }
}

With Client Components

If you're using a Server Action inside a Client Component, create your action in a separate file with the "use server" directive at the top of the file. Then, import the Server Action into your Client Component:

app/actions.js
"use server";
 
export async function myAction() {
  // ...
}
app/client-component.js
"use client";
 
import { myAction } from "./actions";
 
export default function ClientComponent() {
  return (
    <form action={myAction}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}

When using a top-level "use server" directive, all exports below will be considered Server Actions. You can have multiple Server Actions in a single file.

Invocation

You can invoke Server Actions using the following methods:

  • Using action: React's action prop allows invoking a Server Action on a <form> element.

  • Using formAction: React's formAction prop allows handling <button>, <input type="submit">, and <input type="image"> elements in a <form>.

  • Custom Invocation with startTransition: Invoke Server Actions without using action or formAction by using startTransition. This method disables Progressive Enhancement.

action

You can use React's action prop to invoke a Server Action on a form element. Server Actions passed with the action prop act as asynchronous side effects in response to user interaction.

app/add-to-cart.js
import { cookies } from "next/headers";
 
export default function AddToCart({ productId }) {
  async function addItem(data) {
    "use server";
 
    const cartId = cookies().get("cartId")?.value;
    await saveToDb({ cartId, data });
  }
 
  return (
    <form action={addItem}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}
An action is similar to the HTML primitive action

formAction

You can use formAction prop to handle Form Actions on elements such as button, input type="submit", and input type="image". The formAction prop takes precedence over the form's action.

app/form.js
export default function Form() {
  async function handleSubmit() {
    "use server";
    // ...
  }
 
  async function submitImage() {
    "use server";
    // ...
  }
 
  return (
    <form action={handleSubmit}>
      <input type="text" name="name" />
      <input type="image" formAction={submitImage} />
      <button type="submit">Submit</button>
    </form>
  );
}

A formAction is the HTML primitive formaction. React now allows you to pass functions to this attribute.

Custom invocation using startTransition

You can also invoke Server Actions without using action or formAction. You can achieve this by using startTransition provided by the useTransition hook, which can be useful if you want to use Server Actions outside of forms, buttons, or inputs.

Using startTransition disables the out-of-the-box Progressive Enhancement.

app/components/example-client-component.js
"use client";
 
import { useTransition } from "react";
import { addItem } from "../actions";
 
function ExampleClientComponent({ id }) {
  let [isPending, startTransition] = useTransition();
 
  return (
    <button onClick={() => startTransition(() => addItem(id))}>
      Add To Cart
    </button>
  );
}
app/actions.js
"use server";
 
export async function addItem(id) {
  await addItemToDb(id);
  // Marks all product pages for revalidating
  revalidatePath("/product/[id]");
}

Custom invocation without startTransition

If you aren't doing Server Mutations, you can directly pass the function as a prop like any other function.

app/posts/[id]/page.tsx
import kv from "@vercel/kv";
import LikeButton from "./like-button";
 
export default function Page({ params }: { params: { id: string } }) {
  async function increment() {
    "use server";
    await kv.incr(`post:id:${params.id}`);
  }
 
  return <LikeButton increment={increment} />;
}
app/post/[id]/like-button.tsx
"use client";
 
export default function LikeButton({
  increment,
}: {
  increment: () => Promise<void>;
}) {
  return (
    <button
      onClick={async () => {
        await increment();
      }}
    >
      Like
    </button>
  );
}

Enhancements

Experimental useOptimistic

The experimental useOptimistic hook provides a way to implement optimistic updates in your application. Optimistic updates are a technique that enhances user experience by making the app appear more responsive.

When a Server Action is invoked, the UI is updated immediately to reflect the expected outcome, instead of waiting for the Server Action's response.

app/thread.js
"use client";
 
import { experimental_useOptimistic as useOptimistic, useRef } from "react";
import { send } from "./actions.js";
 
export function Thread({ messages }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) => [...state, { message: newMessage, sending: true }]
  );
  const formRef = useRef();
 
  return (
    <div>
      {optimisticMessages.map((m) => (
        <div>
          {m.message}
          {m.sending ? "Sending..." : ""}
        </div>
      ))}
      <form
        action={async (formData) => {
          const message = formData.get("message");
          formRef.current.reset();
          addOptimisticMessage(message);
          await send(message);
        }}
        ref={formRef}
      >
        <input type="text" name="message" />
      </form>
    </div>
  );
}

Experimental useFormStatus

The experimental useFormStatus hook can be used within Form Actions, and provides the pending property.

app/form.js
"use client";
 
import { experimental_useFormStatus as useFormStatus } from "react-dom";
 
function Submit() {
  const { pending } = useFormStatus();
 
  return (
    <input
      type="submit"
      className={pending ? "button-pending" : "button-normal"}
      disabled={pending}
    >
      Submit
    </input>
  );
}

Progressive Enhancement

Progressive Enhancement allows a <form> to function properly without JavaScript, or with JavaScript disabled. This allows users to interact with the form and submit data even if the JavaScript for the form hasn't been loaded yet or if it fails to load.

Both Server Form Actions and Client Form Actions support Progressive Enhancement, using one of two strategies:

  • If a Server Action is passed directly to a <form>, the form is interactive even if JavaScript is disabled.

  • If a Client Action is passed to a <form>, the form is still interactive, but the action will be placed in a queue until the form has hydrated. The <form> is prioritized with Selective Hydration, so it happens quickly.

app/components/example-client-component.js
"use client";
 
import { useState } from "react";
import { handleSubmit } from "./actions.js";
 
export default function ExampleClientComponent({ myAction }) {
  const [input, setInput] = useState();
 
  return (
    <form action={handleSubmit} onChange={(e) => setInput(e.target.value)}>
      {/* ... */}
    </form>
  );
}

In both cases, the form is interactive before hydration occurs. Although Server Actions have the additional benefit of not relying on client JavaScript, you can still compose additional behavior with Client Actions where desired without sacrificing interactivity.

Size Limitation

By default, the maximum size of the request body sent to a Server Action is 1MB. This prevents large amounts of data being sent to the server, which consumes a lot of server resource to parse.

However, you can configure this limit using the experimental serverActionsBodySizeLimit option. It can take the number of bytes or any string format supported by bytes, for example 1000, '500kb' or '3mb'.

next.config.js
module.exports = {
  experimental: {
    serverActions: true,
    serverActionsBodySizeLimit: "2mb",
  },
};