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.
| Item | Componente de cliente | Server action |
|---|---|---|
| Funciona sin JavaScript | No | Sí |
| Los límites de frecuencia cuentan | Cada visitante | La IP de tu servidor |
| Verificación humana de Ikiform | Usa challenge del SDK | Se omite con una clave secreta |
| Dominios permitidos | Se comparan con el origen del navegador | Se omite con una clave secreta |
| Necesita una clave secreta | No | Sí |
Instala el SDK
npm install @ikiform/sdkEl SDK es opcional. Todo lo de abajo también funciona con fetch; consulta JavaScript y fetch.
Desde un componente de cliente
"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>
);
}submitnunca lanza excepciones. Se resuelve como{ ok: true, id }o{ ok: false, error, status, requestId }.error.fieldsrelaciona 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.
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_.
IKIFORM_SECRET_KEY=ik_sk_your_secret_key"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 };
}"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>
);
}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.