# Next.js

> Envoyez depuis un composant client de l'App Router Next.js, ou via une server action avec une clé secrète.

Il y a deux bonnes façons d'envoyer depuis Next.js. Envoyez depuis le navigateur dans un composant client : chaque visiteur garde sa propre IP pour les limites de débit, et cela fonctionne avec tous les contrôles anti-spam. Ou envoyez depuis une server action : le formulaire continue de fonctionner sans JavaScript.

|   | Composant client | Server action |
| --- | --- | --- |
| Fonctionne sans JavaScript | Non | Oui |
| Limites de débit comptées par | Chaque visiteur | L'IP de votre serveur |
| Vérification humaine Ikiform | Utilisez `challenge` du SDK | Ignorée avec une clé secrète |
| Domaines autorisés | Comparés à l'origine du navigateur | Ignorée avec une clé secrète |
| Nécessite une clé secrète | Non | Oui |

## Installez le SDK

`Terminal`:

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

Le SDK est facultatif. Tout ce qui suit fonctionne aussi avec `fetch` ; voir [JavaScript et fetch](https://ikiform.com/fr/docs/frameworks.md).

## Depuis un composant client

`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` ne lève jamais d'exception. Il se résout en `{ ok: true, id }` ou `{ ok: false, error, status, requestId }`.
- `error.fields` associe les ids de champs à des messages quand le formulaire a des [champs](https://ikiform.com/fr/docs/fields.md) définis.
- Affichez le composant depuis n'importe quelle page en server component, par exemple `app/contact/page.tsx`.

### Avec la vérification humaine Ikiform

Si le contrôle anti-spam du formulaire est **Ikiform**, obtenez un token juste avant chaque envoi. `challenge` monte une petite frame ikiform.com qui reste invisible sauf si Turnstile a besoin d'un clic.

`contact-form.tsx`:

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

Importez-le avec `import { challenge, submit } from "@ikiform/sdk";`. Les tokens sont à usage unique : appelez-le une fois par envoi.

## Depuis une server action

Créez une clé secrète dans le tableau de bord sous **API keys** avec la portée `submissions:write`, et stockez-la dans une variable d'environnement. Ne l'exposez jamais au navigateur : ne la préfixez pas par `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>
  );
}
```

> Les envois depuis votre serveur viennent tous de l'adresse IP de votre serveur, donc les limites de débit par visiteur d'Ikiform comptent tous les visiteurs ensemble. Le préréglage Standard autorise 5 envois par minute par IP et par formulaire, ce qu'un site fréquenté peut atteindre. Envoyez depuis le navigateur quand c'est possible, ou choisissez la [limite de débit](https://ikiform.com/fr/docs/rate-limits.md) Relaxed ou Custom pour les formulaires que vous faites passer par un serveur.

Le `submit` du SDK peut aussi servir dans une server action, mais il n'envoie pas d'en-tête `Authorization` : un formulaire avec des domaines autorisés ou la vérification humaine Ikiform refuserait ou signalerait l'envoi. Utilisez `fetch` avec la clé secrète comme ci-dessus.

## Recevoir les webhooks dans un route handler

Pour traiter chaque soumission dans votre app Next.js, ajoutez un route handler. Voir [Recevoir des webhooks](https://ikiform.com/fr/docs/guides/webhook-handlers.md#nextjs).
