Cara Menggunakan Server Actions di Next.js: Optimalkan Aplikasi Full-stack Anda

Sebagai developer Next.js, kita selalu mencari cara untuk menyederhanakan arsitektur aplikasi dan meningkatkan pengalaman pengembangan. Dulu, mengelola data mutation seperti submit form atau update data seringkali membutuhkan API layer terpisah, baik itu REST API, GraphQL, atau tRPC. Proses ini, meskipun efektif, bisa menambah kompleksitas dan overhead.

Nah, di Next.js 13.4, Server Actions hadir sebagai game changer. Fitur ini memungkinkan Anda menjalankan kode server-side secara langsung dari komponen React client-side Anda, menghilangkan kebutuhan akan API endpoint terpisah untuk banyak kasus penggunaan. Ini bukan hanya tentang efisiensi, tapi juga tentang mengkonsolidasi logika backend dan frontend di satu tempat, membuat aplikasi Anda lebih kohesif dan mudah di-maintain.

Mari kita selami lebih dalam bagaimana Server Actions bekerja, mengapa ini penting, dan bagaimana Anda bisa mengimplementasikannya untuk membangun aplikasi full-stack yang lebih powerful dan efisien.

Daftar Isi sembunyikan

Apa Itu Server Actions di Next.js?

Server Actions adalah kemampuan Next.js untuk menjalankan kode server-side langsung dari client component Anda, baik itu dengan onSubmit event dari sebuah form atau dengan memanggil fungsi secara langsung. Konsep dasarnya adalah Remote Procedure Call (RPC), di mana Anda bisa “memanggil” sebuah fungsi yang didefinisikan di server dari client.

Fungsi yang dideklarasikan sebagai Server Action akan dieksekusi di server dan bisa melakukan operasi apapun yang umumnya dilakukan di backend, seperti:

  • Berinteraksi dengan database.
  • Mengakses file system.
  • Menangani autentikasi.
  • Menggunakan variabel environment yang hanya ada di server.

Keuntungan utamanya adalah menyederhanakan alur kerja data mutation. Daripada membuat API route terpisah (/api/create-user, /api/update-post, dll.) dan kemudian memanggilnya dari client menggunakan fetch, Anda cukup mendefinisikan sebuah fungsi di server dan memanggilnya langsung.

Mengapa Server Actions Penting untuk Developer?

Server Actions menawarkan beberapa keuntungan signifikan yang mengubah cara kita membangun aplikasi Next.js:

1. Mengurangi Kompleksitas Arsitektur

Anda tidak perlu lagi membuat API route terpisah hanya untuk operasi data mutation. Ini sangat mengurangi boilerplate code dan jumlah file yang harus di-maintain.

2. Developer Experience (DX) yang Lebih Baik

Logika frontend dan backend untuk sebuah fitur kini bisa ditempatkan lebih dekat, terkadang bahkan dalam satu file komponen. Ini memudahkan pemahaman dan debugging, terutama untuk fitur yang relatif sederhana.

3. Performa Optimal

Next.js mengoptimalkan pengiriman kode Server Action. Hanya JavaScript yang diperlukan untuk memanggil fungsi yang dikirim ke browser, bukan seluruh logika server. Selain itu, Server Actions terintegrasi dengan data revalidation di Next.js, memastikan UI Anda selalu menampilkan data terbaru tanpa perlu refresh manual.

4. Keamanan yang Ditingkatkan

Karena Server Actions berjalan di server, mereka memiliki akses ke resource server-side yang aman (seperti kredensial database) tanpa mengeksposnya ke client. Namun, validasi input tetap krusial.

5. Integrasi Seamless dengan Form HTML

Anda bisa langsung mengaitkan Server Action dengan atribut action pada form HTML, memungkinkan progressive enhancement secara otomatis. Form akan berfungsi bahkan jika JavaScript dinonaktifkan.

Prasyarat Sebelum Memulai

Untuk mengikuti panduan ini, pastikan Anda memiliki:

  • Node.js terinstal di sistem Anda.
  • Pengetahuan dasar React dan Next.js (terutama App Router).
  • Proyek Next.js yang menggunakan App Router (versi 13.4 atau lebih baru).

Mengaktifkan Server Actions di Next.js

Secara default, Server Actions sudah aktif jika Anda membuat proyek baru dengan App Router. Namun, jika Anda mengupgrade proyek lama atau ingin memastikan, Anda bisa mengaktifkannya secara eksplisit di file next.config.js:


/ @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverActions: true,
  },
};

module.exports = nextConfig;

Setelah itu, restart server pengembangan Anda.

Dasar-dasar Menggunakan Server Actions

Ada dua cara utama untuk mendefinisikan Server Action:

1. Mendefinisikan Server Action di Dalam Server Component

Server Actions bisa didefinisikan langsung di dalam Server Component. Ini berguna ketika aksi tersebut sangat terikat dengan komponen tersebut.


// app/page.tsx
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export default function Page() {
  async function createTodo(formData: FormData) {
    'use server'; // Directive ini WAJIB

    const todo = formData.get('todo') as string;
    // Logika untuk menyimpan 'todo' ke database
    console.log('Menambahkan todo:', todo);

    // Revalidasi cache data untuk path ini
    revalidatePath('/');
    // Bisa juga redirect setelah aksi
    // redirect('/success-page');
  }

  return (
    

Daftar Todo

{/* Tampilkan daftar todo di sini */}
); }

Perhatikan beberapa hal penting:

  • 'use server';: Ini adalah directive ajaib yang memberitahu Next.js bahwa fungsi ini harus dieksekusi di server. Ini WAJIB ada di baris pertama fungsi atau di bagian atas file jika semua fungsi di file tersebut adalah Server Actions.
  • formData: FormData: Jika Server Action dipanggil dari atribut action sebuah form, ia akan menerima objek FormData sebagai argumen pertamanya. Ini sangat memudahkan untuk mengakses input dari form.
  • revalidatePath / revalidateTag: Setelah melakukan mutasi data, Anda sering ingin UI client-side menampilkan data terbaru. Fungsi-fungsi ini dari next/cache memungkinkan Anda untuk me-revalidate cache data Next.js, sehingga data terbaru akan diambil dan UI diperbarui secara otomatis.
  • redirect: Dari next/navigation, ini bisa digunakan untuk mengarahkan pengguna ke halaman lain setelah Server Action berhasil dieksekusi.

2. Mendefinisikan Server Action di File Terpisah (Shared Server Actions)

Untuk menjaga kode tetap rapi dan reusable, Anda bisa mendefinisikan Server Actions di file terpisah dan mengimpornya ke mana pun Anda butuhkan.


// lib/actions.ts atau app/actions.ts
'use server'; // Directive ini di bagian atas file menandakan semua export adalah Server Actions

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string;
  const content = formData.get('content') as string;

  // Lakukan validasi input di sini
  if (!title || title.length 

// app/blog/page.tsx (Server Component)
import { createPost } from '@/lib/actions';

export default function BlogPage() {
  return (
    

Buat Post Baru

{/* Tampilkan daftar post di sini */}
); }

// app/profile/[[...id]]/page.tsx (Client Component)
'use client';

import { deleteUser } from '@/lib/actions';

export default function UserProfile({ params }: { params: { id: string[] } }) {
  const userId = params.id ? params.id[0] : 'default-user-id';

  const handleDelete = async () => {
    if (confirm('Apakah Anda yakin ingin menghapus user ini?')) {
      try {
        await deleteUser(userId);
        alert('User berhasil dihapus!');
        // Mungkin redirect atau update UI lainnya
      } catch (error: any) {
        alert(`Gagal menghapus user: ${error.message}`);
      }
    }
  };

  return (
    

Profil Pengguna {userId}

); }

Dalam contoh Client Component di atas, deleteUser dipanggil langsung sebagai fungsi async. Ini menunjukkan fleksibilitas Server Actions; mereka bisa dipanggil baik dari atribut action form maupun secara imperatif dari handler event JavaScript.

Menangani State Loading dan Error dengan Server Actions

Ketika berinteraksi dengan Server Actions dari Client Component, penting untuk memberikan feedback kepada user (misalnya, status loading atau pesan error). Next.js menyediakan beberapa hook untuk membantu hal ini:

1. useFormStatus

Hook ini memungkinkan Anda untuk membaca status pengiriman form di dalam Client Component. Ini sangat berguna untuk menonaktifkan tombol submit atau menampilkan spinner loading.


// app/components/SubmitButton.tsx
'use client';

import { useFormStatus } from 'react-dom'; // Ini dari 'react-dom', bukan 'next/navigation'

export function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    
  );
}

// app/page.tsx (Client Component)
'use client';

import { createTodo } from '@/lib/actions';
import { SubmitButton } from './components/SubmitButton';

export default function TodoForm() {
  return (
    
); }

Penting: SubmitButton harus menjadi children dari elemen <form> yang memanggil Server Action agar useFormStatus dapat berfungsi dengan benar.

2. useFormState

Hook ini memungkinkan Anda untuk mengelola state dari form yang dikirimkan, termasuk pesan error atau feedback lainnya. Ini mengembalikan array dengan state saat ini dan fungsi baru yang diperbarui untuk form action Anda.


// lib/actions.ts
'use server';

import { revalidatePath } from 'next/cache';

export async function createUser(prevState: any, formData: FormData) {
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;

  if (!name || name.length 

// app/users/create/page.tsx (Client Component)
'use client';

import { useFormState } from 'react-dom';
import { createUser } from '@/lib/actions';
import { SubmitButton } from '../../components/SubmitButton'; // Gunakan SubmitButton yang sudah ada

const initialState = {
  message: '',
  success: false,
};

export default function CreateUserForm() {
  const [state, formAction] = useFormState(createUser, initialState);

  return (
    

Buat User Baru

{state.message && (

{state.message}

)}
); }

useFormState menerima dua argumen: fungsi Server Action Anda dan state awal. Ia mengembalikan state saat ini dan fungsi yang dapat Anda teruskan ke atribut action form Anda. Setiap kali form disubmit, fungsi Server Action akan dipanggil dengan state sebelumnya sebagai argumen pertama.

Keamanan dan Validasi di Server Actions

Meskipun Server Actions berjalan di server, validasi input tetap sangat penting. Jangan pernah percaya data yang datang dari client. Selalu lakukan validasi server-side untuk melindungi aplikasi Anda dari input yang berbahaya atau tidak valid.

Contoh validasi sederhana sudah terlihat di bagian createUser di atas. Untuk validasi yang lebih kompleks, Anda bisa menggunakan library seperti Zod.


// lib/actions.ts (dengan Zod)
'use server';

import { z } from 'zod';
import { revalidatePath } from 'next/cache';

const userSchema = z.object({
  name: z.string().min(3, 'Nama harus minimal 3 karakter.'),
  email: z.string().email('Email tidak valid.'),
});

export async function createValidatedUser(prevState: any, formData: FormData) {
  const validatedFields = userSchema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
  });

  if (!validatedFields.success) {
    return {
      message: 'Validasi gagal!',
      errors: validatedFields.error.flatten().fieldErrors,
    };
  }

  const { name, email } = validatedFields.data;
  console.log('Membuat user valid:', { name, email });
  // ... simpan ke database ...

  revalidatePath('/users');
  return { message: 'User berhasil dibuat!', success: true };
}

Selain validasi, pastikan Anda juga menerapkan:

  • Autentikasi: Pastikan hanya user yang terautentikasi yang dapat melakukan aksi tertentu.
  • Otorisasi: Pastikan user yang terautentikasi memiliki izin yang diperlukan untuk melakukan aksi tersebut.

Pengalaman dan Pertimbangan Praktis

Dalam praktik pengembangan nyata, Server Actions menawarkan banyak kemudahan, tetapi juga penting untuk memahami kapan dan bagaimana menggunakannya secara optimal.

Kapan Menggunakan Server Actions vs. API Routes?

Ini adalah pertanyaan umum yang sering muncul. Berikut adalah panduan yang sering saya pakai:

  • Gunakan Server Actions jika:
    • Anda melakukan operasi mutasi data (CUD - Create, Update, Delete) yang terikat erat dengan UI atau form di Next.js App Router Anda.
    • Anda ingin menyederhanakan logika backend untuk fitur-fitur yang tidak memerlukan API publik atau yang sangat spesifik untuk aplikasi Next.js Anda.
    • Anda membutuhkan integrasi seamless dengan revalidasi cache Next.js.
    • Anda memprioritaskan developer experience dan ingin mengkonsolidasi kode server dan client di satu tempat.
  • Gunakan API Routes (Route Handlers) jika:
    • Anda membangun API publik atau internal yang akan diakses oleh client lain selain aplikasi Next.js Anda (misalnya, aplikasi mobile, third-party services, atau aplikasi frontend lain yang tidak berbasis Next.js).
    • Anda membutuhkan kontrol penuh atas headers HTTP, status code, dan respons yang lebih kompleks (meskipun Server Actions juga bisa mengembalikan data).
    • Anda ingin memisahkan secara ketat logika API dari logika komponen UI Anda, terutama untuk aplikasi berskala besar dengan banyak konsumen API.
    • Anda mengimplementasikan middleware kustom yang kompleks sebelum memproses request.

Secara umum, untuk sebagian besar operasi form submission dan data mutation di aplikasi Next.js, Server Actions adalah pilihan yang sangat efisien. Namun, jika Anda membangun API sejati, Route Handlers (API Routes) masih menjadi jawabannya.

Testing Server Actions

Menguji Server Actions sedikit berbeda dari menguji API Routes. Karena mereka adalah fungsi JavaScript biasa yang dieksekusi di server, Anda bisa mengujinya seperti fungsi server-side lainnya menggunakan Jest, Vitest, atau tools testing lainnya. Anda mungkin perlu memock interaksi dengan database atau layanan eksternal lainnya.

Batasan dan Pertimbangan Lain

  • Ukuran Payload: Ada batasan ukuran payload yang bisa dikirimkan ke Server Action. Untuk file upload yang sangat besar, Anda mungkin perlu memikirkan solusi streaming atau menggunakan layanan penyimpanan cloud langsung dari client.
  • Kompilasi: Server Actions dikompilasi ke dalam bundle JavaScript yang dieksekusi di server, jadi pastikan tidak ada efek samping yang tidak diinginkan dari kode server yang Anda tulis.
  • Global State: Server Actions dieksekusi dalam konteks request yang terisolasi. Jangan mengandalkan global state yang akan persisten di antara request.

Contoh Integrasi dengan Database (Mental Model)

Dalam praktiknya, Server Actions akan berinteraksi dengan database Anda. Misalnya, menggunakan ORM seperti Prisma:


// lib/db.ts
import { PrismaClient } from '@prisma/client';

let prisma: PrismaClient;

if (process.env.NODE_ENV === 'production') {
  prisma = new PrismaClient();
} else {
  // Hindari instansi PrismaClient baru di hot-reloading development
  if (!(global as any).prisma) {
    (global as any).prisma = new PrismaClient();
  }
  prisma = (global as any).prisma;
}

export default prisma;

// lib/actions.ts (contoh dengan Prisma)
'use server';

import { revalidatePath } from 'next/cache';
import prisma from '@/lib/db'; // Import Prisma client Anda

export async function createProduct(formData: FormData) {
  const name = formData.get('name') as string;
  const price = parseFloat(formData.get('price') as string);

  if (!name || price 

Dengan cara ini, Server Action Anda secara aman berkomunikasi dengan database melalui ORM.

Masalah yang Sering Terjadi

Saat pertama kali menggunakan Server Actions, beberapa masalah umum mungkin muncul. Berikut adalah beberapa di antaranya dan cara mengatasinya:

1. Lupa Directive 'use server'

Gejala: Fungsi Anda tidak berjalan di server dan mungkin menimbulkan error runtime terkait akses ke variabel environment server-side atau operasi I/O file system.

Penyebab: Anda lupa menambahkan 'use server' di baris pertama fungsi Server Action atau di bagian atas file yang berisi beberapa Server Action.

Solusi: Tambahkan 'use server';. Pastikan itu adalah baris pertama kode di dalam fungsi atau di bagian paling atas file jika Anda mendefinisikan Server Action di file terpisah.

2. Meneruskan Objek Non-Serializable dari Client Component

Gejala: Error serialization di console browser atau server, seperti "Only plain objects, and a few built-ins, can be passed to Server Actions."

Penyebab: Saat memanggil Server Action secara imperatif dari Client Component (misalnya, di handler onClick), Anda mencoba meneruskan objek JavaScript yang kompleks (seperti instance class, fungsi, atau objek yang berisi siklus referensi) sebagai argumen.

Solusi: Pastikan semua argumen yang Anda teruskan ke Server Action dari client adalah tipe data yang serializable (string, number, boolean, array, plain object, FormData). Jika Anda perlu meneruskan data kompleks, ubah menjadi representasi serializable terlebih dahulu.

3. Revalidasi Cache Tidak Bekerja

Gejala: Setelah Server Action berhasil dieksekusi, UI tidak diperbarui untuk menampilkan data terbaru.

Penyebab: Anda lupa memanggil revalidatePath('/your-path') atau revalidateTag('your-tag') di dalam Server Action Anda, atau path/tag yang Anda revalidasi tidak cocok dengan data yang ingin Anda perbarui.

Solusi: Pastikan Anda memanggil fungsi revalidasi yang sesuai setelah operasi mutasi data berhasil. Periksa apakah path atau tag yang Anda gunakan benar-benar mencakup data yang perlu diperbarui.

4. Masalah Keamanan: Tidak Melakukan Validasi Input

Gejala: Data yang tidak valid atau berbahaya tersimpan di database, atau Server Action dieksekusi oleh user yang tidak berhak.

Penyebab: Anda mengandalkan validasi di sisi client saja atau tidak melakukan otorisasi yang memadai.

Solusi: Selalu lakukan validasi input di sisi server, meskipun Anda sudah memvalidasinya di client. Gunakan library seperti Zod. Implementasikan juga logika autentikasi dan otorisasi yang ketat di Server Action Anda untuk memastikan hanya user yang berhak yang bisa melakukan aksi tertentu.

5. Salah Membedakan antara Server Components dan Server Actions

Gejala: Kebingungan tentang kapan menggunakan 'use client' atau 'use server', atau mencoba melakukan operasi server-side langsung di Client Component.

Penyebab: Kurangnya pemahaman tentang perbedaan fundamental antara Server Components (yang secara default dieksekusi di server untuk rendering UI) dan Server Actions (fungsi yang dieksekusi di server untuk data mutation).

Solusi: Ingat bahwa Server Components adalah tentang rendering UI di server, sedangkan Server Actions adalah tentang menjalankan fungsi di server. Client Components menggunakan 'use client' untuk memungkinkan interaktivitas, dan Server Actions dapat dipanggil dari Server Components maupun Client Components (dengan pengecualian serialisasi).

FAQ

Apa perbedaan utama antara Server Actions dan Route Handlers (API Routes)?

Server Actions dirancang khusus untuk operasi data mutation yang dipanggil dari komponen React Anda, seringkali terkait dengan form submission, dan terintegrasi erat dengan sistem cache dan revalidation Next.js. Route Handlers (API Routes) lebih mirip API endpoint tradisional; mereka ideal untuk membangun API publik, mengontrol headers HTTP, dan melayani berbagai jenis client.

Bisakah Server Actions mengembalikan data?

Ya, Server Actions dapat mengembalikan data. Anda bisa menangani nilai kembalian ini di sisi client, terutama jika Anda memanggil Server Action secara imperatif (misalnya, dengan onClick) atau menggunakan hook useFormState untuk menerima feedback setelah form disubmit.

Apakah Server Actions aman secara default?

Server Actions berjalan di server, yang berarti mereka memiliki akses ke environment server-side yang aman. Namun, ini tidak secara otomatis membuat aksi itu sendiri aman. Anda harus tetap melakukan validasi input yang ketat, autentikasi, dan otorisasi di dalam Server Action untuk mencegah kerentanan keamanan seperti injeksi SQL, XSS, atau akses data yang tidak sah.

Bagaimana cara menangani upload file dengan Server Actions?

Saat form dengan input type="file" disubmit ke Server Action, file akan tersedia di objek FormData sebagai instance File. Anda kemudian bisa memproses file tersebut di server, misalnya menyimpannya ke disk atau mengunggahnya ke layanan penyimpanan cloud (seperti AWS S3 atau Cloudinary).

Apakah Server Actions kompatibel dengan database tertentu?

Server Actions agnostik terhadap jenis database atau ORM yang Anda gunakan. Karena mereka adalah kode server-side, Anda bisa menggunakan driver database atau ORM favorit Anda (Prisma, Drizzle, Sequelize, Knex.js, atau bahkan driver database langsung) sama seperti Anda akan menggunakannya di API Route atau aplikasi backend Node.js lainnya.

Kesimpulan

Server Actions adalah salah satu fitur paling revolusioner di Next.js App Router yang benar-benar mengubah cara kita berpikir tentang full-stack development. Dengan memungkinkan Anda menjalankan kode server-side langsung dari komponen client, Server Actions menyederhanakan arsitektur, meningkatkan developer experience, dan mengoptimalkan performa aplikasi Anda.

Meskipun ada kurva pembelajaran kecil, terutama dalam memahami konteks server dan client, manfaat yang ditawarkan Server Actions sangat besar. Saya sangat merekomendasikan setiap developer Next.js untuk menguasai fitur ini. Dengan pemahaman yang kuat tentang Server Actions, Anda dapat membangun aplikasi yang lebih ramping, lebih cepat, dan lebih mudah di-maintain, membawa pengalaman coding Anda ke level berikutnya.

Selamat mencoba dan semoga sukses dalam membangun aplikasi Next.js Anda!

TAGS: Next.js, Server Actions, React, Full-stack Development, App Router, Web Development, Data Mutation, Form Handling, Programming Tutorial


Baca Juga

You May Also Like

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *