Back to blog

Server Actions in Next.js

Next.js Server actions are like specialized tools within React that let you perform specific tasks on the server side. Think of them as handy extensions to your application's functionality, rather than full-fledged UI components. Server Actions are asynchronous functions that run on the server, offering a new way to handle data fetching and mutations in your application. They address challenges faced with traditional approaches like client-side fetching and API routes. They can extend your web application's capabilities, enabling more efficient communication with your backend from various user interactions.

What do Server Actions do:

Traditional Data Fetching vs. Server Actions

Traditional Data Fetching

Previously, Next.js relied on various methods for fetching data, including:

While these methods offer flexibility, they can lead to complex code and potential performance issues, especially with frequent data updates and more round-trip network requests.

Server Actions

Next.js 13 introduced server actions, a new approach that simplifies data fetching and improves application logic.

Server actions offer several benefits:

Traditional Data Fetching with 'getServerSideProps'
1
2export async function getServerSideProps() {
3 const response = await fetch('https://api.example.com/data');
4 const data = await response.json();
5
6 return {
7 props: {
8 data,
9 },
10 };
11}
12
13function MyComponent({ data }) {
14 return (
15 <div>
16 <h1>Data fetched from server:</h1>
17 <p>{JSON.stringify(data)}</p>
18 </div>
19 );
20}
Data fetching using Server Actions
1
2async function fetchData() {
3 "use server";
4
5 const response = await fetch('https://api.example.com/data');
6 const data = await response.json();
7
8 return data;
9}
10
11function MyComponent() {
12 const data = use(fetchData);
13
14 return (
15 <div>
16 <h1>Data fetched from server action:</h1>
17 <p>{JSON.stringify(data)}</p>
18 </div>
19 );
20}
21

As you can see, the server action approach is more concise and separates data fetching logic from the component.

Server actions represent a significant step forward in Next.js data fetching. They offer a simpler, more performant, and secure way to manage data in your applications.

Example of Invoking Server Actions From <form> Elements:

Form submission with server-side validation
1
2// Server action
3export async function submitForm(data: FormData) {
4 "use server";
5 // Validate data on the server
6 // Perform any necessary actions (e.g., save data)
7 // ...
8
9 return { success: true, message: "Form submitted successfully!" };
10}
11
12// Form component
13function MyForm() {
14 return (
15 <form action={submitForm}>
16 {/* Form fields */}
17 <button type="submit">Submit</button>
18 </form>
19 );
20}
21

Examples of Invoking Server Actions Outside of <form> Elements:

1. Event Handlers
1
2import { useServer } from 'next/server';
3
4export function incrementCount(count: Number) {
5 useServer(); // Mark this function as a server action - alternative to using "use server"
6
7 let countValue = count;
8 // Increment counter
9 countValue++;
10 // Update counter value in data source
11 // Return the updated counter value
12 return countValue;
13}
14
15import { useState } from 'react';
16import { incrementCount } from '../app/actions';
17
18export default function HomePage() {
19 const [count, setCount] = useState(0);
20
21 const handleButtonClick = async () => {
22 const updatedCounter = await incrementCounter(count); // Call the server action
23 setCount(updatedCounter);
24 };
25
26 return (
27 <div>
28 <button onClick={handleButtonClick}>Increment Counter</button>
29 <p>Counter: {count}</p>
30 </div>
31 );
32}
33

Explanation: Clicking the button triggers the handleClick function, which sends a POST request to the /api/submit-feedback server action. This action likely handles feedback submission and performs server-side operations. The response can be used to update the UI or handle errors.

2. useEffect
1
2export function fetchData() {
3 "use server";
4 // Return arbitrary data
5 return fetch('/api/fetch-data');
6}
7
8useEffect(() => {
9 const fetchData = async () => {
10 const data = await fetchData();
11 // Update state or UI with the fetched data
12 };
13
14 fetchData();
15}, []); // Run only once on component mount
16
17return (
18 <div>
19 {/* Display fetched data here */}
20 </div>
21);

Explanation: The useEffect hook triggers the fetchData function once, fetching data from the /api/fetch-data server action. This action retrieves data, which is then used to update the component's state or render directly in the UI.

3. Third-Party Libraries
1
2import axios from 'axios';
3import { useServer } from 'next/server';
4
5export async function sendFeedback(message) {
6 useServer(); // Mark this function as a server action
7
8 const { data } = await axios.post(
9 'https://your-feedback-api.com/api/feedback',
10 { message },
11 {
12 headers: {
13 // Add any necessary authorization headers here
14 },
15 }
16 );
17
18 // Process the response from the API (e.g., success message, error handling)
19 return data;
20}
21
22// inside client-side react component component
23"use client";
24import { useState } from 'react';
25import { sendFeedback } from '../app/actions';
26
27export default function ContactPage() {
28 const [feedback, setFeedback] = useState('');
29 const [message, setMessage] = useState('');
30
31 const handleFeedbackChange = (event) => {
32 setFeedback(event.target.value);
33 };
34
35 const handleSubmit = async (event) => {
36 event.preventDefault();
37
38 try {
39 const response = await sendFeedback(feedback);
40 setMessage('Feedback sent successfully!');
41 } catch (error) {
42 // Handle error gracefully
43 setMessage('An error occurred. Please try again later.');
44 }
45 };
46
47 return (
48 <div>
49 <h2>Contact Us</h2>
50 <form onSubmit={handleSubmit}>
51 <textarea
52 value={feedback}
53 onChange={handleFeedbackChange}
54 placeholder="Enter your feedback"
55 />
56 <button type="submit">Send Feedback</button>
57 </form>
58 {message && <p>{message}</p>}
59 </div>
60 );
61}
Explanation:
  • The sendFeedback function uses axios to send a POST request to a feedback API (replace with your actual API endpoint).
  • It marks itself as a server action with useServer.
  • The ContactPage component takes user feedback in a textarea and calls sendFeedback on form submission.
  • The response from the API is handled and displayed as a message after processing.
4. Other Form Elements: (button, image, etc)
1
2// server-action.js (file in `app/server-actions`)
3export async function saveDraft(req, res) {
4 const { content } = req.body;
5
6 // Validate and sanitize content if needed
7
8 // Save draft to your database (e.g., using a database library)
9 const draftId = await savePostDraft(content);
10
11 // Send response with draft ID
12 res.json({ draftId });
13}
14
15export async function submitPost(req, res) {
16 const { content } = req.body;
17
18 // Validate and sanitize content if needed
19
20 // Save final version to your database (e.g., using a database library)
21 const postId = await publishPost(content);
22
23 // Send response with post ID
24 res.json({ postId });
25}
26
27// component.jsx (your form component)
28import { useState } from 'react';
29import { useServerAction } from 'next/server';
30
31function CreatePost() {
32 const [content, setContent] = useState('');
33
34 const saveDraftAction = useServerAction(saveDraft);
35 const submitPostAction = useServerAction(submitPost);
36
37 const handleDraftSave = async () => {
38 const { draftId } = await saveDraftAction.mutate(content);
39 // Show success message or update UI with draft ID
40 };
41
42 const handleSubmit = async (event) => {
43 event.preventDefault();
44 const { postId } = await submitPostAction.mutate(content);
45 // Redirect to post page or show success message
46 };
47
48 return (
49 <form onSubmit={handleSubmit}>
50 <textarea
51 value={content}
52 onChange={(e) => setContent(e.target.value)}
53 onBlur={handleDraftSave}
54 />
55 <button type="submit">Publish Post</button>
56 </form>
57 );
58}
59
60export default CreatePost;
Explanation: 1. Server Actions:
  • We define two Server Actions: saveDraft and submitPost.
  • saveDraft takes the content from the request body and saves it as a draft in your database.
  • submitPost does the same but saves the content as a final version.
2. Client Component:
  • We use useState to manage the content in a text area.
  • We use useServerAction to hook up to the Server Actions.
  • handleDraftSave is called on text area blur and triggers thesaveDraft action with the current content.
  • handleSubmit is called on form submit and triggers thesubmitPost action.
  • The response from the Server Actions is used to update the UI (e.g., show success message, redirect to post page).

Optimistic UI with useOptimistic and Server Actions

**What is useOptimistic?**

The useOptimistic hook in React allows you to update the UI immediately based on the expected outcome of an asynchronous operation, even before the operation completes. This creates a more responsive and engaging user experience.

Using useOptimistic with Server Actions

1. Import the hook:
1
2import {experimental_useOptimistic as useOptimistic} from 'react';
2. Define your optimistic updater function
1
2const updateOptimistically = (currentValue, newData) => {
3 // Update the state based on newData (e.g., add a new item in a list)
4 return updatedState;
5};

This function takes the initial state and returns the updated state based on the expected outcome of the server action.

3. Wrap your server action call with `useOptimistic
1
2const {optimisticState, optimisticUpdater, ...serverActionProps} = useOptimistic(
3 updateOptimistically,
4 serverActionProps
5);

This destructures the hook to access the optimistic state, updater function, and other properties.

4. Handle UI based on optimistic state
1
2return (
3 <div>
4 {optimisticState ? (
5 // Render the optimistic UI
6 ) : (
7 // Render the initial UI
8 )}
9 </div>
10);

Use OptimisticState in your component's render function to display the updated UI.

  • Handle server action response:
    • Success:** Update the actual state to match the optimistic state.
    • Failure:** Revert the UI to its original state using `OptimisticUpdater` with the initial value.
  • Adding a new item to a list
    1
    2const addToList = async ( newItem ) => {
    3 // Perform server-side operation to add the item
    4 return response;
    5};
    6
    7const {optimisticState, optimisticUpdater, ...serverActionProps} = useOptimistic(
    8 ( currentList, newItem ) => [...currentList, newItem ], serverActionProps
    9);
    10
    11return (
    12 <div>
    13 <button onClick={() => serverActionProps.mutate(newItem)}>Add Item</button>
    14 {optimisticState ? (
    15 <ul>
    16 {/* Render the list with the optimistic item included */}
    17 </ul>
    18 ) : (
    19 // Render the original list
    20 )}
    21 </div>
    22);
    Updating a user profile
    1
    2const updateProfile = async ( newProfileData ) => {
    3 // Perform server-side operation to update the profile
    4 return response;
    5};
    6
    7const {optimisticState, optimisticUpdater, ...serverActionProps} = useOptimistic(
    8 ( currentProfile, newProfileData ) => ({...currentProfile, ...newProfileData}), serverActionProps
    9);
    10
    11return (
    12 <div>
    13 <form onSubmit={serverActionProps.mutate}>
    14 {/* Form fields with newProfileData */}
    15 </form>
    16 {optimisticState ? (
    17 <div>
    18 {/* Render the profile with optimistic updates */}
    19 </div>
    20 ) : (
    21 // Render the original profile
    22 )}
    23 </div>
    24)

    Server Actions vs. Server Components: Understanding the Differences

    While both server actions and server components interact with your server logic, they serve distinct purposes and offer different functionalities.

    Server Actions: Focused on Functionality

    Server Components: Rendering Server-Side UI

    Key Differences:

    FeatureServer ActionsServer Components
    TypeFunctionReact Component
    PurposeSpecific tasks, data handlingReusable UI elements
    RenderingNo direct HTML renderingRenders HTML on server
    Data communicationReturns data to clientCan fetch and use data internally
    TriggeringFrom various application partsTypically from client components
    Client interactionNo direct interactionHydrates on client for interactivity
    ExamplesForm validation, email notificationsProduct listing, dynamic UI, SEO content

    Conclusion

    Server Actions empower developers to build performant, dynamic, secure, and user-friendly Next.js applications with several benefits: