# Memperbaiki error 405 pada formulir kontak yang di-host di Vercel

> Halaman statis di Vercel menjawab POST formulir dengan 405 Method Not Allowed; ini penyebabnya, dan cara mengirim formulir ke backend.

Terbit 2026-10-25 · https://ikiform.com/id/guides/contact-form-static-site/vercel

Vercel menyajikan build statis Anda sebagai file, dan `POST` ke file mendapat `405` dengan body kosong karena tidak ada function yang menangani path itu. Arahkan `action` formulir ke endpoint Ikiform Anda, atau tulis Vercel Function yang menerima kiriman. Dengan Ikiform, atur ID formulir per environment Vercel agar preview mengirim ke formulir uji dan production mengirim ke formulir sungguhan.

## Mengapa Vercel menjawab 405

Vercel mengarahkan setiap request ke file statis atau ke function. Situs statis, build Vite, atau aplikasi Next.js dengan `output: "export"` hanya punya file. `GET` untuk `/contact` mengembalikan file; `POST` ke path yang sama tidak punya tujuan, jadi Vercel menolaknya sebelum kode Anda sempat berjalan:

`Terminal`:

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

`Respons`:

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

Respons tidak punya body, jadi browser menampilkan halaman kosong atau layar error-nya sendiri. Tiga setup berujung di sini:

| Kode Anda | Mengapa 405 di Vercel |
| --- | --- |
| `<form method="post">` tanpa `action` | Formulir mengirim ke halaman tempat ia berada, yang berupa file statis |
| `action="/api/contact"` di proyek Vite atau HTML biasa | Tidak ada file di folder `api/` di root proyek, jadi tidak ada function untuk path itu |
| Server Action atau Route Handler Next.js dalam static export | `output: "export"` tidak mendukung Server Actions, atau Route Handlers yang membaca request. `next dev` melaporkan error-nya, dan build tidak punya server untuk menjalankannya |

Diagram: Bagaimana Vercel memutuskan apa yang dilakukan dengan POST formulir

1. Formulir mengirim ke path di deployment Vercel Anda.
2. Vercel mencari function yang menangani path itu.
3. Jika Anda menulisnya, function itu berjalan. Di build statis tidak ada, jadi Vercel menjawab 405 dengan body kosong.
4. Dengan action mengarah ke Ikiform, kiriman tidak pernah sampai ke Vercel.
5. Ikiform menyimpan pesan, mengirimi Anda email, dan me-redirect browser kembali ke halaman statis di situs Vercel Anda.

Vercel tidak rusak di sini. Deployment statis tidak punya kode untuk POST, dan function adalah satu-satunya cara menambahkannya di sisi Vercel.

Jadi Anda menambahkan function atau mengirim kiriman ke tempat lain. Vercel Function di `api/contact.ts` bisa dipakai, tetapi Anda lalu butuh API email, penyaringan spam, dan rate limit, dan Anda yang memeliharanya. Mengirim ke Ikiform menjaga deployment tetap statis. Perbaikan di HTML cukup satu atribut:

`Sebelum`:

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

`Sesudah`:

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

### Jika Anda lebih suka menulis function sendiri

Function adalah pilihan yang wajar jika pesan harus dikirim ke tempat yang hanya bisa dijangkau kode Anda. Hitung apa saja yang dibutuhkan sebelum mulai:

- Akun penyedia email, domain pengirim yang terverifikasi, dan API key di environment variable Vercel.
- Validasi di server, karena atribut `required` di browser hanya kemudahan, bukan pemeriksaan.
- Penyaringan spam dan rate limit, atau formulir kontak yang mengirimi Anda email dari setiap bot di internet.
- Tempat menyimpan pesan saat penyedia email sedang down, dan cara mengirim ulang.

Form backend menangani daftar itu untuk Anda. Jika Anda tetap ingin kode sendiri ikut terlibat, biarkan formulir mengirim ke Ikiform dan terima webhook bertanda tangan di sebuah function; [server Anda sendiri](https://ikiform.com/blog/form-submissions-to-your-own-server.md) menunjukkan handler-nya.

## Satu ID formulir per environment Vercel

Setiap pull request di Vercel mendapat deployment preview. Jika preview mengirim ke formulir production, setiap pesan uji masuk ke inbox Anda di samping pesan asli. Buat formulir Ikiform kedua untuk preview, dan biarkan environment variable Vercel yang memilih. Di **Settings › Environment Variables**, tambahkan nama yang sama dua kali:

`Environment variable Vercel`:

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

Vite mengganti placeholder `%NAME%` di `index.html` dengan variabel `VITE_` saat build, jadi formulir HTML biasa bisa memakainya tanpa JavaScript. `VITE_VERCEL_ENV` diatur oleh Vercel untuk proyek Vite, dan mencatat dari deployment mana sebuah pesan berasal:

`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 membaca environment variable saat build untuk situs statis. Setelah mengubahnya, deploy ulang, atau nilai lama tetap ada di HTML.
- Vite membiarkan placeholder apa adanya jika variabelnya tidak ada, jadi ID yang hilang ikut terkirim sebagai `%VITE_IKIFORM_FORM_ID%` dan setiap kiriman gagal dengan `400 Bad Request` sebelum mencapai formulir. Pengaman di bawah menangkap hal itu.
- ID formulir tetap publik bagaimanapun juga. Hanya variabel `VITE_` yang sampai ke browser, itulah sebabnya rahasia tidak boleh disimpan di sana.

`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;
}
```

Memakai Next.js dengan `output: "export"`? Baca `process.env.NEXT_PUBLIC_IKIFORM_FORM_ID` di client component; [panduan utama](https://ikiform.com/id/guides/contact-form-static-site.md#form) punya formulir untuk static export. Jika aplikasi Next.js Anda memang berjalan di server Vercel, Server Action bisa mengirim dengan secret key: lihat [panduan Next.js](https://ikiform.com/id/docs/guides/nextjs.md#server-action).

## Header dan URL bersih

Jika situs Anda mengatur Content-Security-Policy, atur di `vercel.json` dan sertakan Ikiform. `cleanUrls` menyajikan `thanks.html` di `/thanks`, yang menjadi tujuan redirect yang lebih rapi:

`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"
        }
      ]
    }
  ]
}
```

## Preview dan domain yang diizinkan

URL preview seperti `your-project-git-branch-team.vercel.app` bukan subdomain dari `your-project.vercel.app`, jadi tidak cocok di **Allowed domains**. Kiriman dari sana masuk ke spam dengan `origin_mismatch`. Itu alasan lain untuk memakai formulir preview terpisah: biarkan domain yang diizinkannya kosong, atau tambahkan host preview yang sedang Anda uji.

> Dengan Vercel Authentication aktif dan cakupan Standard Protection, yang mencakup setiap deployment kecuali domain production, URL preview berada di balik login Vercel. Hanya tim Anda yang bisa mengaksesnya, jadi formulir preview tanpa domain yang diizinkan tidak terbuka untuk publik. Cek **Settings › Deployment Protection** sebelum mengandalkannya. Kiriman dari preview bukan uji `localhost`, jadi dihitung terhadap paket Anda seperti kiriman live lainnya.

## Pastikan berfungsi

1. Jalankan `curl` di atas terhadap URL production Anda sebelum perbaikan, dan pastikan hasilnya `405`. Setelah perbaikan, `action` formulir di HTML yang di-deploy diawali `https://ikiform.com/f/`.
2. Buka deployment preview, kirim, dan pastikan pesannya masuk ke formulir preview, bukan formulir production.
3. Kirim di domain production Anda dan cek field `build` berisi `production`.
4. Cek inbox setiap penerima notifikasi. Masing-masing mendapat email berisi field, dan **Reply** terkirim ke pengirim formulir jika formulir punya field `email`.
5. Tidak ada yang masuk? Ubah filter status di dashboard ke **Spam**. Spam mendapat jawaban sukses yang sama dengan kiriman asli.

## Hal yang sering menjebak

| Masalah | Yang harus dilakukan |
| --- | --- |
| Masih `405` setelah mengubah formulir | Deployment yang Anda uji dibangun sebelum perubahan, atau handler JavaScript mengirim ke path Anda sendiri dengan `fetch`. Cari `/api/contact` di file hasil build. |
| `400 Bad Request` di setiap kiriman, atau `404 not_found` dari Ikiform | ID formulir tidak sampai ke build, atau ID-nya salah untuk environment itu. Cek variabelnya ada untuk environment tersebut lalu deploy ulang. |
| Preview berfungsi, kiriman production masuk ke spam | Domain production Anda tidak ada di **Allowed domains** formulir production. Tambahkan domain kustom serta `your-project.vercel.app`. |
| Browser mendarat di halaman terima kasih Ikiform, bukan halaman Anda | `_redirect` diabaikan kecuali domainnya ada di **Allowed domains** dan berupa URL `https:`. Tambahkan domainnya, atau atur **Redirect after submit** di pengaturan formulir sebagai gantinya. |

## Pertanyaan

### Mengapa saya mendapat 405 Method Not Allowed saat mengirim formulir di Vercel?

Formulir mengirim ke path yang hanya punya file statis. Vercel menyajikan file untuk request GET, dan tanpa function untuk path itu, Vercel menolak POST dengan 405. Kirim formulir ke form backend atau tambahkan Vercel Function.

### Bisakah saya memakai Server Action Next.js pada static export?

Tidak. Server Actions tidak didukung dengan output: export, karena tidak ada server untuk menjalankannya. Kirim dari client component ke form backend, atau deploy tanpa static export agar Vercel menjalankan action-nya.

### Haruskah deployment preview memakai formulir yang sama dengan production?

Sebaiknya tidak. Beri preview ID formulir sendiri lewat environment variable Preview, agar pesan uji tidak sampai ke inbox atau webhook Anda.

### Apakah saya butuh vercel.json untuk formulir kontak?

Tidak. Formulir berfungsi tanpanya. Tambahkan vercel.json hanya untuk mengatur Content-Security-Policy yang mengizinkan ikiform.com, atau untuk mengaktifkan cleanUrls agar URL terima kasih lebih rapi.

### Apakah Ikiform gratis?

Tidak, tidak ada paket gratis. Pengujian gratis: Anda bisa mengirim 50 kiriman uji per bulan dari localhost sebelum membayar. Untuk go live, Pro mulai $6/bln untuk 500 kiriman, atau Anda bisa bayar sekali untuk 10.000 kiriman seharga $49 atau 50.000 kiriman seharga $149, dengan kredit yang tidak pernah kedaluwarsa.

## Panduan lainnya

- [Formulir kontak di situs statis](https://ikiform.com/id/guides/contact-form-static-site.md): Pasang formulir kontak yang berfungsi di situs statis tanpa server: ke mana POST dikirim, spam, balasan lewat email, dan kode untuk setiap host dan framework.
- [Netlify](https://ikiform.com/id/guides/contact-form-static-site/netlify.md): Jalankan formulir kontak di situs Netlify tanpa Netlify Forms: tanpa deteksi saat build, kiriman JSON dari React, dan formulir terpisah untuk deploy preview.
- [Cloudflare Pages](https://ikiform.com/id/guides/contact-form-static-site/cloudflare-pages.md): Tambahkan formulir kontak ke Cloudflare Pages tanpa menulis Function: kirim ke form backend, atur header di _headers, dan pakai key Turnstile Anda sendiri.
- [GitHub Pages](https://ikiform.com/id/guides/contact-form-static-site/github-pages.md): GitHub Pages tidak bisa menjalankan kode server, jadi POST formulir mendapat 405 Not Allowed; kirim ke form backend dan atur redirect project site dengan benar.
- [Amazon S3](https://ikiform.com/id/guides/contact-form-static-site/aws-s3.md): Endpoint website S3 hanya menerima GET dan HEAD lewat HTTP biasa, jadi kirim formulir kontak ke form backend dan atur redirect dengan benar lewat CloudFront.
- [Website portofolio](https://ikiform.com/id/guides/contact-form-static-site/portfolio-website.md): Formulir kontak portofolio yang menanyakan hal yang biasa Anda tanyakan di panggilan pertama: proyek, budget, waktu, dan link, tanpa server dan dengan balasan dari inbox Anda.
