Exploring Server Actions in Next.js: A Game-Changing Feature for Web Developers
- Md. Saad
- October 21, 2024
Next.js, one of the most popular React frameworks, continues pushing web development boundaries. It has introduced a powerful feature called Server Actions. This feature allows developers to execute functions on the server side, enhancing security and performance by offloading tasks like data fetching and mutations from the client to the server. This blog will explore Server Actions, their benefits, and how to implement them in your Next.js applications.
What are Server Actions?
Server Actions are asynchronous functions that run on the server. They can be used in both Server and Client Components to handle tasks such as form submissions and data mutations. By executing these actions on the server, you can avoid exposing sensitive operations to the client, reducing security risks.
Implementing Server Action
Server Actions are defined using the React "use server" directive. You can add this directive at the top of an asynchronous function to designate that specific function as a Server Action. Alternatively, you can place the "use server" directive at the beginning of a file, which will automatically mark all exported functions in that file as Server Actions. This allows for flexible handling of server-side logic directly from your component.
First, create an async function using the server directive to use the server actions inside the server component. You must place the directive on top of the function body. Then, you can call the server function. Here’s a basic example:
// app/page.jsx
export default function Page() {
// Server Action
async function create() {
'use server'
// Perform data mutation
}
return <button onClick={create}>Create</button>
}
You can also define Server Actions in a separate file and import them into your components:
// app/actions.js
'use server'
export async function create() {
// Perform data mutation
}
// app/ui/button.jsx
import { create } from '@/app/actions'
export function Button() {
return <button onClick={create}>Create</button>
}
Implementing Server Action in client components
We may have client and server components in a Next Js project. We may want to use server actions inside the client components. Next Js allows us to do that also.
To invoke a Server Action in a Client Component, create a new file and include the "use server" directive at the start. All exported functions in the file will be tagged as Server Actions, which can be utilised in both client and server components:
// app/actions.js
'use server'
export async function create() {
// Perform data mutation
}
// app/ui/button.jsx
'use client'
import { create } from '@/app/actions'
export function Button() {
return <button onClick={create}>Create</button>
}
Passing actions as props
In Next.js, you can also pass a Server Action to a Client Component as a prop like this:
<ClientComponent updateItemAction={updateItem} />
// app/client-component.js
'use client'
export default function ClientComponent({ updateItemAction }) {
return <form action={updateItemAction}>{/* ... */}</form>
}
Typically, the Next.js TypeScript plugin would issue a warning when passing a function (like updateItemAction) from the server to the client, as functions generally can't be serialized across client-server boundaries. However, Next.js uses a special heuristic: when a prop is named action or ends with Action, the plugin assumes it may be a Server Action.
This is just a naming convention and doesn't imply the TypeScript plugin can definitively know if it's dealing with a Server Action or a regular function. Still, at runtime, type-checking will ensure that you don't mistakenly pass a non-serializable function into a Client Component.
Using Server Actions in Forms
Server Actions can be invoked using the action attribute in a <form> element.
When a form is submitted, the Server Action automatically receives the FormData object. This means you don't need to use useState to manage form fields. Instead, you can extract the form data directly using the native FormData methods.
// app/page.jsx
export default function Page() {
async function handleSubmit(formData) {
'use server'
// Handle form submission
}
return (
<form action={handleSubmit}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
)
}
You can pass additional arguments to a Server Action using JavaScript's bind method. Here’s an example:
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }) {
// Bind the userId as an additional argument to the updateUser function
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
In this example, the updateUserWithId function binds the userId argument to the updateUser function. When the form is submitted, the updateUser function will receive the userId along with the form data.
Using Server Actions in Forms
Server Actions can be invoked using the action attribute in a <form> element.
When a form is submitted, the Server Action automatically receives the FormData object. This means you don't need to use useState to manage form fields. Instead, you can extract the form data directly using the native FormData methods.
// app/page.jsx
export default function Page() {
async function handleSubmit(formData) {
'use server'
// Handle form submission
}
return (
<form action={handleSubmit}>
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
)
}
You can pass additional arguments to a Server Action using JavaScript's bind method. Here’s an example:
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }) {
// Bind the userId as an additional argument to the updateUser function
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
In this example, the updateUserWithId function binds the userId argument to the updateUser function. When the form is submitted, the updateUser function will receive the userId along with the form data.
On the server side:
'use server'
export async function updateUser(userId, formData) {
// handle updating the user with the userId and formData
}
This allows you to pass the userId as an additional argument to the server action alongside the form data.
Using Server Action we can also add other awesome features in our next js form such as Programmatic form submission, server-side form validation, revalidating data, error handling and so on. You can read further from the Next.js official documentation to learn more.
Conclusion
Server Actions in Next.js provide a robust way to handle server-side operations securely and efficiently. By leveraging this feature, you can enhance the performance and security of your applications while keeping your codebase clean and maintainable. Whether you’re handling form submissions or performing data mutations, Server Actions offer a seamless solution for modern web development
Keep exploring and enhancing your Next.js projects! Please do not hesitate to contact StaticMania with any questions or feedback. Happy coding!