# Next.js

> Kirim dari client component Next.js App Router, atau melalui server action dengan secret key.

Ada dua cara yang baik untuk mengirim dari Next.js. Kirim dari browser di client component, yang mempertahankan IP setiap pengunjung untuk rate limit dan berfungsi dengan semua pemeriksaan spam. Atau kirim dari server action, yang membuat formulir tetap berfungsi tanpa JavaScript.

|   | Client component | Server action |
| --- | --- | --- |
| Berfungsi tanpa JavaScript | Tidak | Ya |
| Rate limit dihitung per | Setiap pengunjung | IP server Anda |
| Pemeriksaan manusia Ikiform | Gunakan `challenge` dari SDK | Dilewati dengan secret key |
| Domain yang diizinkan | Dicocokkan dengan origin browser | Dilewati dengan secret key |
| Butuh secret key | Tidak | Ya |

## Pasang SDK

`Terminal`:

```bash
npm install @ikiform/sdk
```

SDK bersifat opsional. Semua yang di bawah juga berfungsi dengan `fetch`; lihat [JavaScript dan fetch](https://ikiform.com/id/docs/frameworks.md).

## Dari client component

`app/contact/contact-form.tsx`:

```tsx
"use client";

import { submit } from "@ikiform/sdk";
import { useState } from "react";

type Status = "idle" | "sending" | "sent" | "error";

export function ContactForm() {
  const [status, setStatus] = useState<Status>("idle");
  const [errors, setErrors] = useState<Record<string, string[]>>({});
  const [message, setMessage] = useState("");

  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setStatus("sending");
    const result = await submit("frm_xxxxxxxxxxxxxxxx", new FormData(event.currentTarget));
    if (result.ok) return setStatus("sent");
    setErrors(result.error.fields ?? {});
    setMessage(result.error.message);
    setStatus("error");
  }

  if (status === "sent") return <p>Thanks, we got it.</p>;

  return (
    <form onSubmit={onSubmit} noValidate>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required aria-invalid={Boolean(errors.email)} />
      {errors.email ? <p>{errors.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {status === "error" && !Object.keys(errors).length ? <p role="alert">{message}</p> : null}
      <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
    </form>
  );
}
```

- `submit` tidak pernah melempar error. Hasilnya `{ ok: true, id }` atau `{ ok: false, error, status, requestId }`.
- `error.fields` memetakan ID field ke pesan saat formulir punya [field](https://ikiform.com/id/docs/fields.md) yang didefinisikan.
- Render komponen dari halaman server component mana pun, misalnya `app/contact/page.tsx`.

### Dengan pemeriksaan manusia Ikiform

Jika pemeriksaan spam formulir adalah **Ikiform**, ambil token tepat sebelum setiap kiriman. `challenge` memasang frame kecil ikiform.com yang tetap tak terlihat kecuali Turnstile butuh klik.

`contact-form.tsx`:

```tsx
const data = new FormData(event.currentTarget);
const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx");
const result = await submit("frm_xxxxxxxxxxxxxxxx", data, { turnstileToken });
```

Impor dengan `import { challenge, submit } from "@ikiform/sdk";`. Token hanya sekali pakai, jadi panggil sekali per kiriman.

## Dari server action

Buat secret key di dashboard pada **API keys** dengan scope `submissions:write`, lalu simpan sebagai environment variable. Jangan pernah mengeksposnya ke browser: jangan beri awalan `NEXT_PUBLIC_`.

`.env.local`:

```bash
IKIFORM_SECRET_KEY=ik_sk_your_secret_key
```

`app/contact/actions.ts`:

```ts
"use server";

export interface ContactState {
  ok: boolean;
  message: string;
  fields?: Record<string, string[]>;
}

export async function sendContact(_previous: ContactState, formData: FormData): Promise<ContactState> {
  const body = new URLSearchParams();
  for (const [name, value] of formData) {
    if (typeof value === "string" && !name.startsWith("$ACTION")) body.append(name, value);
  }
  const response = await fetch("https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx", {
    method: "POST",
    headers: {
      Accept: "application/json",
      Authorization: `Bearer ${process.env.IKIFORM_SECRET_KEY}`,
    },
    body,
  });
  const result = await response.json();
  if (result.ok) return { ok: true, message: "Thanks, we got it." };
  return { ok: false, message: result.error.message, fields: result.error.fields };
}
```

`app/contact/contact-form.tsx`:

```tsx
"use client";

import { useActionState } from "react";
import { type ContactState, sendContact } from "./actions";

const initial: ContactState = { ok: false, message: "" };

export function ContactForm() {
  const [state, action, pending] = useActionState(sendContact, initial);

  if (state.ok) return <p>{state.message}</p>;

  return (
    <form action={action}>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />
      {state.fields?.email ? <p>{state.fields.email[0]}</p> : null}
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" />
      {state.message && !state.fields ? <p role="alert">{state.message}</p> : null}
      <button disabled={pending}>{pending ? "Sending" : "Send"}</button>
    </form>
  );
}
```

> Kiriman dari server Anda semuanya berasal dari alamat IP server Anda, jadi rate limit per pengunjung milik Ikiform menghitung semua pengunjung sekaligus. Preset Standard mengizinkan 5 kiriman per menit per IP dan formulir, yang bisa tercapai oleh situs yang ramai. Kirim dari browser jika bisa, atau pilih [rate limit](https://ikiform.com/id/docs/rate-limits.md) Relaxed atau Custom untuk formulir yang Anda proxy lewat server.

`submit` dari SDK juga bisa dipakai di server action, tetapi tidak mengirim header `Authorization`, jadi formulir dengan domain yang diizinkan atau pemeriksaan manusia Ikiform akan menolak atau menandai kirimannya. Gunakan `fetch` dengan secret key seperti contoh.

## Terima webhook di route handler

Untuk memproses setiap kiriman di aplikasi Next.js Anda, tambahkan route handler. Lihat [Menerima webhook](https://ikiform.com/id/docs/guides/webhook-handlers.md#nextjs).
