Server Action in Next.js: What it is, How to Define Them, and What to Consider for Security

Learn how to use Server Actions in Next.js to manage server-side logic safely and efficiently. This comprehensive guide includes practical examples, implementation tips, and best practices to improve the performance and security of your applications.

5 min readNov 19, 2024

--

Server Actions in Next.js are server-side functions that process forms and data. They use the "use server" directive to move processing tasks to the server, which improves app performance and scalability. This guide will show you how to set up and use Server Actions to reduce client workload, enhance the user experience, and better manage server resources.

After reading this guide, you’ll learn how to:

  • Set up Server Actions for secure and efficient processing.
  • Boost form submission and data handling speed.
  • Use caching and revalidation for quick updates.
  • Properly use Next.js 15 features to avoid setup mistakes

What are server actions?

Server actions are server-side functions that process forms and data in Next.js applications. They execute asynchronously and can be invoked from client components. To implement a server action, add the "use server"directive at the top of the file containing the function.

// 1 Method: External File
"use server"

import { UserSchema } from "@/schemas/User"
import type { User } from "@/schemas/User"

type ReturnType = { message: string, errors?: Record<string, unknown> }

export async function saveUser(user: User): Promise<ReturnType> {
const parsed = UserSchema.safeParse(user)
if (!parsed.success) return;
return { message: “success” }
}

// 2 Method: In Componet
export function Component() {
const serverAction = async () => {
"use server"
}

return <form action={serverAction}>...</form>
}

You can use the bind() method to pass extra parameters to server actions. This allows you to preset certain arguments for your server action.

"use server"

import { revalidatePath } from "next/cache"

async function addItem(prevState: any, formData: FormData) {
const item = formData.get("item")
// Logica per aggiungere l'item
revalidatePath("/")
return { message: `Item ${item} aggiunto con successo` }
}

// Aggiunta di un nuovo parametro 'category'
const addItemToCategory = addItem.bind(null, "electronics")

export default addItemToCategor

Note: Server actions are publicly accessible APIs. We’ll discuss security measures later.

Using the “use server” Directive

For server-side execution of functions defined outside components, include the "use server" directive at the file’s start or within the function. Omitting this directive triggers a Next.js security error, as it can’t determine the intended execution environment. This is a key aspect of Server Actions.

Advantages of Server Actions

  • Enhanced Performance: Server-side logic processing reduces client workload.
  • Efficient Caching and Revalidation: Server actions work seamlessly with Next.js’s caching and revalidation system, enabling single-request updates for both UI and data.

Server Action Behavior: Key Points

Server actions operate differently depending on the context:

  1. Form submissions:
  • Server Components: Forms work without JavaScript, using standard HTML.
  • Client Components: Actions queue until JavaScript loads, prioritizing hydration.
  • Post-hydration: No page reload on form submission.

2. Runtime inheritance: Actions use the page or layout’s runtime (node, edge).

3. Flexible usage: Not limited to forms; can be used in event handlers, useEffect, and other UI elements.

Potential Challenges with Server Actions

  • Resource management: Shifting tasks to the server requires careful monitoring of server resources to maintain optimal performance.
  • Network considerations: While generally improving performance, server actions may lead to minor delays, particularly noticeable for users with slower internet connections.

Security Enhancements

Next.js 15 brings key security upgrades for server actions:

  • Unused server actions are now excluded from the client bundle, reducing exposure.
  • Each server action gets a unique ID, boosting security through a hidden input field.

These IDs are cached for up to 14 days and reset after new builds or cache invalidation.

Let’s examine practical strategies to bolster the security of our server actions:

  1. User verification: Implement robust checks to confirm user identity and permissions. For instance:
'use server'

import { auth } from './lib'

export function addItem() {
const { user } = auth()
if (!user) {
throw new Error('You must be signed in to perform this action')
}

// ...
}

2. Backend Validation with Zod: For server actions in forms, Zod is an effective tool for data validation. Here’s a straightforward example:

import { z } from 'zod';

const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().min(18)
});

export async function createUser(formData: FormData) {
'use server'

const result = UserSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
age: Number(formData.get('age'))
});

if (!result.success) {
return { error: result.error.errors };
}
}

This approach ensures that the server only processes data that meets specified criteria, enhancing security and reliability. By validating input before processing, Zod helps prevent potential vulnerabilities and improves overall application integrity.

3. Closure & Encryption: Declaring server actions inside components creates closures that can access component variables. To prevent client-side data exposure, Next.js encrypts these actions with a new private key at each build. This only applies to actions defined within components. It’s best to declare them externally, possibly using the “server-only” package. Don’t rely solely on encryption; use React taint APIs for added security.

4. Origin Restrictions: Next.js Server Actions use POST requests and compare headers to prevent cross-site attacks. They’re limited to the same host by default. For multi-domain setups, use "allowedOrigins" in your config to specify trusted origins:

/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}

Conclusion

Server Actions in Next.js boost app performance, scalability, and security. They move processing to the server, cutting client workload and using Next.js caching for quick updates. Here’s what you need to know:

  • Speed Boost: The server handles heavy lifting, making apps faster, especially for big tasks.
  • Smooth Teamwork: Works well with caching, keeping data and UI in sync without extra server calls.
  • Tighter Security: Unique IDs and smart bundling make apps safer. Add Zod for even better data checks.
  • Easy Setup: Use "use server" to run functions on the server without hassle.
  • Trade-offs: While great for scaling, watch out for server load and slower connections.

Join dev journey

Join the community to learn more about development. Please share your thoughts about the topic to help us know more.

  • Share the article on socials.
  • More content at Dev Journey.

--

--

Dev Journey
Dev Journey

Written by Dev Journey

Dev journey speaks about development. I want to share my thoughts and new knowledge that I learn day per day.

Responses (1)