Belajar Nuxt - Forms & Validation
Series/Belajar Nuxt/Episode 10
Episode 10 of 24

Belajar Nuxt - Forms & Validation

Episode ini membahas pembangunan form di Nuxt: pengikatan data dengan v-model, validasi client-side memakai VeeValidate, pemrosesan form di server dengan server actions, serta aspek UX form seperti pesan error, state loading, dan accessibility.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Form adalah jembatan antara aplikasi dan pengguna — tapi juga sumber error paling banyak bila dikerjakan asal-asalan. Episode 10 membahas cara membangun form yang benar di Nuxt: dari pengikatan data, validasi di client, pemrosesan di server, sampai pengalaman pengguna saat terjadi error atau proses yang lama.

Pendekatan kita: validasi di client untuk feedback cepat, dan validasi ulang di server untuk keamanan. Dua lapis ini tidak bisa ditawar — form tanpa validasi server berbahaya, dan form tanpa validasi client terasa lambat dan membingungkan.

Client-Side Forms dengan v-model

Pengikatan Data Form

Mulai dari yang paling dasar: mengikat input ke state reaktif dengan v-model:

HTMLForm dasar dengan v-model
<script setup lang="ts">
const form = ref({
  nama: "",
  email: "",
  alamat: "",
})
</script>
 
<template>
  <form>
    <input v-model="form.nama" type="text" placeholder="Nama lengkap" />
    <input v-model="form.email" type="email" placeholder="Email" />
    <textarea v-model="form.alamat" placeholder="Alamat"></textarea>
  </form>
</template>

v-model="form.nama" membuat dua arah: saat pengguna mengetik, state berubah; saat state berubah dari kode, input ikut tampil. Ini pola dasar seluruh form Nuxt.

Validasi dengan VeeValidate

Setup VeeValidate

Install VeeValidate dan adapter validasi yang diinginkan:

Install VeeValidate
npm install vee-validate @vee-validate/zod zod

VeeValidate menyediakan useForm dan useField untuk membungkus input dengan validasi:

HTMLForm dengan validasi
<script setup lang="ts">
import { useForm, useField } from "vee-validate"
import { toTypedSchema } from "@vee-validate/zod"
import { z } from "zod"
 
const schema = toTypedSchema(
  z.object({
    nama: z.string().min(3, "Nama minimal 3 karakter"),
    email: z.string().email("Format email tidak valid"),
  })
)
 
const { handleSubmit, errors } = useForm({ validationSchema: schema })
const { value: nama } = useField("nama")
const { value: email } = useField("email")
 
const onSubmit = handleSubmit(async (values) => {
  console.log("Data valid:", values)
})
</script>
 
<template>
  <form @submit.prevent="onSubmit">
    <input v-model="nama" type="text" placeholder="Nama" />
    <p v-if="errors.nama">{{ errors.nama }}</p>
    <input v-model="email" type="email" placeholder="Email" />
    <p v-if="errors.email">{{ errors.email }}</p>
    <button type="submit">Kirim</button>
  </form>
</template>

useForm({ validationSchema: schema }) menghubungkan skema Zod ke form. Setiap useField membungkus satu field, dan errors menampung pesan validasi yang siap dirender. handleSubmit hanya menjalankan handler jika seluruh form valid.

Validasi Pesan yang Ramah

Buat pesan error yang menjelaskan apa yang salah dan cara memperbaikinya. Kalimat seperti "Format email tidak valid" jauh lebih membantu daripada "error" generik.

Server-Side Form Processing dengan Server Actions

Proses di Server

Validasi client hanya untuk kenyamanan. Kebenaran final harus di server — server action kita dari episode 6 diterapkan penuh di sini:

JSserver/utils/pesanan.ts
import { z } from "zod"
 
const skemaPesanan = z.object({
  nama: z.string().min(3),
  email: z.string().email(),
  alamat: z.string().min(10),
})
 
export const buatPesanan = defineServerAction(
  async (input: unknown) => {
    const validasi = skemaPesanan.safeParse(input)
    if (!validasi.success) {
      throw createError({
        statusCode: 400,
        message: "Data pesanan tidak valid",
      })
    }
    const id = await simpanPesanan(validasi.data)
    return { id }
  }
)

skemaPesanan.safeParse(input) memvalidasi ulang data di server dengan skema yang sama. createError({ statusCode: 400, ... }) mengembalikan error HTTP yang bisa ditangkap client.

Panggil dari Komponen

JSSubmit dengan server action
const { handleSubmit, errors, isSubmitting } = useForm({
  validationSchema: schema,
})
 
const onSubmit = handleSubmit(async (values) => {
  try {
    const hasil = await buatPesanan(values)
    await navigateTo(`/pesanan/${hasil.id}`)
  } catch (e) {
    console.error("Pesanan gagal", e)
  }
})

Alur lengkapnya: validasi di client untuk feedback instan, server action memvalidasi lagi dan menyimpan, lalu redirect ke halaman sukses. isSubmitting dari VeeValidate bisa dipakai untuk menampilkan loading.

UX Forms: Errors, Loading, dan Accessibility

Error yang Jelas dan Loading State

Tampilkan error di tempat yang mudah ditemukan, dan beri umpan balik saat form sedang diproses:

HTMLUX form lengkap
<template>
  <form @submit.prevent="onSubmit">
    <label for="email">Email</label>
    <input id="email" v-model="email" type="email" :disabled="isSubmitting" />
    <p v-if="errors.email" role="alert">{{ errors.email }}</p>
 
    <button type="submit" :disabled="isSubmitting">
      {{ isSubmitting ? "Mengirim..." : "Buat Pesanan" }}
    </button>
  </form>
</template>

role="alert" membuat pesan error dibacakan screen reader saat muncul. :disabled="isSubmitting" mencegah submit ganda yang sering menyebabkan pesanan dobel.

Accessibility Form

Beberapa prinsip yang wajib: setiap input punya <label> yang terhubung lewat atribut id, fokus keyboard berpindah dengan tab yang logis, dan pesan error terhubung ke field-nya. Episode 17 akan membahas accessibility secara menyeluruh.

Penutup

Episode 10 membuat form kalian berfungsi penuh dan aman: v-model untuk pengikatan data, VeeValidate dengan Zod untuk validasi client, server actions untuk pemrosesan yang divalidasi ulang di server, serta UX yang memperhatikan error, loading, dan accessibility.

Inti yang harus dibawa pulang:

  • v-model menghubungkan input form dengan state reaktif dua arah.
  • VeeValidate dengan Zod memberikan validasi client yang terskema.
  • Validasi server wajib ada; client validation hanya untuk kenyamanan.
  • Gunakan isSubmitting untuk menonaktifkan tombol dan mencegah submit ganda.
  • Hubungkan label ke input dan gunakan role="alert" untuk pesan error.

Di episode 11 selanjutnya kita akan membahas content dan CMS integration — integrasi konten statis dengan @nuxt/content, sourcing konten dari Markdown, MDX, dan headless CMS, membangun halaman berbasis konten dengan pencarian, serta preview mode untuk alur kerja editorial. Blog toko kalian siap berdiri.

Belajar Nuxt - Forms & Validation | Belajar Nuxt