How to Use Server Actions in Next js with Best Practices

Introduction:

Next.js continues to improve with new features that enhance developer experience and performance. One of those features is Server Actions, It’s introduced to simplify server-side logic handling without creating separate API routes. Server Actions helps you to keep your components cleaner, improve security, and provide a better way to handle mutations in both Server and Client Components.

By mastering Server Actions, developers can create fast, reliable, and maintainable full-stack applications with ease.

What Are Server Actions?

Server Actions are asynchronous functions that run only on the server. They are invoked directly from your React components and can handle tasks like database mutations, form processing, and more. These actions simplify server-client interactions by eliminating the need for explicit API endpoints.

To declare a Server Action, use the "use server" directive:

// app/actions/user.ts
"use server";

export async function createUser(formData: FormData) {
  const name = formData.get("name");
  const email = formData.get("email");
  // Save to database here
  return { success: true };
}

Using Server Actions in Server Components

In Server Components, you can define Server Actions inline or import them from a separate file. This is especially useful for quick forms or specific mutations tied to one component.

// app/actions/user.ts
'use server';

export async function createUser(formData: FormData) {
const name = formData.get('name');
const email = formData.get('email');
// Save to database here
return { success: true };
}

Using Server Actions in Client Components

You can also use Server Actions in Client Components by importing them from a server-marked module.

// app/actions/user.ts
"use server";

export async function updateUser(formData: FormData) {
  const id = formData.get("id");
  const name = formData.get("name");
  // Update user in DB
  return { success: true };
}

// app/components/EditUserForm.tsx
("use client");

import { updateUser } from "@/app/actions/user";

export default function EditUserForm() {
  return (
    <form action={updateUser}>
      <input type="hidden" name="id" value="123" />
      <input type="text" name="name" />
      <button type="submit">Update</button>
    </form>
  );
}

Binding Parameters to Server Actions

You can pass arguments to Server Actions using .bind(), making them dynamic and reusable.

// app/actions/user.ts
"use server";

export async function deleteUser(userId: string, formData: FormData) {
  // Delete user by ID
}

// app/components/DeleteUserButton.tsx
("use client");

import { deleteUser } from "@/app/actions/user";

export default function DeleteUserButton({ userId }: { userId: string }) {
  const deleteWithId = deleteUser.bind(null, userId);

  return (
    <form action={deleteWithId}>
      <button type="submit">Delete</button>
    </form>
  );
}

Best Practices for Server Actions

Separation of Concerns

  • Keep your logic and UI separate. Define Server Actions in dedicated files and import them where needed.

Organize by Domain

  • Group your actions by feature or domain (actions/user.tsactions/orders.ts) for better structure.

Error Handling

  • Use try-catch blocks inside Server Actions to gracefully handle failures and log issues.

Type Safety

  • Use TypeScript to enforce correct types for FormData fields and return values.

Secure Operations

  • Always verify user sessions or tokens before making sensitive changes, even inside Server Actions.

Avoid Logic Duplication

  • Reuse Server Actions across components to prevent writing the same logic multiple times.

Validate Input

  • Use libraries like Zod or Yup to validate incoming data and avoid corrupting your database.

Final Thoughts

Server Actions offer a powerful pattern for managing server-side logic in a way that feels native to React and Next.js. They simplify the code, reduce the boilerplate of API routes, and make it easier to maintain a full-stack application.

By following the best practices outlined above, you’ll write cleaner, more scalable code that benefits both your team and your users.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *