# Den 405-Fehler bei einem auf Vercel gehosteten Kontaktformular beheben

> 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.

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

Vercel liefert deinen statischen Build als Dateien aus, und ein `POST` an eine Datei bekommt `405` mit leerem Body, weil keine Funktion diesen Pfad verarbeitet. Richte die `action` des Formulars stattdessen auf deinen Ikiform-Endpunkt aus oder schreib eine Vercel Function, die den Post annimmt. Mit Ikiform setzt du die Formular-ID pro Vercel-Umgebung, damit Previews an ein Testformular und die Produktion an das echte senden.

## Warum Vercel mit 405 antwortet

Vercel leitet jeden Request an eine statische Datei oder an eine Funktion. Eine statische Site, ein Vite-Build oder eine Next.js-App mit `output: "export"` hat nur Dateien. Ein `GET` auf `/contact` liefert die Datei; ein `POST` an denselben Pfad hat kein Ziel, also lehnt Vercel ihn ab, bevor irgendein Code von dir laufen könnte:

`Terminal`:

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

`Antwort`:

```http
HTTP/2 405
server: Vercel
content-disposition: inline
x-vercel-cache: HIT
content-length: 0
```

Die Antwort hat keinen Body, daher zeigt der Browser eine leere Seite oder seine eigene Fehleransicht. Drei Setups führen hierher:

| Dein Code | Warum es auf Vercel 405 gibt |
| --- | --- |
| `<form method="post">` ohne `action` | Das Formular sendet an die Seite, auf der es steht, und die ist eine statische Datei |
| `action="/api/contact"` in einem Vite- oder einfachen HTML-Projekt | Im Projektstamm gibt es keine Datei in einem `api/`-Ordner, also existiert für diesen Pfad keine Funktion |
| Eine Next.js-Server-Action oder ein Route Handler in einem statischen Export | `output: "export"` unterstützt keine Server Actions und keine Route Handler, die den Request lesen. `next dev` meldet den Fehler, und der Build hat keinen Server, der sie ausführen könnte |

Diagramm: Wie Vercel entscheidet, was mit einem Formular-POST passiert

1. Das Formular sendet an einen Pfad in deinem Vercel-Deployment.
2. Vercel sucht eine Funktion, die diesen Pfad verarbeitet.
3. Wenn du eine geschrieben hast, läuft sie. In einem statischen Build gibt es keine, also antwortet Vercel mit 405 und leerem Body.
4. Zeigt die action auf Ikiform, erreicht der Post Vercel gar nicht.
5. Ikiform speichert die Nachricht, schickt dir eine E-Mail und leitet den Browser zurück auf eine statische Seite deiner Vercel-Site.

Vercel ist hier nicht kaputt. Ein statisches Deployment hat keinen Code für einen POST, und eine Funktion ist auf Vercels Seite der einzige Weg, einen hinzuzufügen.

Du fügst also entweder eine Funktion hinzu oder schickst den Post woandershin. Eine Vercel Function in `api/contact.ts` funktioniert, aber dann brauchst du eine E-Mail-API, Spamfilter und Rate-Limits, und du pflegst sie. Sendest du den Post an Ikiform, bleibt das Deployment statisch. Die Lösung im HTML ist ein einziges Attribut:

`Vorher`:

```html
<form method="post">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button>Send</button>
</form>
```

`Nachher`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
```

### Wenn du die Funktion lieber selbst schreibst

Eine Funktion ist eine faire Wahl, wenn die Nachricht irgendwohin muss, wo nur dein Code hinkommt. Rechne vorher zusammen, was dazugehört:

- Ein Konto bei einem E-Mail-Anbieter, eine verifizierte Absenderdomain und ein API-Schlüssel in den Umgebungsvariablen von Vercel.
- Validierung auf dem Server, weil die `required`-Attribute des Browsers eine Bequemlichkeit sind, keine Prüfung.
- Spamfilter und Rate-Limits, sonst schickt dir das Kontaktformular jeden Bot im Internet.
- Einen Ort, an dem Nachrichten liegen bleiben, wenn der E-Mail-Anbieter ausfällt, und eine Möglichkeit, sie erneut zu senden.

Ein Formular-Backend deckt diese Liste für dich ab. Wenn du trotzdem eigenen Code dazwischen haben willst, lass das Formular weiter an Ikiform senden und empfange einen signierten Webhook in einer Funktion; [dein eigener Server](https://ikiform.com/blog/form-submissions-to-your-own-server.md) zeigt den Handler.

## Eine Formular-ID pro Vercel-Umgebung

Jeder Pull Request auf Vercel bekommt ein Preview-Deployment. Wenn Previews an dein Produktionsformular senden, landet jede Testnachricht neben echten in deinem Postfach. Leg ein zweites Ikiform-Formular für Previews an und lass die Umgebungsvariablen von Vercel entscheiden. Füge unter **Settings › Environment Variables** denselben Namen zweimal hinzu:

`Vercel-Umgebungsvariablen`:

```text
VITE_IKIFORM_FORM_ID   Production   frm_xxxxxxxxxxxxxxxx
VITE_IKIFORM_FORM_ID   Preview      frm_yyyyyyyyyyyyyyyy
```

Vite ersetzt `%NAME%`-Platzhalter in `index.html` zur Build-Zeit durch `VITE_`-Variablen, sodass ein einfaches HTML-Formular sie ohne JavaScript nutzen kann. `VITE_VERCEL_ENV` setzt Vercel für Vite-Projekte, und es hält fest, aus welchem Deployment eine Nachricht kam:

`index.html`:

```html
<form id="contact" action="https://ikiform.com/f/%VITE_IKIFORM_FORM_ID%" method="post">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" required />

  <label for="email">Email</label>
  <input id="email" name="email" type="email" autocomplete="email" required />

  <label for="message">Message</label>
  <textarea id="message" name="message" rows="6" minlength="10" required></textarea>

  <input type="hidden" name="build" value="%VITE_VERCEL_ENV%" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button type="submit">Send message</button>
</form>
```

- Vercel liest Umgebungsvariablen bei einer statischen Site zur Build-Zeit. Nach einer Änderung musst du neu deployen, sonst bleibt der alte Wert im HTML.
- Vite lässt einen Platzhalter stehen, wenn die Variable nicht existiert, daher wird eine fehlende ID als `%VITE_IKIFORM_FORM_ID%` ausgeliefert, und jeder Post scheitert mit `400 Bad Request`, bevor er das Formular erreicht. Die Prüfung unten fängt das ab.
- Die Formular-ID ist so oder so öffentlich. Nur `VITE_`-Variablen erreichen den Browser, deshalb gehört kein Geheimnis in eine davon.

`src/contact.js`:

```js
const form = document.querySelector("#contact");

if (form && form.action.includes("%VITE_")) {
  console.error("VITE_IKIFORM_FORM_ID is not set for this Vercel environment");
  form.querySelector("button").disabled = true;
}
```

Du nutzt Next.js mit `output: "export"`? Lies stattdessen `process.env.NEXT_PUBLIC_IKIFORM_FORM_ID` in einer Client-Komponente; die [Hauptanleitung](https://ikiform.com/de/guides/contact-form-static-site.md#form) hat ein Formular für den statischen Export. Wenn deine Next.js-App doch auf den Servern von Vercel läuft, kann eine Server Action mit einem Secret Key senden: siehe die [Next.js-Anleitung](https://ikiform.com/de/docs/guides/nextjs.md#server-action).

## Header und saubere URLs

Wenn deine Site eine Content-Security-Policy setzt, setze sie in `vercel.json` und nimm Ikiform auf. `cleanUrls` liefert `thanks.html` unter `/thanks` aus, was ein aufgeräumteres Weiterleitungsziel ergibt:

`vercel.json`:

```json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "cleanUrls": true,
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
        }
      ]
    }
  ]
}
```

## Previews und erlaubte Domains

Preview-URLs wie `your-project-git-branch-team.vercel.app` sind keine Subdomains von `your-project.vercel.app`, passen also unter **Allowed domains** nicht dazu. Posts von dort landen mit `origin_mismatch` im Spam. Ein weiterer Grund für ein separates Preview-Formular: Lass seine erlaubten Domains leer oder füge den Preview-Host hinzu, den du gerade testest.

> Mit aktivierter Vercel Authentication und dem Schutzumfang Standard Protection, der jedes Deployment außer den Produktionsdomains abdeckt, liegen Preview-URLs hinter einem Vercel-Login. Nur dein Team erreicht sie, ein Preview-Formular ohne erlaubte Domains ist also nicht öffentlich zugänglich. Prüfe **Settings › Deployment Protection**, bevor du dich darauf verlässt. Posts aus Previews sind keine `localhost`-Tests, sie zählen also wie jede Live-Einsendung auf deinen Tarif.

## Prüfen, ob es funktioniert

1. Führe das `curl` oben vor der Lösung gegen deine Produktions-URL aus und bestätige den `405`. Nach der Lösung beginnt die `action` des Formulars im deployten HTML mit `https://ikiform.com/f/`.
2. Öffne ein Preview-Deployment, sende ab und prüfe, ob die Nachricht im Preview-Formular ankommt, nicht im Produktionsformular.
3. Sende auf deiner Produktionsdomain ab und prüfe, ob das Feld `build` den Wert `production` hat.
4. 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.
5. Nichts angekommen? Stell den Statusfilter im Dashboard auf **Spam**. Spam bekommt dieselbe Erfolgsantwort wie eine echte Einsendung.

## Stolperfallen

| Problem | Was zu tun ist |
| --- | --- |
| Immer noch `405` nach der Änderung am Formular | Das Deployment, das du testest, wurde vor der Änderung gebaut, oder ein JavaScript-Handler sendet mit `fetch` an deinen eigenen Pfad. Durchsuche die gebauten Dateien nach `/api/contact`. |
| `400 Bad Request` bei jedem Post oder `404 not_found` von Ikiform | Die Formular-ID hat den Build nicht erreicht, oder es ist die falsche ID für diese Umgebung. Prüfe, ob die Variable für diese Umgebung existiert, und deploye neu. |
| Previews funktionieren, Posts aus der Produktion landen im Spam | Deine Produktionsdomain fehlt unter **Allowed domains** des Produktionsformulars. Füge die eigene Domain sowie `your-project.vercel.app` hinzu. |
| Der Browser landet auf der Dankeseite von Ikiform statt auf deiner | `_redirect` wird ignoriert, außer seine Domain steht unter **Allowed domains** und es ist eine `https:`-URL. Füge die Domain hinzu oder setze stattdessen **Redirect after submit** in den Einstellungen des Formulars. |

## Fragen

### Warum bekomme ich 405 Method Not Allowed, wenn ich ein Formular auf Vercel absende?

Das Formular sendet an einen Pfad, hinter dem nur eine statische Datei liegt. Vercel liefert Dateien für GET-Requests aus, und ohne Funktion für diesen Pfad lehnt es den POST mit 405 ab. Sende das Formular an ein Formular-Backend oder füge eine Vercel Function hinzu.

### Kann ich eine Next.js-Server-Action in einem statischen Export verwenden?

Nein. Server Actions werden mit output: export nicht unterstützt, weil es keinen Server gibt, der sie ausführt. Sende aus einer Client-Komponente an ein Formular-Backend oder deploye ohne statischen Export, damit Vercel die Action ausführt.

### Sollten Preview-Deployments dasselbe Formular wie die Produktion verwenden?

Besser nicht. Gib Previews über eine Preview-Umgebungsvariable eine eigene Formular-ID, damit Testnachrichten weder dein Postfach noch deine Webhooks erreichen.

### Brauche ich vercel.json für ein Kontaktformular?

Nein. Das Formular funktioniert auch ohne. Füge vercel.json nur hinzu, um eine Content-Security-Policy zu setzen, die ikiform.com erlaubt, oder um cleanUrls für eine aufgeräumtere Danke-URL zu aktivieren.

### Ist Ikiform kostenlos?

Nein, es gibt keinen kostenlosen Tarif. Testen ist kostenlos: Du kannst 50 Testeinsendungen pro Monat von localhost senden, bevor du zahlst. Für den Livebetrieb startet Pro bei $6/Monat für 500 Einsendungen, oder du zahlst einmalig 10.000 Einsendungen für $49 oder 50.000 Einsendungen für $149, mit Guthaben, das nie verfällt.

## 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.
- [Netlify](https://ikiform.com/de/guides/contact-form-static-site/netlify.md): 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.
- [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.
