ID del formulario

Next.js

Envía desde un componente de cliente del App Router de Next.js, o desde una server action con una clave secreta.

Hay dos buenas formas de enviar desde Next.js. Envía desde el navegador en un componente de cliente, lo que conserva la IP de cada visitante para los límites de frecuencia y funciona con todas las verificaciones antispam. O envía desde una server action, lo que mantiene el formulario funcionando sin JavaScript.

ItemComponente de clienteServer action
Funciona sin JavaScriptNoSí
Los límites de frecuencia cuentanCada visitanteLa IP de tu servidor
Verificación humana de IkiformUsa challenge del SDKSe omite con una clave secreta
Dominios permitidosSe comparan con el origen del navegadorSe omite con una clave secreta
Necesita una clave secretaNoSí

Instala el SDK

Terminal
npm install @ikiform/sdk

El SDK es opcional. Todo lo de abajo también funciona con fetch; consulta JavaScript y fetch.

Desde un componente de cliente

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 lanza excepciones. Se resuelve como { ok: true, id } o { ok: false, error, status, requestId }.
  • error.fields relaciona los ids de campo con mensajes cuando el formulario tiene campos definidos.
  • Renderiza el componente desde cualquier página de server component, por ejemplo app/contact/page.tsx.

Con la verificación humana de Ikiform

Si la verificación antispam del formulario es Ikiform, obtén un token justo antes de cada envío. challenge monta un pequeño frame de ikiform.com que permanece invisible salvo que Turnstile necesite un clic.

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

Impórtalo con import { challenge, submit } from "@ikiform/sdk";. Los tokens son de un solo uso, así que llámalo una vez por envío.

Desde una server action

Crea una clave secreta en el panel, en API keys, con el permiso submissions:write, y guárdala como variable de entorno. Nunca la expongas al navegador: no le pongas el prefijo 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>
  );
}
Todos los envíos que salen de tu servidor vienen de la dirección IP de tu servidor, así que los límites de frecuencia por visitante de Ikiform cuentan a todos los visitantes juntos. El preset Standard permite 5 envíos por minuto por IP y formulario, algo que un sitio con mucho tráfico puede alcanzar. Envía desde el navegador cuando puedas, o elige el límite de frecuencia Relaxed o Custom para los formularios que pasan por un servidor.

El submit del SDK también se puede usar en una server action, pero no envía la cabecera Authorization, así que un formulario con dominios permitidos o con la verificación humana de Ikiform rechazaría o marcaría el envío. Usa fetch con la clave secreta como se muestra.

Recibe webhooks en un route handler

Para actuar sobre cada envío en tu app de Next.js, añade un route handler. Consulta Recibir webhooks.