Skip to content

Introduction to Server Actions

Server Actions are asynchronous functions that run directly on the server. They allow you to define server-side logic directly within your React components (or in separate files) that can be invoked from the client. This eliminates the need for manually creating API routes for every data mutation, making your code simpler, more organized, and often more performant.

The Modern Way to Handle Data Mutations in Next.js

Before Server Actions, a typical data mutation workflow looked like this:

  1. User interacts with a form on a Client Component.
  2. Client Component makes a fetch (POST, PUT, DELETE) request to an API Route.
  3. API Route (running on the server) interacts with the database.
  4. API Route sends a response back to the client.
  5. Client Component handles the response and updates its UI (e.g., revalidates data).

With Server Actions, this process is streamlined:

  1. User interacts with a form on a Client Component (or even directly from a Server Component).
  2. The form's action prop points directly to a Server Action.
  3. The Server Action (running on the server) interacts with the database.
  4. Next.js automatically handles form data serialization, invocation, response, and crucially, cache revalidation on the client.

This tight integration drastically reduces boilerplate code and improves the developer experience.

Defining Server Actions

To define a Server Action, you simply need to add the 'use server' directive at the top of the file where the action is defined. This directive tells the Next.js build system that all exported functions in that file are Server Actions.

Server Actions can be defined in two main places:

  1. Directly in a Server Component: For actions tightly coupled to a single component's functionality.
  2. In a separate file: For reusable actions that might be called from multiple components or layouts. This is generally the preferred approach for organization and reusability. We'll use this method for our Task App.

'use server' Directive

Place 'use server' at the very top of a .ts or .tsx file (before any imports) to designate it as a Server Actions file. All exported async functions within this file will be Server Actions.

// Example: actions/tasks.ts
'use server';

// All functions exported from this file will be Server Actions
export async function createTask(formData: FormData) {
  // ... server-side logic
}

Passing Data to Server Actions

Server Actions automatically receive form data when used with a <form action={serverAction}> element. The first argument to a Server Action will typically be a FormData object, which contains all the name and value pairs from the form inputs.

You can also pass additional arguments to a Server Action if needed, for instance, when using it with a button onClick or when it's called from a Client Component using startTransition.

Revalidation & Cache Invalidation

One of the most powerful features integrated with Server Actions is automatic cache revalidation. After a Server Action successfully completes a data mutation (e.g., adding a task), Next.js can automatically:

  1. Revalidate cached data: This ensures that fetch requests (that might have been cached) are re-run on the next render, fetching the fresh data.
  2. Update the UI: The updated data will be re-rendered on the page, reflecting the changes immediately to the user without a full page refresh.

This is typically achieved using Next.js's revalidatePath or revalidateTag functions. For simplicity in this module, we will primarily use revalidatePath to revalidate the path where our tasks are displayed.

Enhanced User Experience with useFormStatus and useOptimistic

While not strictly required for Server Actions to work, these two React hooks (introduced in React 18) greatly enhance the user experience when performing data mutations:

  • useFormStatus (Client Component Hook):
    • Allows a Client Component to read the status of the form it's contained within.
    • Useful for showing pending states (e.g., disabling a submit button, showing a loading spinner) while a Server Action is in progress.
    • It's imported from react-dom.
  • useOptimistic (Client Component Hook):
    • Allows you to update the UI optimistically before the Server Action response is received.
    • This means the UI immediately reflects the expected change (e.g., a new task appears in the list) even before the server confirms the operation. If the server action fails, the UI can revert.
    • It's imported from react.

We'll implement useFormStatus to show a loading state for our task form. useOptimistic is a bit more advanced but highly recommended for production apps, and you can explore it after mastering the basics.