Lewati ke konten
Tutorial

Form Kontak Next.js pakai Server Actions

M. Irfan Ramadhan4 menit baca
Balok kayu bertuliskan ikon amplop, telepon, dan lokasi, melambangkan kanal kontak.
Foto: Unsplash
Daftar Isi

Form kontak di Next.js App Router dibuat dengan memanggil Server Action ("use server") langsung dari handler submit form, bukan lewat endpoint API terpisah. Data divalidasi ulang dengan skema yang sama di server (misalnya zod), dilengkapi honeypot anti-spam dan rate limiting sederhana, sebelum email benar-benar dikirim lewat layanan seperti Resend.

Ringkasan cepat:

  1. Server Action ditandai "use server" dan dipanggil langsung dari form, tanpa perlu app/api/.../route.ts terpisah untuk kasus paling umum.
  2. Validasi HARUS diulang di server dengan skema yang sama seperti client (mis. zod), karena Server Action pada dasarnya endpoint HTTP publik.
  3. Honeypot field tersembunyi lewat CSS adalah cara sederhana menyaring bot tanpa CAPTCHA yang mengganggu pengguna asli.
  4. useActionState adalah pola paling umum untuk progressive enhancement, tapi bukan satu-satunya cara memanggil Server Action dari form.
  5. Pengiriman email sebaiknya lewat layanan pihak ketiga (mis. Resend) dari dalam Server Action, bukan SMTP manual di server sendiri.

Apa Itu Server Actions untuk Form Kontak?

Server Actions adalah fungsi asinkron yang berjalan sepenuhnya di server, ditandai directive "use server", dan bisa dipanggil langsung dari komponen React tanpa membuat endpoint API terpisah untuk setiap aksi. Konsep dasarnya sudah dibahas di panduan Server Actions Next.js untuk pemula; artikel ini fokus pada penerapannya khusus untuk form kontak, termasuk validasi ulang, anti-spam, dan pengiriman email.

Pola paling umum menggabungkan Server Action dengan hook useActionState React, yang menyatukan state hasil, status pending, dan argumen sebelumnya dalam satu hook yang terhubung ke atribut action elemen <form>.

"useActionState is a React Hook that lets you update state with side effects using Actions." (React documentation, useActionState)

Keuntungan pola <form action={...}> plus useActionState adalah progressive enhancement murni: form tetap bisa disubmit dan diproses server meski JavaScript belum selesai dimuat di browser pengguna, sama seperti perilaku dasar HTML form.

Kenapa Server Action Dianggap Endpoint Publik?

Menurut dokumentasi resmi Next.js soal keamanan data, setiap Server Action yang diekspor otomatis menjadi endpoint HTTP publik begitu referensinya ada di bundle client, dan harus diperlakukan dengan asumsi keamanan yang sama seperti endpoint API biasa — termasuk validasi ulang input dan pengecekan otorisasi di dalam fungsi itu sendiri, bukan hanya mengandalkan validasi di sisi client.

Bagaimana Cara Membuat Server Action untuk Form Kontak?

Buat file terpisah dengan directive "use server" di baris pertama, lalu ekspor fungsi async yang menerima data form dan mengembalikan hasil sukses/gagal:

// app/kontak/actions.ts
"use server";
 
import { z } from "zod";
 
const contactFormSchema = z.object({
  name: z.string().trim().min(2, "Nama minimal 2 karakter."),
  email: z.string().trim().email("Format email tidak valid."),
  subject: z.string().trim().min(3, "Subjek minimal 3 karakter."),
  message: z.string().trim().min(10, "Pesan minimal 10 karakter."),
});
 
export interface ContactActionResult {
  success: boolean;
  message: string;
}
 
export async function submitContactForm(
  payload: unknown
): Promise<ContactActionResult> {
  // Server tidak pernah percaya data dari client — validasi ulang di sini
  // dengan skema yang sama seperti di form.
  const parsed = contactFormSchema.safeParse(payload);
 
  if (!parsed.success) {
    return {
      success: false,
      message: "Data yang dikirim tidak valid. Periksa kembali isian form.",
    };
  }
 
  // Kirim email di sini (lihat bagian pengiriman email di bawah)...
 
  return { success: true, message: "Pesan berhasil dikirim." };
}

Skema zod di atas sebaiknya diletakkan di modul terpisah yang di-import bersama oleh client (react-hook-form/useActionState) dan Server Action, supaya aturan validasi tidak pernah tidak sinkron antara dua sisi.

Kenapa Perlu Validasi Ulang di Server, Bukan Cukup di Client?

Validasi client (react-hook-form dengan resolver zod, atau atribut HTML required) hanya mempercepat feedback ke pengguna asli sebelum request terkirim. Karena Server Action pada dasarnya endpoint HTTP publik (lihat bagian sebelumnya), siapa pun yang tahu referensinya bisa memanggilnya langsung dengan payload sembarang, melewati form dan validasi client sama sekali — jadi validasi di dalam Server Action itu sendiri adalah lapisan yang benar-benar menentukan data mana yang diproses lebih lanjut.

Bagaimana Cara Menangani Spam Tanpa CAPTCHA?

Pola honeypot menambahkan satu field ekstra ke form yang disembunyikan secara visual lewat CSS (posisi absolut di luar viewport, bukan display:none yang bisa membuat sebagian screen reader ikut mengabaikannya) sehingga hanya bot pengisi form otomatis yang biasanya mengisinya:

const { name, email, subject, message, website } = parsed.data;
 
// Honeypot: field ini seharusnya selalu kosong untuk pengguna asli.
if (website) {
  // Balas sukses palsu tanpa mengirim email, supaya bot tidak tahu
  // ia terdeteksi.
  return { success: true, message: "Pesan berhasil dikirim." };
}

Tambahkan juga rate limiting sederhana (mis. membatasi jumlah submission per rentang waktu tertentu dari satu proses server) untuk menahan percobaan spam beruntun, meski untuk trafik tinggi lintas banyak instance server, penyimpanan bersama seperti Redis lebih tahan lama dibanding variabel in-memory yang hilang tiap restart.

Kapan Sebaiknya Pakai useActionState, dan Kapan Tidak?

useActionState lewat atribut action pada <form> adalah pola yang paling direkomendasikan karena progressive enhancement: form tetap terkirim dan diproses server walau JavaScript belum termuat di browser. Tapi Server Action tetap berupa fungsi async biasa, jadi bisa juga dipanggil manual di dalam handler submit library form pihak ketiga (mis. react-hook-form) ketika proyek butuh kontrol validasi client dan UI status yang lebih detail (pesan error per field yang kompleks, toast notification kustom) dibanding yang disediakan useActionState secara bawaan — dengan konsekuensi form tidak lagi berfungsi tanpa JavaScript, karena submit-nya sepenuhnya bergantung pada event handler client.

Trade-off ini bukan salah satu benar dan satunya salah, murni soal prioritas: progressive enhancement penuh versus kontrol UI form yang lebih detail dengan bantuan library form.

Bagaimana Cara Mengirim Email dari Server Action?

Setelah validasi dan pengecekan honeypot lolos, panggil layanan pengirim email pihak ketiga (mis. Resend) langsung di dalam Server Action:

import { Resend } from "resend";
 
const resend = new Resend(process.env.RESEND_API_KEY);
 
const { error } = await resend.emails.send({
  from: "[email protected]",
  to: process.env.CONTACT_TO_EMAIL!,
  replyTo: email,
  subject: `[Kontak Website] ${subject}`,
  text: `Nama: ${name}\nEmail: ${email}\n\nPesan:\n${message}`,
});
 
if (error) {
  return { success: false, message: "Gagal mengirim pesan." };
}

API key disimpan sebagai environment variable server-side, tidak pernah dikirim ke bundle client karena seluruh kode Server Action memang hanya dieksekusi di server. Selama pengembangan lokal sebelum kredensial layanan email diset, Server Action bisa mengembalikan sukses sambil mencatat payload ke log server saja, supaya alur UI tetap bisa diuji tanpa kredensial nyata.

Ringkasan

  • Server Action ditandai "use server" dipanggil langsung dari form, tanpa perlu app/api/.../route.ts terpisah untuk kasus paling umum.
  • Validasi HARUS diulang di server dengan skema yang sama (mis. zod), karena Server Action pada dasarnya endpoint HTTP publik.
  • Honeypot field tersembunyi lewat CSS adalah cara sederhana menyaring bot tanpa mengganggu pengguna asli dengan CAPTCHA.
  • useActionState cocok untuk progressive enhancement penuh; memanggil Server Action manual dari library form pihak ketiga cocok kalau butuh kontrol UI validasi yang lebih detail, dengan trade-off form bergantung penuh pada JavaScript.
  • Konsep dasar Server Actions (bukan khusus form kontak) dibahas lebih lengkap di panduan Server Actions Next.js untuk pemula.
  • Butuh bantuan implementasi form kontak atau fitur web lain di situs sendiri? Lihat layanan web development yang ditawarkan.

server actions · form kontak · nextjs · app router · resend

Pertanyaan yang Sering Diajukan

Apakah form kontak dengan Server Actions butuh Route Handler API terpisah?

Tidak wajib. Server Action ditandai "use server" bisa dipanggil langsung dari komponen form tanpa membuat endpoint app/api/.../route.ts terpisah. Route Handler tetap relevan kalau form itu juga perlu diakses pihak ketiga (mis. webhook eksternal), bukan cuma dipanggil dari form situs sendiri.

Kenapa validasi form kontak harus diulang di server, bukan cukup di client saja?

Karena Server Action pada dasarnya adalah endpoint HTTP publik yang bisa dipanggil siapa saja yang tahu referensinya, tidak cuma lewat form di halaman itu. Validasi client (react-hook-form, HTML required) hanya mempercepat feedback ke pengguna asli; validasi server dengan skema yang sama (mis. zod) adalah lapisan yang benar-benar menentukan apakah data diproses lebih lanjut.

Bagaimana cara mencegah spam di form kontak tanpa CAPTCHA?

Pola honeypot: tambahkan satu field tersembunyi secara visual lewat CSS (posisi absolut di luar viewport, bukan display:none) yang seharusnya selalu kosong untuk pengguna asli. Server Action mengecek field itu setelah validasi utama lolos; kalau terisi, balas dengan pesan sukses palsu tanpa benar-benar mengirim email, supaya bot tidak tahu ia terdeteksi.

Apakah useActionState wajib dipakai untuk form kontak dengan Server Actions?

Tidak wajib. useActionState adalah pola paling umum karena progressive enhancement (form tetap terkirim walau JavaScript belum termuat), tapi Server Action juga bisa dipanggil manual di dalam handler submit library form seperti react-hook-form ketika butuh validasi client dan kontrol UI yang lebih detail, dengan konsekuensi form tidak berfungsi tanpa JavaScript.

M. Irfan Ramadhan

SEO Specialist & Web Developer

Menulis tentang SEO teknis, GEO/AI search, dan pengembangan web modern berdasarkan pengalaman langsung mengerjakan proyek klien.