Panduan komprehensif untuk Server Actions Next.js 14, mencakup praktik terbaik penanganan formulir, validasi data, pertimbangan keamanan, dan teknik canggih untuk membangun aplikasi web modern.
Server Actions Next.js 14: Menguasai Praktik Terbaik Penanganan Formulir
Next.js 14 memperkenalkan fitur-fitur canggih untuk membangun aplikasi web yang berkinerja tinggi dan ramah pengguna. Di antaranya, Server Actions menonjol sebagai cara transformatif untuk menangani pengiriman formulir dan mutasi data langsung di server. Panduan ini memberikan gambaran komprehensif tentang Server Actions di Next.js 14, dengan fokus pada praktik terbaik untuk penanganan formulir, validasi data, keamanan, dan teknik-teknik canggih. Kami akan menjelajahi contoh-contoh praktis dan memberikan wawasan yang dapat ditindaklanjuti untuk membantu Anda membangun aplikasi web yang tangguh dan skalabel.
Apa itu Server Actions Next.js?
Server Actions adalah fungsi asinkron yang berjalan di server dan dapat dipanggil langsung dari komponen React. Mereka menghilangkan kebutuhan akan rute API tradisional untuk menangani pengiriman formulir dan mutasi data, menghasilkan kode yang lebih sederhana, keamanan yang lebih baik, dan performa yang ditingkatkan. Server Actions adalah React Server Components (RSC), yang berarti mereka dieksekusi di server, menghasilkan waktu muat halaman awal yang lebih cepat dan SEO yang lebih baik.
Manfaat Utama Server Actions:
- Kode yang Disederhanakan: Mengurangi kode boilerplate dengan menghilangkan kebutuhan akan rute API terpisah.
- Keamanan yang Ditingkatkan: Eksekusi di sisi server meminimalkan kerentanan di sisi klien.
- Performa yang Ditingkatkan: Jalankan mutasi data langsung di server untuk waktu respons yang lebih cepat.
- SEO yang Dioptimalkan: Manfaatkan server-side rendering untuk pengindeksan mesin pencari yang lebih baik.
- Keamanan Tipe (Type Safety): Dapatkan manfaat dari keamanan tipe end-to-end dengan TypeScript.
Menyiapkan Proyek Next.js 14 Anda
Sebelum mendalami Server Actions, pastikan Anda telah menyiapkan proyek Next.js 14. Jika Anda memulai dari awal, buat proyek baru menggunakan perintah berikut:
npx create-next-app@latest my-next-app
Pastikan proyek Anda menggunakan struktur direktori app
untuk memanfaatkan sepenuhnya Server Components dan Actions.
Penanganan Formulir Dasar dengan Server Actions
Mari kita mulai dengan contoh sederhana: sebuah formulir yang mengirimkan data untuk membuat item baru di database. Kita akan menggunakan formulir sederhana dengan satu kolom input dan tombol kirim.
Contoh: Membuat Item Baru
Pertama, definisikan fungsi Server Action di dalam komponen React Anda. Fungsi ini akan menangani logika pengiriman formulir di server.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Simulasi interaksi database
console.log('Membuat item:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulasi latensi
console.log('Item berhasil dibuat!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
await createItem(formData);
setIsSubmitting(false);
}
return (
);
}
Penjelasan:
- Direktif
'use client'
menunjukkan bahwa ini adalah komponen klien. - Fungsi
createItem
ditandai dengan direktif'use server'
, yang menunjukkan bahwa itu adalah Server Action. - Fungsi
handleSubmit
adalah fungsi sisi klien yang memanggil server action. Ini juga menangani status UI seperti menonaktifkan tombol selama pengiriman. - Properti
action
dari elemen<form>
diatur ke fungsihandleSubmit
. - Metode
formData.get('name')
mengambil nilai dari kolom input 'name'. await new Promise
menyimulasikan operasi database dan menambahkan latensi.
Validasi Data
Validasi data sangat penting untuk memastikan integritas data dan mencegah kerentanan keamanan. Server Actions memberikan kesempatan yang sangat baik untuk melakukan validasi di sisi server. Pendekatan ini membantu mengurangi risiko yang terkait dengan validasi di sisi klien saja.
Contoh: Memvalidasi Data Input
Modifikasi Server Action createItem
untuk menyertakan logika validasi.
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
if (!name || name.length < 3) {
throw new Error('Nama item harus memiliki panjang minimal 3 karakter.');
}
// Simulasi interaksi database
console.log('Membuat item:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulasi latensi
console.log('Item berhasil dibuat!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Terjadi kesalahan.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Penjelasan:
- Fungsi
createItem
sekarang memeriksa apakahname
valid (panjangnya minimal 3 karakter). - Jika validasi gagal, sebuah error dilemparkan.
- Fungsi
handleSubmit
diperbarui untuk menangkap setiap error yang dilemparkan oleh Server Action dan menampilkan pesan kesalahan kepada pengguna.
Menggunakan Pustaka Validasi
Untuk skenario validasi yang lebih kompleks, pertimbangkan untuk menggunakan pustaka validasi seperti:
- Zod: Pustaka deklarasi dan validasi skema yang mengutamakan TypeScript.
- Yup: Pembangun skema JavaScript untuk mem-parsing, memvalidasi, dan mengubah nilai.
Berikut adalah contoh menggunakan Zod:
// app/utils/validation.ts
import { z } from 'zod';
export const CreateItemSchema = z.object({
name: z.string().min(3, 'Nama item harus memiliki panjang minimal 3 karakter.'),
});
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
const validatedFields = CreateItemSchema.safeParse({ name });
if (!validatedFields.success) {
return { errors: validatedFields.error.flatten().fieldErrors };
}
// Simulasi interaksi database
console.log('Membuat item:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulasi latensi
console.log('Item berhasil dibuat!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Terjadi kesalahan.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Penjelasan:
CreateItemSchema
mendefinisikan aturan validasi untuk kolomname
menggunakan Zod.- Metode
safeParse
mencoba memvalidasi data input. Jika validasi gagal, ia mengembalikan objek dengan error. - Objek
errors
berisi informasi terperinci tentang kegagalan validasi.
Pertimbangan Keamanan
Server Actions meningkatkan keamanan dengan mengeksekusi kode di server, tetapi tetap penting untuk mengikuti praktik terbaik keamanan untuk melindungi aplikasi Anda dari ancaman umum.
Mencegah Cross-Site Request Forgery (CSRF)
Serangan CSRF mengeksploitasi kepercayaan yang dimiliki sebuah situs web terhadap browser pengguna. Untuk mencegah serangan CSRF, terapkan mekanisme perlindungan CSRF.
Next.js secara otomatis menangani perlindungan CSRF saat menggunakan Server Actions. Kerangka kerja ini menghasilkan dan memvalidasi token CSRF untuk setiap pengiriman formulir, memastikan bahwa permintaan berasal dari aplikasi Anda.
Menangani Otentikasi dan Otorisasi Pengguna
Pastikan hanya pengguna yang berwenang yang dapat melakukan tindakan tertentu. Terapkan mekanisme otentikasi dan otorisasi untuk melindungi data dan fungsionalitas sensitif.
Berikut adalah contoh menggunakan NextAuth.js untuk melindungi Server Action:
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';
async function createItem(formData: FormData) {
'use server'
const session = await getServerSession(authOptions);
if (!session) {
throw new Error('Tidak Sah');
}
const name = formData.get('name') as string;
// Simulasi interaksi database
console.log('Membuat item:', name, 'oleh pengguna:', session.user?.email);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulasi latensi
console.log('Item berhasil dibuat!');
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Terjadi kesalahan.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Penjelasan:
- Fungsi
getServerSession
mengambil informasi sesi pengguna. - Jika pengguna tidak diautentikasi (tidak ada sesi), sebuah error dilemparkan, mencegah Server Action dieksekusi.
Membersihkan Data Input
Bersihkan data input untuk mencegah serangan Cross-Site Scripting (XSS). Serangan XSS terjadi ketika kode berbahaya disuntikkan ke dalam situs web, yang berpotensi membahayakan data pengguna atau fungsionalitas aplikasi.
Gunakan pustaka seperti DOMPurify
atau sanitize-html
untuk membersihkan input yang diberikan pengguna sebelum memprosesnya di Server Actions Anda.
Teknik Tingkat Lanjut
Setelah kita membahas dasar-dasarnya, mari kita jelajahi beberapa teknik canggih untuk menggunakan Server Actions secara efektif.
Pembaruan Optimistis
Pembaruan optimistis memberikan pengalaman pengguna yang lebih baik dengan segera memperbarui UI seolah-olah tindakan akan berhasil, bahkan sebelum server mengonfirmasinya. Jika tindakan gagal di server, UI akan dikembalikan ke keadaan sebelumnya.
// app/components/UpdateItemForm.tsx
'use client';
import { useState } from 'react';
async function updateItem(id: string, formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Simulasi interaksi database
console.log('Memperbarui item:', id, 'dengan nama:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulasi latensi
// Simulasi kegagalan (untuk tujuan demonstrasi)
const shouldFail = Math.random() < 0.5;
if (shouldFail) {
throw new Error('Gagal memperbarui item.');
}
console.log('Item berhasil diperbarui!');
return { name }; // Kembalikan nama yang diperbarui
}
export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [itemName, setItemName] = useState(initialName);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
// Perbarui UI secara optimistis
const newName = formData.get('name') as string;
setItemName(newName);
try {
const result = await updateItem(id, formData);
//Jika berhasil maka pembaruan sudah tercermin di UI melalui setItemName
} catch (error: any) {
setErrorMessage(error.message || 'Terjadi kesalahan.');
// Kembalikan UI jika terjadi kesalahan
setItemName(initialName);
} finally {
setIsSubmitting(false);
}
}
return (
Nama Saat Ini: {itemName}
{errorMessage && {errorMessage}
}
);
}
Penjelasan:
- Sebelum memanggil Server Action, UI segera diperbarui dengan nama item baru menggunakan
setItemName
. - Jika Server Action gagal, UI dikembalikan ke nama item asli.
Merevalidasi Data
Setelah Server Action memodifikasi data, Anda mungkin perlu merevalidasi data yang di-cache untuk memastikan bahwa UI mencerminkan perubahan terbaru. Next.js menyediakan beberapa cara untuk merevalidasi data:
- Revalidate Path: Merevalidasi cache untuk path tertentu.
- Revalidate Tag: Merevalidasi cache untuk data yang terkait dengan tag tertentu.
Berikut adalah contoh merevalidasi path setelah membuat item baru:
// app/components/CreateItemForm.tsx
'use client';
import { useState } from 'react';
import { revalidatePath } from 'next/cache';
async function createItem(formData: FormData) {
'use server'
const name = formData.get('name') as string;
// Simulasi interaksi database
console.log('Membuat item:', name);
await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulasi latensi
console.log('Item berhasil dibuat!');
revalidatePath('/items'); // Revalidasi path /items
}
export default function CreateItemForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
async function handleSubmit(formData: FormData) {
setIsSubmitting(true);
setErrorMessage(null);
try {
await createItem(formData);
} catch (error: any) {
setErrorMessage(error.message || 'Terjadi kesalahan.');
} finally {
setIsSubmitting(false);
}
}
return (
{errorMessage && {errorMessage}
}
);
}
Penjelasan:
- Fungsi
revalidatePath('/items')
membatalkan validasi cache untuk path/items
, memastikan bahwa permintaan berikutnya ke path tersebut mengambil data terbaru.
Praktik Terbaik untuk Server Actions
Untuk memaksimalkan manfaat Server Actions, pertimbangkan praktik terbaik berikut:
- Jaga Server Actions Tetap Kecil dan Terfokus: Server Actions harus melakukan satu tugas yang terdefinisi dengan baik. Hindari logika yang kompleks di dalam Server Actions untuk menjaga keterbacaan dan kemudahan pengujian.
- Gunakan Nama yang Deskriptif: Beri nama yang deskriptif pada Server Actions Anda yang dengan jelas menunjukkan tujuannya.
- Tangani Kesalahan dengan Baik: Terapkan penanganan kesalahan yang kuat untuk memberikan umpan balik yang informatif kepada pengguna dan mencegah aplikasi mogok.
- Validasi Data secara Menyeluruh: Lakukan validasi data yang komprehensif untuk memastikan integritas data dan mencegah kerentanan keamanan.
- Amankan Server Actions Anda: Terapkan mekanisme otentikasi dan otorisasi untuk melindungi data dan fungsionalitas sensitif.
- Optimalkan Performa: Pantau kinerja Server Actions Anda dan optimalkan sesuai kebutuhan untuk memastikan waktu respons yang cepat.
- Manfaatkan Caching secara Efektif: Manfaatkan mekanisme caching Next.js untuk meningkatkan kinerja dan mengurangi beban database.
Kesalahan Umum dan Cara Menghindarinya
Meskipun Server Actions menawarkan banyak keuntungan, ada beberapa kesalahan umum yang perlu diwaspadai:
- Server Actions yang Terlalu Kompleks: Hindari menempatkan terlalu banyak logika di dalam satu Server Action. Pecah tugas-tugas kompleks menjadi fungsi-fungsi yang lebih kecil dan lebih mudah dikelola.
- Mengabaikan Penanganan Kesalahan: Selalu sertakan penanganan kesalahan untuk menangkap kesalahan tak terduga dan memberikan umpan balik yang membantu kepada pengguna.
- Mengabaikan Praktik Terbaik Keamanan: Ikuti praktik terbaik keamanan untuk melindungi aplikasi Anda dari ancaman umum seperti XSS dan CSRF.
- Lupa Merevalidasi Data: Pastikan Anda merevalidasi data yang di-cache setelah Server Action memodifikasi data untuk menjaga UI tetap terbaru.
Kesimpulan
Server Actions Next.js 14 menyediakan cara yang kuat dan efisien untuk menangani pengiriman formulir dan mutasi data langsung di server. Dengan mengikuti praktik terbaik yang diuraikan dalam panduan ini, Anda dapat membangun aplikasi web yang tangguh, aman, dan berkinerja tinggi. Manfaatkan Server Actions untuk menyederhanakan kode Anda, meningkatkan keamanan, dan meningkatkan pengalaman pengguna secara keseluruhan. Saat Anda mengintegrasikan prinsip-prinsip ini, pertimbangkan dampak global dari pilihan pengembangan Anda. Pastikan formulir dan proses penanganan data Anda dapat diakses, aman, dan ramah pengguna untuk audiens internasional yang beragam. Komitmen terhadap inklusivitas ini tidak hanya akan meningkatkan kegunaan aplikasi Anda tetapi juga memperluas jangkauan dan efektivitasnya dalam skala global.