Introduction to Next.js Server Actions

Next.js Server Actions are a new feature introduced in Next.js 13 that allows you to run server code without having to create an API endpoint. In this article, we'll learn how to use them.

ยท9 min read
Cover Image for Introduction to Next.js Server Actions

The new App Router released in Next.js 13 brings with it a ton of new features that brought the famous framework by Vercel to a whole new level. We have discussed the new App Router in our article Next.js 13: complete guide to Server Components and the App Directory.

What are Next.js Server Actions?

One of the most exciting features is the introduction of Server Actions or Server Functions. Just like the word says, Server Actions are functions that run on the server, but that we can call from the client, just like a normal function. This is a huge step forward for Next.js, as it allows us to run code on the server without having to create an API endpoint. This is a whole new DX for Next.js developers, and it's going to be a game changer.

To be totally honest, this is actually not a Next.js-specific concept: rather, it's a [React.js built-in functionality, which is still in alpha.

What can I do with Next.js Server Actions?

Server Actions are a very powerful feature, and they can be used for a lot of different use cases. Here are a few examples:

  • writing to a database: you can write to a database directly from the client, without having to create an API endpoint - just by defining your logic in a server action.
  • server logic: executing any server-related business logic, such as sending emails, creating files, etc.
  • calling external APIs: you can call external APIs directly from server actions, without having to create an API endpoint

In summary, you can do anything you would normally do on the server, but without having to create an API endpoint.

Pros to using Next.js Server Actions

There are a few pros to using Next.js Server Actions:

  1. No need to create an API endpoint: you can run server code without having to create an API endpoint.
  2. Jumping to the definition: you can jump to the definition of a server action just by clicking on it in your code editor, without the need of searching for it in your codebase.
  3. Type safety: you can use TypeScript to define the arguments and return value of your server actions, and Next.js will automatically validate them for you.
  4. Less code: you can write less code, as you need a lot less boilerplate to run server code - you can just define a function and its parameters - and then call it from the client.

There's a lot to love about Next.js Server Actions, and I'm sure you'll find a lot of use cases for them.

How to define a Next.js Server Action

Server actions can normally be defined anywhere in your components but with a few exceptions. Let's take a look at a few scenarios.

Before you start, ensure you have enabled the experimental server actions in your next.config.js file:

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

Defining a Server Action in a Server Component

If you are defining a server action in a server component, the only thing you need to do is to define a function with the use server keyword at the top.

For example, the below is a valid server action:

async function myActionFunction() { 'use server'; // do something }

Very important: server actions functions should have serializable arguments and a serializable return value based on the React Server Components protocol. This is because the function's response will be serialized and sent to the client.

Defining multiple Next.js Server Actions

An alternative way to define a server action is to use export multiple functions from a file, adding the use server keyword at the top of the file.

'use server' export async function myActionFunction() { // do something } export async function anotherActionFunction() { // do something }

Client Components can only import actions from server actions files: client components cannot define server actions inline from the same file - but you can still import them from a file that defines multiple server actions using the use server keyword.

How to invoke a Next.js Server Action

To invoke a server action, you have numerous options.

Invoking a Server Action from a Form

The simplest way to invoke a server action is to invoke it from a form. To do so, you can use the onSubmit prop of the form element, and call the server action from there.

export function MyFormComponent() { function handleFormAction( formData: FormData ) { 'use server'; const name = formData.get('name'); // do something } return ( <form action={handleFormAction}> <input type={'name'} /> <button type="submit">Save</button> </form> ); }

Displaying the form status while the Server Action is running

If you want to display a loading indicator while the server action is running, we need to use a new experimental experimental_useFormStatus hook.

There are a few things you need to know about this hook:

  1. It needs to be called within the form element
  2. It needs to be called within a client component

Consider the component below:

'use client'; import { experimental_useFormStatus as useFormStatus } from 'react-dom' function CreatePostForm() { return ( <form action={createPostAction}> <div className='flex flex-col space-y-4'> <h2 className='text-lg font-semibold'>Create a new Post</h2> <Label className='flex flex-col space-y-1.5'> <span>Title</span> <Input name='title' placeholder='Ex. The best Next.js libraries' required /> </Label> <Label className='flex flex-col space-y-1.5'> <span>Description</span> <Input /> </Label> <SubmitButton /> </div> </form> ); } function SubmitButton() { const { pending } = useFormStatus(); return ( <button disabled={pending}> {pending ? 'Creating article...' : 'Create Article'} </button> ); }

As you may have noticed, we are calling the useFormStatus hook within the form element, and we are also calling it within a client component.

To make sure the useFormStatus hook works, we create a new SubmitButton component, and we call it from the CreatePostForm component.

Invoking a Server Action from a Button

You can also invoke a server action from a button. To do so, you can use the handleAction prop of the button element, and call the server action from there.

export function Form() { async function handleSubmit() { 'use server'; // ... } return ( <form> <input type="text" name="name" /> <button formAction={handleSubmit}>Submit</button> </form> ); }

Invoking a Server Action imperatively

You can also invoke a server action imperatively, by using the useTransition hook.

First, we define our server action in a separate file that defines multiple server actions:

'use server'; export async function saveData(id) { await addItemToDb(id); revalidatePath('/product/[id]'); }

Then, we can import it in our client component, and invoke it imperatively using the useTransition hook:

'use client'; import { useTransition } from 'react'; import { saveData } from '../actions'; function ClientComponent({ id }) { let [isPending, startTransition] = useTransition(); return ( <button onClick={() => startTransition(() => saveData(id))}> Save </button> ); }

Revalidating data after a Server Action

You can use the revalidatePath function to revalidate the data for a specific path. This is useful if you want to revalidate the data for a specific path after the server action has been executed.

Progressive Enhancement

If you use Server Actions from a form component, these will also work if JavaScript is disabled, as the form will be submitted to the server, unlike actions that are invoked imperatively, which will only work if JavaScript is enabled.

If you can, you're encouraged to use Server Actions from forms, as this will ensure your app works even if JavaScript is disabled.

Server Mutations

Next.js defines Server Mutations as Server Actions that mutate your data and calls redirect, revalidatePath, or revalidateTag.

If you are not doing any of these - you are not doing a mutation, and are not required to use useTransition: in such cases, you can just call the server action directly from your client components.

Server Actions Error Handling

Any non-trivial application needs to handle errors gracefully - and learning how to do it with Server Actions is paramount.

As we've mentioned above, there are multiple ways to invoke a Server Action: from a form, from a button, or imperatively. Let's take a look at how to handle errors in each of these cases.

Unhandled errors from a Server Action

In all cases above - if you don't handle the error from a Server Action - the error will bubble up to the upper error.tsx component.

Handling errors from a form

If you are invoking a Server Action from a form, you can wrap your form using an ErrorBoundary component, and handle the error from there.

Let's create a very basic ErrorBoundary component that will render a fallback UI if an error occurs:

'use client'; import { Component } from 'react'; class ErrorBoundary<Props extends { fallback: React.ReactNode; children: React.ReactNode }> extends Component<Props> { state = { hasError: false }; constructor(props: Props) { super(props); } static getDerivedStateFromError() { return { hasError: true }; } render() { if (this.state.hasError) { // You can render any custom fallback UI return this.props.fallback; } return this.props.children; } } export default ErrorBoundary;

Let's assume we have a Form using a server action that throws an error:

async function serverActionWithError() { 'use server'; throw new Error(`This is error is in the Server Action`); } function FormWithServerAction() { return ( <form action={serverActionWithError}> <button>Submit Form</button> </form> ); } export default FormWithServerAction;

We can wrap our form using the ErrorBoundary component, and handle the error from there:

import ErrorBoundary from './ErrorBoundary'; import FormWithServerAction from './Form'; function FormWithErrorBoundary() { return ( <ErrorBoundary fallback={<p>Something went wrong</p>}> <FormWithServerAction /> </ErrorBoundary> ); }

If you click on the button, you'll see the error message rendered.

Handling errors from an imperatively invoked Server Action

If you are invoking a Server Action imperatively, we can wrap the useTransition hook in a try/catch block, and handle the error from there.

Let's assume we have a Server Action that throws an error:

'use server'; export async function serverActionWithError() { throw new Error(`This is error is in the Server Action`); }

We can invoke it imperatively from a client component, and handle the error from there:

'use client'; import { useTransition } from 'react'; import { serverActionWithError } from './actions'; function ImperativeServerAction() { const [pending, startTransition] = useTransition(); return ( <button disabled={pending} onClick={() => { startTransition(async () => { try { await serverActionWithError() } catch (e) { alert('error'); } }); }} > Click Button </button> ); } export default ImperativeServerAction;

If you click on the button, you'll see the alert popup with the error message.

Conclusion

In this article, we've seen how to use Next.js Server Actions, and how to invoke them from client components.

You can use these today, but they are still experimental, so you should use them with caution - since the API might change in the future.

The new Next.js Supabase SaaS Starter Kit at Makerkit already makes use of Server Components and App Actions - so you can check it out if you want to see a real-world example of how to use them.



Read more about Tutorials

Cover Image for Building an AI Writer SaaS with Next.js and Supabase

Building an AI Writer SaaS with Next.js and Supabase

ยท57 min read
Learn how to build an AI Writer SaaS with Next.js and Supabase - from writing SEO optimized blog posts to managing subscriptions and billing.
Cover Image for Announcing the Data Loader SDK for Supabase

Announcing the Data Loader SDK for Supabase

ยท8 min read
We're excited to announce the Data Loader SDK for Supabase. It's a declarative, type-safe set of utilities to load data into your Supabase database that you can use in your Next.js or Remix apps.
Cover Image for Adding AI capabilities to your Next.js SaaS with Supabase and HuggingFace

Adding AI capabilities to your Next.js SaaS with Supabase and HuggingFace

ยท20 min read
In this tutorial, we will learn how to use add AI capabilities to your SaaS using Supabase Vector, HuggingFace models and Next.js Server Components.
Cover Image for Building an AI-powered Blog with Next.js and WordPress

Building an AI-powered Blog with Next.js and WordPress

ยท17 min read
Learn how to build a blog with Next.js 13 and WordPress and how to leverage AI to generate content.
Cover Image for Using Supabase Vault to store secrets

Using Supabase Vault to store secrets

ยท6 min read
Supabase Vault is a Postgres extension that allows you to store secrets in your database. This is a great way to store API keys, tokens, and other sensitive information. In this tutorial, we'll use Supabase Vault to store our API keys
Cover Image for Next.js 13: complete guide to Server Components and the App Directory

Next.js 13: complete guide to Server Components and the App Directory

ยท19 min read
Unlock the full potential of Next.js 13 with our most complete and definitive tutorial on using server components and the app directory.