Speed up server actions with Next.js' after function

I have a small fun post for you today! Hoping this will help you speed up your server actions!

Server actions are a great paradigm that brings back logic to the server. When it first came out it reminded people of PHP. The beauty of this paradigm is that it lets you keep a lot of logic on the server, hiding API keys, sensitive data, and more.

One thing however to keep in mind is that servers have a flaw: round trips can be slow.

Let's imagine a typical round trip for an action:

  1. The user requests a page to subscribe to a news letter
  2. The page is rendered on the server and send to the user
  3. The user fills in the form and submits it to the server
  4. The server performs the database write, sends the email and redirects the user to a thank you page
  5. The user sees the thank you page but the loading took 3-4 seconds.

The problem is that the user has to wait for the server to perform the action. This is where the after function comes in.

The after function is a function that is called after the server action is complete. It can be used to perform any additional logic, such as sending a thank you email.

How to use the after function

Let's take a real example such as the unsubscrive function from this blog's newsletter. (which is great by the way you should subscribe)


// grab the subscription id from the url in the Next.js page
  const { id } = await searchParams;

  async function formAction() {
    "use server";

    // get the subscription from the database
    const newsletterDoc = await firebaseAdminDb
      .collection("newsletter")
      .doc(id)
      .get();

    // if the subscription does not exist, throw an error
    if (!newsletterDoc.exists) {
      throw new Error("Newsletter subscription not found");
    }
    // get the email from the subscription
    const data = newsletterDoc.data();
    const email = data?.email;

    // if the email is not found, throw an error
    if (!email) {
      throw new Error("Email not found in subscription data");
    }

    // Call the unsubscribe function from the newsletter library
    await unsubscribeFromNewsletter(email);

    // Send unsubscribe confirmation email
    await sendUnsubscribeEmail(email);

    // Redirect to confirmation page
    redirect("/newsletter/resubscribe");
  }

On a good day this will take 1-2 seconds to complete. But does your user really need to wait for this?

The answer is no. The user just wants to unsubscribe and move on with their day. The idea behavior is that the user clicks the unsubscribe button and are immediately redirected to the next page.

We can use the after function to send the email after the server action is complete.

Let's now move all of the async logic to the after function.

async function formAction() {
    "use server";

    after(async () => {
      const newsletterDoc = await firebaseAdminDb
        .collection("newsletter")
        .doc(id)
        .get();

      if (!newsletterDoc.exists) {
        console.error("Newsletter subscription not found");
        return;
      }

      const data = newsletterDoc.data();
      const email = data?.email;

      if (!email) {
        console.error("Email not found in subscription data");
        return;
      }

      await unsubscribeFromNewsletter(email);
      await sendUnsubscribeEmail(email);
    });

    // Redirect immediately
    redirect("/newsletter/resubscribe");
  }

This will now take 0-1 seconds to complete. The user will be redirected immediately and the email will be sent in the background. The after funciton will allow the server action to terminate early but will ensure the code in the after function will be executed. A drawback is that the code might actually error out!

A good idea would be to ensure a proper retry mechanism or a proper error handling flow.

Conclusion

The after function is a great way to speed up your server actions. It allows you to perform any additional logic after the server action is complete.

I hope you enjoyed this post! If you have any questions, feel free to ask in the comments below.