# Ein Kontaktformular auf Netlify ohne Netlify Forms einbauen

> Betreib ein Kontaktformular auf einer Netlify-Site ohne Netlify Forms: keine Erkennung beim Build, JSON-Posts aus React und ein eigenes Formular für Deploy Previews.

Veröffentlicht am 2026-10-25 · https://ikiform.com/de/guides/contact-form-static-site/netlify

Lass `data-netlify` am Formular weg und richte seine `action` auf deinen Ikiform-Endpunkt aus; Netlify liefert dann die Seite aus und rührt den Post nie an. React- und Vue-Formulare brauchen keine versteckte HTML-Kopie und können JSON senden. Setze in `netlify.toml` eine andere Formular-ID für Deploy Previews, weil Preview-Hosts unter **Allowed domains** nicht zu deiner Site passen.

## Was bei Netlify anders ist

Netlify ist der einzige große statische Host mit eingebauter Formularfunktion. Netlify Forms liest nach jedem Build dein deploytes HTML, findet Formulare mit `data-netlify` oder `netlify` und nimmt für diese Formulare Posts an deine eigene Site an. Jeder andere Post an eine statische Seite scheitert, und auf Netlify scheitert er mit `404` statt `405`:

`Terminal`:

```bash
curl -i -X POST -d "message=hello" https://your-site.netlify.app/contact/
```

`Antwort`:

```http
HTTP/2 404
server: Netlify
cache-status: "Netlify Edge"; fwd=method; fwd-status=404
content-length: 0
```

Dieser `404` schickt Leute auf die Suche nach einer fehlenden Seite, die gar nicht fehlt. Er bedeutet, dass kein erkanntes Formular zum Post gepasst hat. Ob du Netlify Forms überhaupt nutzen solltest, ist eine andere Frage, und der [Vergleich Ikiform vs. Netlify Forms](https://ikiform.com/alternatives/netlify-forms) beantwortet sie ehrlich: Wenn deine Site immer auf Netlify bleibt und das Formular einfaches HTML ist, macht Netlify Forms weniger Arbeit. Diese Seite ist für die anderen Fälle.

| Deine Situation | Warum Netlify Forms im Weg ist |
| --- | --- |
| Das Formular wird von React, Vue oder Svelte gerendert | Der Build analysiert statisches HTML, daher braucht ein clientseitig gerendertes Formular ein verstecktes Duplikat, das Netlify finden kann |
| Du willst JSON per `fetch` senden | Netlify Forms nimmt nur urlencoded- und Multipart-Bodys an |
| Die Site zieht vielleicht zu einem anderen Host um | Das Formular sendet an deine eigene Domain, funktioniert also nur, solange Netlify diese Domain ausliefert |
| Nachrichten sollen einen Webhook erreichen, den du verifizierst | Ikiform signiert jede Zustellung mit Standard-Webhooks-Headern und wiederholt sie bei Bedarf |

Diagramm: Netlify Forms und Ikiform nebeneinander auf derselben Netlify-Site

1. Mit Netlify Forms durchsucht Netlify das HTML jedes Deployments nach Formularen mit data-netlify.
2. Ein gefundenes Formular nimmt Posts an deine eigene Site an, und Netlify speichert sie.
3. Ein nicht gefundenes Formular, etwa eines, das ohne versteckte Kopie per JavaScript gerendert wird, bekommt bei jedem Post 404.
4. Mit Ikiform muss beim Build nichts gefunden werden.
5. Das Formular sendet direkt an ikiform.com, als HTML oder JSON, und Ikiform speichert es und schickt dir eine E-Mail.

Beides kann auf einer Site nebeneinander existieren. Ein Formular mit data-netlify geht an Netlify; ein Formular, dessen action ein Ikiform-Endpunkt ist, geht an Ikiform.

## Ein Formular von Netlify Forms lösen

- Entferne `data-netlify` bzw. `netlify`, `netlify-honeypot` und `data-netlify-recaptcha` vom Formular sowie ein eventuelles verstecktes `form-name`-Eingabefeld. Bleibt `form-name` drin, wird es als normales Feld gespeichert.
- Lösche die versteckte statische Kopie eines JavaScript-Formulars, wie die unten. Netlify brauchte sie nur, um das Formular zu erkennen.
- Setze die `action` oder die `fetch`-URL auf deinen Ikiform-Endpunkt. Die alte `action="/thanks"` wandert in die Einstellung **Redirect after submit** des Formulars.
- Lade vorher die bisherigen Einsendungen auf der Seite **Forms** von Netlify herunter. Sie ziehen nicht von allein um.

`index.html, das hier löschen`:

```html
<form name="contact" netlify hidden>
  <input name="name" />
  <input name="email" />
  <textarea name="message"></textarea>
</form>
```

Lass **form detection** an, wenn andere Formulare auf der Site noch Netlify Forms nutzen. Laut Netlifys Dokumentation ist das Abschalten der Erkennung nur für Sites gedacht, die Netlify Forms nicht nutzen. Sobald nichts mehr darauf angewiesen ist, sparst du durch das Abschalten bei jedem Deployment einen Scan.

### Was die Spam-Einstellungen von Netlify ersetzt

Netlify lässt Akismet über die Einsendungen laufen und bietet `netlify-honeypot` und reCAPTCHA. Bei Ikiform funktioniert der Honeypot weiter, wenn du **Honeypot field** des Formulars auf den Namen setzt, den du schon verwendest, etwa `bot-field`. Spam-Bewertung läuft ohne Einrichtung bei jedem Post, und **Spam check** auf **Ikiform** ersetzt reCAPTCHA durch Cloudflare Turnstile, ganz ohne Schlüssel. Entferne beim Wechsel das reCAPTCHA-`div`.

## Ein React-Formular, das JSON sendet

Ohne Netlify Forms im Weg braucht ein clientseitig gerendertes Formular kein Duplikat und kann JSON senden, was Typen erhält und dich Werte vorher trimmen lässt. Dieses hier hält außerdem fest, welcher Netlify-Kontext die Seite gebaut hat:

`src/ContactForm.tsx`:

```tsx
import { type FormEvent, useState } from "react";

const ENDPOINT = `https://ikiform.com/f/${import.meta.env.VITE_IKIFORM_FORM_ID}`;

interface Message {
  name: string;
  email: string;
  message: string;
  context: string;
}

export function ContactForm() {
  const [sent, setSent] = useState(false);
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    const body: Message & Record<string, string> = {
      name: String(data.get("name") ?? "").trim(),
      email: String(data.get("email") ?? "").trim(),
      message: String(data.get("message") ?? "").trim(),
      context: import.meta.env.VITE_CONTEXT ?? "local",
      _gotcha: String(data.get("_gotcha") ?? ""),
    };
    setError("");
    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify(body),
    }).catch(() => null);
    const result = await response?.json().catch(() => null);
    if (result?.ok) return setSent(true);
    const fields: Record<string, string[]> = result?.error?.fields ?? {};
    setError(Object.values(fields)[0]?.[0] ?? result?.error?.message ?? "Couldn't send. Try again.");
  }

  if (sent) return <p role="status">Got it. I'll answer from my own inbox, usually the same day.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" autoComplete="name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" autoComplete="email" required />
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} minLength={10} required />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error ? <p role="alert">{error}</p> : null}
      <button>Send</button>
    </form>
  );
}
```

- `Content-Type: application/json` sagt Ikiform, dass es JSON parsen soll; `Accept: application/json` bittet um eine JSON-Antwort.
- `VITE_CONTEXT` ist ein Beispielname. Netlify setzt `CONTEXT` während des Builds; gib es in deinem Build-Befehl an Vite weiter oder lass das Feld weg.
- Der Honeypot wird als JSON-Schlüssel gesendet. Behalte ihn im Body, auch wenn das Eingabefeld versteckt ist, damit Bots, die ihn ausfüllen, sich weiterhin selbst verraten.

## Deploy Previews bekommen ihr eigenes Formular

Deploy Previews liegen auf Hosts wie `deploy-preview-12--your-site.netlify.app`. Das ist keine Subdomain von `your-site.netlify.app`, passt also unter **Allowed domains** nicht, und Posts aus Previews landen im Spam. Gib Previews und Branch-Deployments in `netlify.toml` ein eigenes Formular und setze den Content-Security-Policy-Header in derselben Datei:

`netlify.toml`:

```toml
[build]
  command = "npm run build"
  publish = "dist"

[context.production.environment]
  VITE_IKIFORM_FORM_ID = "frm_xxxxxxxxxxxxxxxx"

[context.deploy-preview.environment]
  VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"

[context.branch-deploy.environment]
  VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
```

Lass **Allowed domains** des Preview-Formulars leer oder füge beim Testen den genauen Preview-Host hinzu. Das Produktionsformular bekommt deine Domain und `your-site.netlify.app`. Header in `netlify.toml` gelten für jeden Deploy-Kontext; wenn du eine `_headers`-Datei bevorzugst, muss sie im Publish-Verzeichnis landen.

> Füge niemals `netlify.app` allein zu **Allowed domains** hinzu. Jede Netlify-Site würde passen, auch Kopien deines Formulars auf den Sites anderer Leute.

## Prüfen, ob es funktioniert

1. Öffne ein Deploy Preview, sende ab und prüfe, ob die Nachricht im Preview-Formular landet.
2. Merge, dann sende in der Produktion ab. Prüfe, ob die Nachricht im Produktionsformular landet und die Seite **Forms** von Netlify nichts Neues zeigt.
3. Prüfe das Postfach jedes Benachrichtigungsempfängers. Jeder bekommt eine E-Mail mit den Feldern, und **Reply** geht an den Absender, wenn das Formular ein `email`-Feld hat.
4. Sende einen Post mit ausgefülltem Honeypot, `_gotcha=bot`. Er bekommt trotzdem eine Erfolgsantwort, aber die Einsendung wird als Spam gespeichert und löst weder E-Mail noch Webhook aus.
5. Nichts angekommen? Stell den Statusfilter im Dashboard auf **Spam**. Spam bekommt dieselbe Erfolgsantwort wie eine echte Einsendung.

## Stolperfallen

| Problem | Was zu tun ist |
| --- | --- |
| `404` beim Absenden | Der Post ging an deine eigene Site. Ein gecachter Build hat vielleicht noch die alte `action`, oder ein `fetch`-Aufruf sendet noch an `/`. Durchsuche das Publish-Verzeichnis nach `form-name`. |
| Einsendungen tauchen noch in Netlify auf | Irgendein Formular auf der Seite sendet noch mit `data-netlify` oder `netlify` an deine eigene Site, also erkennt und speichert Netlify es weiterhin. Entferne das Attribut, setze die `action` auf Ikiform und deploye neu. |
| `404 not_found` von Ikiform nur bei Previews | Der Deploy-Preview-Kontext hat keine `VITE_IKIFORM_FORM_ID`, daher endet die URL auf `undefined`. Füge sie unter `[context.deploy-preview.environment]` hinzu. |
| `403 origin_not_allowed`, oder jeder Post landet im Spam | Die Domain der Seite steht nicht unter **Allowed domains** des Formulars. Füge sie hinzu, plus `localhost` für lokale Tests. Subdomains einer eingetragenen Domain sind erlaubt. |
| `422 files_not_supported` | Das Formular hat ein `<input type="file">` mit ausgewählter Datei. Datei-Uploads werden noch nicht unterstützt; entferne das Eingabefeld oder bitte stattdessen um einen Link. |

## Fragen

### Kann ich ein anderes Formular-Backend nutzen, während meine Site auf Netlify bleibt?

Ja. Netlify verarbeitet nur Formulare, die es beim Build erkannt hat. Ein Formular ohne data-netlify, dessen action auf einen anderen Endpunkt zeigt, wird von Netlify wie jede Seite ausgeliefert und sendet direkt an diesen Endpunkt.

### Warum bekommt mein Formular auf Netlify einen 404?

Netlify beantwortet einen POST an eine statische Seite mit 404, außer Netlify Forms hat das Formular im deployten HTML erkannt. Markiere das Formular entweder für Netlify Forms oder richte seine action auf ein anderes Backend aus.

### Kann ich Netlify Forms für ein Formular behalten und Ikiform für ein anderes nutzen?

Ja. Die Erkennung erfolgt pro Formular, anhand des Attributs. Behalte data-netlify bei den Formularen, die Netlify verarbeiten soll, und lass die Formularerkennung an.

### Verliere ich beim Wechsel meine alten Netlify-Einsendungen?

Sie bleiben bei Netlify, nicht bei Ikiform. Lade sie auf der Forms-Seite der Site herunter, bevor du die Formularerkennung abschaltest, und bewahre die Datei bei deinen Unterlagen auf.

### Kann das Formular Datei-Uploads annehmen?

Noch nicht. Ein Post mit einer Datei wird mit 422 files_not_supported abgelehnt. Bitte stattdessen um einen Link zur Datei, etwa die URL eines geteilten Laufwerks.

## Weitere Anleitungen

- [Kontaktformular auf einer statischen Site](https://ikiform.com/de/guides/contact-form-static-site.md): Bau ein funktionierendes Kontaktformular in eine statische Site ohne Server ein: wohin der POST geht, Spam, Antworten per E-Mail und Code für jeden Host und jedes Framework.
- [Vercel](https://ikiform.com/de/guides/contact-form-static-site/vercel.md): Eine statische Seite auf Vercel beantwortet einen Formular-POST mit 405 Method Not Allowed; hier steht, warum, und wie du das Formular stattdessen an ein Backend sendest.
- [Cloudflare Pages](https://ikiform.com/de/guides/contact-form-static-site/cloudflare-pages.md): Bau ein Kontaktformular in Cloudflare Pages ein, ohne eine Function zu schreiben: an ein Formular-Backend senden, Header in _headers setzen und eigene Turnstile-Schlüssel nutzen.
- [GitHub Pages](https://ikiform.com/de/guides/contact-form-static-site/github-pages.md): GitHub Pages kann keinen Servercode ausführen, also bekommt ein Formular-POST 405 Not Allowed; sende an ein Formular-Backend und setze Weiterleitungen für Projekt-Sites richtig.
- [Amazon S3](https://ikiform.com/de/guides/contact-form-static-site/aws-s3.md): S3-Website-Endpunkte akzeptieren nur GET und HEAD über einfaches HTTP, also sende das Kontaktformular an ein Formular-Backend und setze Weiterleitungen mit CloudFront richtig.
- [Portfolio-Website](https://ikiform.com/de/guides/contact-form-static-site/portfolio-website.md): Ein Portfolio-Kontaktformular mit den Fragen, die du im ersten Gespräch stellen würdest: Projekt, Budget, Zeitplan und ein Link, ohne Server und mit Antworten aus deinem Postfach.
