Server Actions in Next.js
Next.js is known for its power and flexibility when it comes to building fast, modern web applications. With Next.js 14, a new feature got introduced that changed the way we thought about server-side logic: Server Actions.
What are server actions?
Server actions allow you to handle asynchronous operations on the server side. The server action function can perform operations such as data fetching, interacting with databases, etc.
Defining Server Actions
A server action is defined using the use server
directive. We can use this directive at an inline function level or at module level.
Using Server Actions in Server Components
By default, all the components in nextjs are server components. We can call the Server Actions in server components directly at inline function level.
//This component will fetch the list of the products
export default async function ProductListPage() {
const getListOfProducts = await fetchProductsList();
return <>
<ul>
{getListOfProducts && getListOfProducts.length > 0 ? : getListOfProducts.map(item => <li>{item?.title}</li>) <p>No products found!</p>}
</ul>
</>
}
//utility function to fetch the products
export async function fetchProductList() {
const response = await fetch('https://dummyjson.com/products');
const data = await response.json();
return data?.products;
}
Using Server Actions in Client Components
In a client component, we cannot use the ‘use server’ directive at an inline level, i.e., we cannot create server actions inside them. We can create a separate file and use the 'use server’
directive at the top level. Whatever functions we create inside this file are considered as Server Actions. Then we can import these server actions in a client component.
Let’s understand this with an example of a form, we will implement a form that allows users to add new products to the system.
'use client';
import { useState } from 'react';
import { addProductAction } from '../actions/index';
export default function ProductForm() {
const [formData, setFormData] = useState({ title: '', description: '' });
// Handle input changes
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
// Handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
const addedProduct = await addProductAction(formData); //addProductAction is a server action
console.log(addedProduct);
};
return (
<form action={handleSubmit}>
<div>
<input
placeholder="Enter product title"
name="title"
value={formData.title}
onChange={handleChange}
/>
</div>
<div>
<textarea
placeholder="Enter product description"
name="description"
value={formData.description}
onChange={handleChange}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}
Now, let’s write the server action to understand how to add a new product.
// /src/actions/index.js
'use server';
import { connectToDB } from './db';
import Product from './models/Product';
export async function addProductAction(formData) {
try {
await connectToDB(); // Ensure you connect to the database
// Assuming Product is a model that you can use to create a new product
const newProduct = await Product.create(formData);
return newProduct;
} catch (error) {
console.error("Error adding product:", error);
throw new Error("Could not add product"); // Handle the error properly
}
}
When to Use Server Actions
While Server Actions provide many advantages, they are not always the right tool for every situation. Some scenarios where we should consider using Server Actions -
For Data Mutations or Form Submissions : They simplify the process by handling the form submission and data processing directly within the component, without requiring a separate API layer.
Declarative Server-Side Logic: They allow you to write server-side logic in a declarative, readable way within your React components, rather than having to manage separate API calls and handlers.
When Not to Use Server Actions
Some cases where using Server Actions might not be the best option -
Highly Interactive UIs : For highly dynamic UIs that rely a lot on client-side interactivity or real-time updates, you may want to handle data fetching via traditional methods (e.g., GraphQL or REST APIs) that allow more control over the client-server communication.
Heavy Computational Tasks: If you need to perform long-running computations or complex tasks that are not related to user interactions (e.g., batch processing, background jobs), it's better to offload those to a separate backend service or a job queue system.
Conclusion
Server Actions in Next.js offer significant advantages in terms of simplifying architecture, improving security, and enhancing performance. They are best suited for tasks like form submissions, data mutations, and operations that need to run on the server but are triggered from the client. Read more about them -
Subscribe to my newsletter
Read articles from Keshav Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by