Server Actions are a new feature in Next.js. The first time I heard about them, they didn’t seem very intuitive to me. Now that I’m a bit more used to them, let me contribute to making them easier to understand.
I tried to create the smallest possible example to help developers understand what they are and what they can be used for. The result is this example, which consists of two files:
// client-component.tsx
'use client'
import { useEffect, useState } from 'react'
import { getInformationFromTheServer } from './actions'
export function ClientComponent() {
const [info, setInfo] = useState('')
useEffect(() => {
getInformationFromTheServer('World').then((res) => setInfo(res))
}, [])
return (
<p>
This comes from the server: <code>{info}</code>
</p>
)
}
// actions.ts
'use server'
export async function getInformationFromTheServer(name: string) {
return `Hello ${name}!`
}
The use case is as follows: In a client component, we’d like to get some information from the server. Without server actions, we’d have to:
- Either get this information as a prop passed by the parent component (assuming it is a server component),
- Or create an API route and call it from the client component using
fetch
. Server actions are similar to the second option, but with a shortcut: the API route will be created by Next.js for us.
In our example, the server action is the function getInformationFromTheServer
, declared in actions.ts (server actions cannot be defined in the same file as a client component). Like any server action:
- It is declared as
async
(it has to, even if it doesn’t perform any async operation), - It uses the directive
'use server'
(either at the top of the file or the top of the function).
💡 Tiny interruption in this post: if you’d like to learn how to use React and Next.js to create powerful applications, check out my online course or my live workshop 😉.
From the client component, we can call the function like any other side effect (async) function: in a useEffect
or an event callback.
It looks like we’re just calling a function, so you might wonder why everybody is making such a fuss about server actions… Look closer: from a client component, we call a function stored on the server.
This means that it isn’t just a function call: there is actually an HTTP request made to the API to call the function. You can see it in the Network tab of your DevTools:
The endpoint is at the same path as your current page but:
- Uses the
POST
method, - Needs some headers, such as the
Next-Action
one, containing some kind of ID for the server action, - Gets the parameters to pass to the function from the request body (as JSON).
When you copy the server action ID from the headers, you can even call the API endpoint using cURL:
$ curl 'http://localhost:3001/getting-info-from-server' \
-H 'Content-Type: text/plain;charset=UTF-8' \
-H 'Next-Action: da9f55acc16563503a57a4fdfe567f8770898818' \
-X POST --data-raw '["World"]'
0:["$@1",["development",null]]
1:"Hello World!"
Note: In Chrome DevTools, at the time I’m writing this post, it looks like it isn’t possible to see the result of the request, as it is sent using HTTP streaming. It works well with cURL 😉.
As you can see, there is nothing magical about server actions:
- Next.js takes our
getInformationFromTheServer
function, - It makes it callable through a specific API endpoint,
- From the client component, when we think we are calling the function we defined, we actually call a function generated by Next.js which deals with the necessary API call for us.
If you are familiar with distributed computing patterns, you can notice that server actions offer a remote procedure call (RPC) pattern for Next.js applications.
The cURL example shows something very important about server actions: anyone can call existing server actions with any parameter they want! This means that the parameters received by the server actions must be validated, just like any parameter sent to a classic API route.
This is especially counter-intuitive when using TypeScript. Even if you declare that your function must get a string
parameter, this function will be made available to the rest of the world, even to big bad hackers who want to send a number instead.
With that in mind, server actions still offer a nice pattern to get information from the server, especially in a client component. From there, we can imagine cool and more complex use cases:
- Mutating data on the server (e.g. in a database),
- Sending an email,
- Processing data entered in a form, etc.
And, like in any other API endpoint, we can, of course, check user authentication to ensure we don’t allow anything for anyone.
I hope that server actions are now a bit clearer to you!
When you understand how they work, you can find many resources about how to use them in real-life applications:
Top comments (0)