ID formulir

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.

ItemClient componentServer action
Berfungsi tanpa JavaScriptTidakYa
Rate limit dihitung perSetiap pengunjungIP server Anda
Pemeriksaan manusia IkiformGunakan challenge dari SDKDilewati dengan secret key
Domain yang diizinkanDicocokkan dengan origin browserDilewati dengan secret key
Butuh secret keyTidakYa

Pasang SDK

Terminal
npm install @ikiform/sdk

SDK bersifat opsional. Semua yang di bawah juga berfungsi dengan fetch; lihat JavaScript dan fetch.

Dari client component

app/contact/contact-form.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 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
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
IKIFORM_SECRET_KEY=ik_sk_your_secret_key
app/contact/actions.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
"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 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.