ID do formulário

Next.js

Envie a partir de um client component do App Router do Next.js, ou por uma server action com uma chave secreta.

Existem duas boas formas de enviar a partir do Next.js. Envie do navegador em um client component, que mantém o IP de cada visitante para os limites de taxa e funciona com todas as verificações de spam. Ou envie de uma server action, que mantém o formulário funcionando sem JavaScript.

ItemClient componentServer action
Funciona sem JavaScriptNãoSim
Os limites de taxa contamCada visitanteO IP do seu servidor
Verificação humana do IkiformUse challenge do SDKIgnorada com uma chave secreta
Domínios permitidosComparados à origem do navegadorIgnorada com uma chave secreta
Exigem uma chave secretaNãoSim

Instale o SDK

Terminal
npm install @ikiform/sdk

O SDK é opcional. Tudo abaixo também funciona com fetch; veja JavaScript e fetch.

A partir de um 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 nunca lança exceção. Ele resolve para { ok: true, id } ou { ok: false, error, status, requestId }.
  • error.fields associa ids de campo a mensagens quando o formulário tem campos definidos.
  • Renderize o componente a partir de qualquer página server component, por exemplo app/contact/page.tsx.

Com a verificação humana do Ikiform

Se a verificação de spam do formulário for Ikiform, obtenha um token logo antes de cada envio. challenge monta um pequeno frame de ikiform.com que fica invisível, a menos que o Turnstile precise de um clique.

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

Importe com import { challenge, submit } from "@ikiform/sdk";. Os tokens são de uso único, então chame uma vez por envio.

A partir de uma server action

Crie uma chave secreta no painel em API keys com o escopo submissions:write e guarde-a como variável de ambiente. Nunca a exponha ao navegador: não use o prefixo 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>
  );
}
Envios feitos pelo seu servidor vêm todos do endereço IP do seu servidor, então os limites de taxa por visitante do Ikiform contam todos os visitantes juntos. O preset Standard permite 5 envios por minuto por IP e formulário, o que um site movimentado pode atingir. Envie do navegador quando puder, ou escolha o limite de taxa Relaxed ou Custom para formulários que passam por um servidor.

O submit do SDK também pode ser usado em uma server action, mas ele não envia um header Authorization, então um formulário com domínios permitidos ou com a verificação humana do Ikiform rejeitaria ou sinalizaria o envio. Use fetch com a chave secreta, como mostrado.

Receba webhooks em um route handler

Para agir sobre cada envio no seu app Next.js, adicione um route handler. Veja Recebendo webhooks.