DEV Community

Cover image for Next.js 14 Server Actions: Boosting Performance
Aziz Bergach
Aziz Bergach

Posted on

Next.js 14 Server Actions: Boosting Performance

Hello developers, today I will show you how to use server actions in next js 14. For the ones who are new to next js, next js is a popular open-source framework for developing server-side rendered React applications. It was created by Vercel, the company formerly known as Zeit, and is maintained by a community of developers, like me. It aims to simplify the development process for "React js" applications by providing various features and tools that streamline the workflow and improve performance. the best thing about next js is the performance and the seo. let's get started!

What are server actions in next js

I will simplify the meaning, they are just a javascript functions, that could be define the react component or in a separated file. the only thing that let them defer from the other function is that they have "use server" in the start point of the function, like this:

// Server Component
export default function Page() {
  // Server Action
  async function create() {
    'use server'

    // ...
  }

  return (
    // ...
  )
}
Enter fullscreen mode Exit fullscreen mode

they also are "async" functions, because they probably need to use "await" inside of them to call the database using prisma for example:

export default function Page() {
  // Server Action
  async function create(formData) {
    'use server'
     prisma.product.create({
     name: formData.name,
     price: formData.price
   })
  }

  return (
    <form action="create" method="POST" >
           <div>
              <label htmlFor="name">Product Name</label>
              <div>
                <input id="name" name="text" type="text"
                  placeholder="MacBook pro M2 Max"
                  required
                />
              </div>
            </div>
            <div>
              <label htmlFor="price">Product Price</label>
              <div>
                <input id="price" name="text" type="text"
                  placeholder="$15.99"
                  required
                />
              </div>
            </div>
            <div>
              <button type="submit"> Add Todo</button>
            </div>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

Why using server actions in next js app ?

to understand that, let's talk of server side rendering "SSR".However, I will let "SSR" for another article, anyway the Server Side Rendering (SSR) is a technique used in web development to improve the user experience by rendering web pages on the server before sending them to the client's browser. So the clients or visitors of the next app would be happy, because there is no heavy tasks compiled on their browsers and it's also good for security reasons. I will cover also "client side vs server side rendering" and "client component vs server component in next js".

Image description

However, it's good to know that server actions doesn't get compiled in the browser, but in node js ">= v18" server. they are the new API endpoints in next js 14, yes as you heard they are actually end points, if you noticed in the code above, we use the server action function "create" as "action" of the "form" element in HTML:

async function create(formData) {
    'use server'
     prisma.product.create({
     name: formData.name,
     price: formData.price
   })
  }

  return (
    <form action="create" method="**POST**" >
...
</form>)
Enter fullscreen mode Exit fullscreen mode

and if you have some experience using PHP programing language maybe you've noticed that what we see in the next js server actions in the same like:

<form action="script.php" method="POST">
    <label for="name">Name:</label>
    <input type="text" name="name" required>

    <label for="price">Price:</label>
    <input type="text" name="price" required>

    <button type="submit">Create Product</button>
</form>
Enter fullscreen mode Exit fullscreen mode

where the "script.php" is the file where you implement the logic to insert the product to database , it may something like:

<?php

// Include your database connection logic here
// For example, assuming you have a file named "db.php" with database connection details
require_once('db.php');

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // Get form data
    $name = $_POST["name"] ?? null;
    $price = $_POST["price"] ?? null;

    // Validate form data (you may want to add more validation)
    if ($name && $price) {
        // Use MySQLi to connect to the database (adjust with your database details)
        $conn = new mysqli($db_host, $db_username, $db_password, $db_name);

        // Check the connection
        if ($conn->connect_error) {
            die("Connection failed: " . $conn->connect_error);
        }

        // Prepare and execute the SQL query to insert the product
        $stmt = $conn->prepare("INSERT INTO products (name, price) VALUES (?, ?)");
        $stmt->bind_param("ss", $name, $price);

        if ($stmt->execute()) {
            // Product inserted successfully
            echo "Product created successfully!";
        } else {
            // Handle any errors that occurred during product creation
            echo "Error creating product: " . $stmt->error;
        }

        // Close the database connection
        $stmt->close();
        $conn->close();
    } else {
        // Handle invalid form data
        echo "Invalid form data!";
    }
}
?>
Enter fullscreen mode Exit fullscreen mode

next js and php use the same context for handling API calls at the same manner, like you sent a "POST" request from the "form" in php you do the same in next js 14, as you see, server actions are just API end points.

let's resume why you need use server actions in next js 14:

  • they get rendered in the server side

  • you could gather your logic in one place

  • you could export and use it anywhere

  • no more extra APIs

  • Fetching data from external APIs

  • perform business logic that requires server execution

  • update DB data without creating separate API routes

Implementing server actions in next js 14

After releasing next js 14, which is a wonderful update from vercel, server actions became stable and you could start using them safely right away!
In the past versions like next js 13, we implement API (server side routes) in another folder called "api" like this:

// apis
/app/api/hello/route.ts
/app/api/product/create/route.ts
/app/api/login/route.ts

// front end
/app/login/page.tsx
/app/dashboard/page.tsx
Enter fullscreen mode Exit fullscreen mode

we separate the back-end from the front-end, and this could lead to creating extra endpoints or maybe an endpoint for one purpose, one page from the from end that use it.

Next js becomes more PHP like, and I think that is not bad too much. the ability of contacting with database from server or client react component is useful in many cases and make the development easy.

To use server actions in Next Js 14, you could use them directly inside the component, it doesn't matter if it is a client or server component the definition (of the server action function) and the utilize stays the same, it's like we do before. In the other hand, you could use them in separate file, the only condition is to remark the entire file with "use server" at the top so everything inside of this file will execute on the server:
/src/actions/product-actions.ts

"use server"

export async function createProduct(formData) {
     prisma.product.create({
     name: formData.name,
     price: formData.price
   })
  }

export async function updateProduct(formData) {
     prisma.product.create({
     name: formData.name,
     price: formData.price
   })
  }
Enter fullscreen mode Exit fullscreen mode

How server actions work

let's jump to the work process. In a Next.js 14 app, when a user does something or met certain condition on the app, like clicking a button or filling out a form, it triggers a special async function that works on the server called "Server Action". Think of it like calling a function that works in the background.

Next.js makes the development process easy for you as a next js developer. It takes the information from the user's action, like form data like we did in the example before, then serialize the request parameters, and after that next js sends it over to the server. Once there, the server will then deserialize the request parameters, understands them, and uses the specific function related to the "Server Action" you put as action of form.
<form action="create" method="POST" >

After Server Action has finished executing successfully, the server will prepare the information, and serialize the response, and sends it back to Next.js 14, and then Next.js will deserialize the response from server and sends it to the user's browser. When everything is ready and settled, the user's browser continues doing what it needs to do on the webpage based on the received information. It's like a smooth conversation between the user's browser and the server in a Next.js application, making things work efficiently.

The utilize of server actions

in this section, we will learn what can server action do ?
Server actions are built to take rid of the API routes that you use inside the app, if your project need an external use API, meaning another app or website will use that API, then just create the /api/my-api/route.ts file.

However,the utilize of next js 14 server actions is very simple, it doesn't offer data fetching on the client, but it handles the other staff, database update and insert or fetching data from external APIs. Server actions are the new API endpint unless data fetching, use server component to do this await prisma.product.findMany(), because server component has an access to database and that wasn't weird, just read its name server component.

To utilize server actions you'll need to define them, so to define server action on server component:

// Server Component
export default function Page() {
  // Server Action
  async function create() {
    'use server'

    // ...
  }

  return (
    // ...
  )
}
Enter fullscreen mode Exit fullscreen mode

and unfortunately,you can't do the same in client component, However, you could define them in separate file for example app/actions.ts, just like this:

'use server'

export async function create() {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

and you could the number of server actions you want, and to use them on the client component, let say app/ui/button.tsx you will need to import them and use them in a <from action="create" method="POST"> like we did in the server component:

import { create } from '@/app/actions'

export function Button() {
  return (
    // ...
  )
}
Enter fullscreen mode Exit fullscreen mode

In some cases, you may need to pass this server action to a client component as prop, yes you could do it just like this <ClientComponent createProduct={create} />, and you can use it like if you import it, think of it like this:

  • it's a good practice if the Page for example src/app/page.tsx to be a server component and create another folder for example src/components/ui/ and put all the client components here in separate folders and then import them to your Page.

  • you could define a custom server action that you will just need to use in that server component (Page) and his children client components also

  • you can pass it throw props, or you could use context API from React Js

  • then start use it in the children components also

Nice, until now you've learned a lot, but we didn't finished yet :)
I still have problem, what if I create a server action that updates products (name and description) in the file src/app/actions.ts that looks like this:

'use server'

export async function update(productID, formData) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

and for the component I have something like this:

'use client'

import { update } from './actions'

export function updateProduct({ productID }: { productID: string }) {

  return (
    <form action={update}>
      <input type="text" name="name" />
      <button type="submit">Update Product Name</button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

MMM, that's look good, but how I could get the id of the product ?
For that situation I come to you with a trick to pass the id of the product without any problem, just do like this and pass any number of args you like, but keep it like this .bind(null, arg1, arg2, ...) and you are good to go:

'use client'

import { update } from './actions'

export function updateProduct({ productID }: { productID: string }) {
  const updateProductWithId = updateProduct.bind(null, productID)

  return (
    <form action={updateProductWithId}>
      <input type="text" name="name" />
      <button type="submit">Update Product Name</button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

I think this method is the best, however, there is an alternative of it. Use hidden inputs like this <input type="hidden" name="productID" value={productID} /> and make sure to put them between <form> ...here... </form>, this just for anyone that doesn't like the first method for any reason :)

Different use places of server actions in next js 14

this is a too long article, but we still in progress, we didn't finished the utilize yet. maybe someone ask, is the form action the only way to use server actions in Next Js 14 ?
Good question, the simple answer is no, they can be invoked from event handlers, useEffect, third-party libraries, and other form elements like .

Invoke server actions from event handler

for example let's update a user info, using server action invoked by onClick event of a button:

import { updateUser } from './serverAction';

function UpdateUser() {
  const handleButtonClick = async () => {
    const data = { /* ... */ };
    await updateUser(data);
  };

  return (
    <button onClick={handleButtonClick}>Update User</button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Invoke server actions from UseEffect hook

If we say useEffect, we could imagine what may be the purpose of that server action, yes you are right, it could be two things, as I think, fetching data or tricking some thing like page view, I mean doing something when a state changed. Let's just fetch user profiles data from database using server action inside useEffect hook:

import { useEffect, useState } from 'react';
import { fetchUserProfile } from './serverActions';

function UserProfileComponent({ userId }) {
  const [userProfile, setUserProfile] = useState(null);

  useEffect(() => {
    async function getUserProfile() {
      const profile = await fetchUserProfile(userId);
      setUserProfile(profile);
    }

    getUserProfile();
  }, [userId]);

  if (!userProfile) {
    return <div>Loading profile...</div>;
  }

  return (
    <div>
      <h1>{userProfile.name}</h1>
      {/* Render other profile details */}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

because I hate talking about something without demonstration, so here is the code to track page views with server actions and useEffect hook:

'use client'

import { incrementViews } from './actions'
import { useState, useEffect } from 'react'

export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)

  useEffect(() => {
    const updateViews = async () => {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    }

    updateViews()
  }, [])

  return <p>Total Views: {views}</p>
}
Enter fullscreen mode Exit fullscreen mode

and if didn't understand anything, just let me know in the comment :)

Invoke server actions from third party libarary

let's take an example of 'react-query', its an npm package and you could install it by npm i react-query.

import { useQuery } from 'react-query';
import { getLatestNews } from './serverActions';

function NewsComponent() {
  const { data: news, isLoading } = useQuery('latestNews', getLatestNews);

  if (isLoading) {
    return <div>Loading news...</div>;
  }

  return (
    <div>
      {news.map((newsItem, index) => (
        <div key={index}>
          <h3>{newsItem.title}</h3>
          {/* render other news item details */}
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

As you see above, we’ve implemented the server action ‘getLatestNews()’ Inside of the hook ‘useQuery()’, it's quite simple.

If you have any questions, feel free to leave them in the comments section. you could find me on substack

Top comments (0)