# Cara menambahkan formulir daftar tunggu dengan HTML biasa ke landing page apa pun

> Halaman daftar tunggu lengkap tanpa build dengan HTML biasa yang tetap berfungsi saat JavaScript mati, menampilkan ucapan terima kasih inline saat skrip dimuat, dan punya fallback bila perlu.

Terbit 2026-10-25 · https://ikiform.com/id/guides/waitlist/html

Tulis formulir HTML biasa yang mengirim email ke endpoint Ikiform Anda dan me-redirect ke halaman terima kasih, sehingga berfungsi di host mana pun tanpa JavaScript sama sekali. Lalu tambahkan skrip kecil yang mengirim formulir yang sama dengan `fetch` dan menampilkan ucapan terima kasih inline, serta mengembalikannya ke kiriman biasa setiap kali skrip tidak bisa menyelesaikannya, misalnya saat pemeriksaan manusia aktif.

Halaman daftar tunggu tidak memerlukan framework, build step, atau server. Empat file statis dan backend formulir sudah cukup, dan halamannya dimuat lebih cepat daripada apa pun yang diekspor builder. Halaman ini membangun semuanya lapis demi lapis, sehingga setiap lapisan berfungsi sendiri.

## Yang penting tanpa build step

- **Formulir adalah integrasinya.** `<form>` biasa dengan `action` adalah seluruh koneksi ke Ikiform. Tanpa paket, tanpa API key, tanpa kompilasi.
- **JavaScript adalah peningkatan, bukan keharusan.** Halaman harus tetap mengumpulkan pendaftaran saat skrip diblokir, lambat, atau rusak. Setiap lapisan di bawah menambah lapisan sebelumnya dan kembali ke sana jika gagal.
- **Skrip tidak boleh membuat orang terjebak.** Jika `fetch` gagal atau Ikiform meminta pemeriksaan manusia yang tidak bisa dijalankan `fetch`, skrip mengembalikan formulir ke browser untuk dikirim dengan cara biasa.
- **Hosting bisa di mana saja.** GitHub Pages, Netlify, Cloudflare Pages, bucket S3, atau shared hosting semuanya bisa menyajikan file-file ini apa adanya.

| Lapisan | Yang ditambahkan | Jika tidak ada |
| --- | --- | --- |
| Formulir HTML | Mengirim email, redirect ke `thanks.html` | Tidak ada yang berfungsi, jadi ini yang pertama |
| CSS | Halaman yang mudah dibaca di mode terang dan gelap | Formulir polos tapi berfungsi |
| Skrip | Terima kasih inline, error per field, tanpa pindah halaman | Kiriman biasa dan redirect |

Diagram: Tiga jalur yang bisa ditempuh pendaftaran di halaman daftar tunggu HTML biasa

1. Saat JavaScript mati, browser mengirim formulir, Ikiform me-redirect dengan 303 ke URL _redirect Anda, dan pengunjung melihat thanks.html.
2. Saat skrip dimuat, skrip mengirim dengan fetch dan meminta JSON. Ikiform menjawab 201 atau error, dan skrip menampilkan hasilnya di halaman yang sama.
3. Jika fetch gagal, atau Ikiform menjawab challenge_required, skrip memanggil form.submit().
4. Kiriman biasa itu menjalankan halaman pemeriksaan manusia Ikiform bila perlu dan berakhir di thanks.html.

Setiap jalur berakhir dengan pendaftaran yang tersimpan. Skrip hanya mengubah apa yang dilihat pengunjung.

## File-filenya

### Halamannya

`index.html`:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Acme is almost here</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <h1>Acme is almost here</h1>
    <p>Invoices that chase themselves. Leave your email and we'll invite you first.</p>

    <form id="waitlist" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
      <label for="email">Email</label>
      <div class="row">
        <input id="email" name="email" type="email" autocomplete="email" required placeholder="you@company.com">
        <button>Join the waitlist</button>
      </div>
      <input type="hidden" name="_redirect" value="https://example.com/thanks.html">
      <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>
      <p class="message" role="status" aria-live="polite"></p>
    </form>
  </main>
  <script src="waitlist.js" defer></script>
</body>
</html>
```

- `_redirect` mengarahkan browser ke `thanks.html` setelah kiriman biasa. Ini hanya berfungsi untuk URL `https:` di salah satu **Allowed domains** formulir, jadi tambahkan domain Anda di sana. **Redirect after submit** di pengaturan formulir melakukan hal yang sama tanpa input tersembunyi.
- Input `_gotcha` adalah honeypot. Orang tidak pernah melihatnya; bot yang mengisi semua input disimpan sebagai spam.
- `aria-live="polite"` membuat pembaca layar mengumumkan pesan skrip tanpa memindahkan fokus.
- Tag script memakai `defer`, jadi berjalan setelah formulir ada dan tidak pernah menghalangi halaman tampil.

### Peningkatannya

`waitlist.js`:

```js
const form = document.getElementById("waitlist");
const message = form.querySelector(".message");
const button = form.querySelector("button");
const key = crypto.randomUUID();

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  button.disabled = true;
  message.textContent = "Joining…";
  try {
    const response = await fetch(form.action, {
      method: "POST",
      headers: { Accept: "application/json", "Idempotency-Key": key },
      body: new FormData(form),
    });
    const result = await response.json();
    if (result.ok) {
      form.replaceChildren(Object.assign(document.createElement("p"), {
        textContent: "You're on the list. We'll email you on launch day.",
        role: "status",
      }));
      return;
    }
    if (result.error.code === "challenge_required") {
      form.submit();
      return;
    }
    const wait = response.headers.get("Retry-After");
    message.textContent = wait
      ? `Too many tries. Wait ${wait} seconds and try again.`
      : result.error.fields?.email?.[0] ?? result.error.message;
  } catch {
    form.submit();
    return;
  }
  button.disabled = false;
});
```

- `Accept: application/json` membuat Ikiform menjawab dengan JSON alih-alih redirect, sehingga skrip bisa menampilkan hasilnya di tempat.
- Idempotency key dibuat sekali per pemuatan halaman. Klik ganda, atau percobaan ulang setelah jawaban yang lambat, hanya menyimpan satu pendaftaran.
- `challenge_required` berarti pemeriksaan spam formulir diatur ke **Ikiform** dan `fetch` tidak mengirim token. `form.submit()` mengirim dengan cara biasa, yang menampilkan halaman pemeriksaan Ikiform lalu halaman terima kasih. Cara ini tidak memicu event `submit` lagi, jadi tidak terjadi loop.
- Error jaringan memakai fallback yang sama. Jika Ikiform tidak bisa dijangkau, kiriman biasa menampilkan error milik browser sendiri, yang tetap lebih jujur daripada tombol yang macet.
- `429` membaca `Retry-After` dan memberi tahu berapa lama harus menunggu.

### Halaman terima kasih

`thanks.html`:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>You're on the list</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <h1>You're on the list</h1>
    <p>We'll email you the day Acme opens. Nothing before that.</p>
    <p><a href="/">Back to the page</a></p>
  </main>
</body>
</html>
```

### Gayanya

`style.css`:

```text
body { margin: 0; font: 16px/1.5 system-ui, sans-serif; color: #111; background: #fff; }
main { max-width: 34rem; margin: 0 auto; padding: 18vh 1rem 4rem; }
h1 { font-size: 2.25rem; line-height: 1.1; margin: 0 0 0.75rem; }
label { display: block; font-weight: 600; margin: 1.5rem 0 0.375rem; }
.row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
input[type="email"] { flex: 1 1 14rem; font: inherit; padding: 0.625rem 0.75rem; border: 1px solid #ccc; border-radius: 0.5rem; }
button { font: inherit; padding: 0.625rem 1rem; border: 0; border-radius: 0.5rem; background: #111; color: #fff; cursor: pointer; }
button:disabled { opacity: 0.6; cursor: progress; }
.message { min-height: 1.5em; margin: 0.5rem 0 0; }
@media (prefers-color-scheme: dark) {
  body { color: #eee; background: #111; }
  input[type="email"] { background: #1b1b1b; color: inherit; border-color: #444; }
  button { background: #eee; color: #111; }
}
```

> Jika halaman berada di domain yang berbeda dari `example.com`, perbarui `_redirect` dan tautan kembalinya. Ikiform mengabaikan `_redirect` yang tidak bisa diikutinya dan menampilkan halaman terima kasih hosted miliknya, jadi ketidakcocokan tidak pernah menghilangkan pendaftaran.

## Deploy dan uji

Letakkan keempat file dalam satu folder dan unggah ke host statis mana pun. Lalu tambahkan domain host di **Allowed domains**: misalnya `username.github.io`, `your-site.netlify.app`, atau domain Anda sendiri. Saat bekerja secara lokal, tambahkan juga `localhost`; sajikan folder dengan server statis apa pun, karena halaman yang dibuka sebagai URL `file://` tidak mengirim origin.

## Pastikan berfungsi

1. Di **Allowed domains** formulir, tambahkan domain situs Anda, ditambah `localhost` selama pengembangan. Biarkan kosong untuk menerima kiriman dari mana saja.
2. Matikan JavaScript di browser dan mendaftar. Anda mendarat di `thanks.html`.
3. Nyalakan lagi dan mendaftar sekali lagi. Formulir berganti menjadi kalimat terima kasih tanpa meninggalkan halaman.
4. Atur **Spam check** ke **Ikiform** dan mendaftar sekali lagi. Halaman berpindah ke pemeriksaan, lalu ke `thanks.html`.
5. Buka **Submissions** di dashboard. Kiriman baru ada di sana dengan setiap field yang Anda kirim.
6. Kirim dengan honeypot terisi, `_gotcha=bot`. Kiriman tetap mendapat jawaban sukses, tetapi disimpan sebagai spam dan tidak memicu email atau webhook.

## Jebakan

| Masalah | Yang harus dilakukan |
| --- | --- |
| 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. |
| `403 origin_not_allowed`, atau setiap kiriman masuk ke spam | Domain halaman tidak ada di **Allowed domains** formulir. Tambahkan, ditambah `localhost` untuk tes lokal. Subdomain dari domain yang tercantum diizinkan. |
| Kiriman dari halaman yang dibuka sebagai file ditolak atau ditandai spam | Halaman `file://` tidak mengirim origin. Sajikan folder dengan server statis lokal dan tambahkan `localhost` ke **Allowed domains**. |
| Kalimat terima kasih tidak pernah muncul, tetapi pendaftaran masuk | Skrip tidak dimuat: periksa path di atribut `src`, atau pastikan host menyajikan file `.js`. Kiriman biasa tetap berfungsi, itulah sebabnya pendaftaran tetap masuk. |
| `402 test_limit_reached` | Workspace belum punya paket dan sudah memakai 50 tes `localhost` gratisnya bulan ini. Pilih paket, atau gunakan **Send test** di dashboard. |

[Dokumentasi formulir HTML](https://ikiform.com/id/docs/html.md) mencantumkan setiap field yang dicadangkan, dan [template daftar tunggu](https://ikiform.com/forms/waitlist) membuat versi dengan nama dan peran. Mengekspor daftar dan email peluncuran dibahas di [panduan utama](https://ikiform.com/id/guides/waitlist.md#launch-day).

## Pertanyaan

### Bisakah formulir daftar tunggu berfungsi tanpa JavaScript?

Bisa. Formulir HTML biasa mengirim email ke endpoint Ikiform Anda dan Ikiform me-redirect browser ke halaman terima kasih Anda. JavaScript hanya menambahkan pesan inline.

### Di mana saya bisa meng-hosting halaman daftar tunggu HTML biasa?

Di mana saja yang menyajikan file statis: GitHub Pages, Netlify, Cloudflare Pages, S3, atau shared hosting. Formulir mengirim ke Ikiform, jadi host tidak pernah menjalankan kode apa pun.

### Apa yang terjadi jika pemeriksaan manusia aktif dan skrip mengirim dengan fetch?

Ikiform menjawab 403 challenge_required. Skrip di panduan ini menangkapnya dan mengirim formulir dengan cara biasa, yang menampilkan halaman pemeriksaan lalu halaman terima kasih Anda.

### Apakah Ikiform meng-hosting halaman formulir untuk saya?

Tidak. Ikiform tidak punya halaman formulir hosted. Anda meletakkan formulir di situs Anda sendiri, di builder atau framework apa pun, dan mengarahkannya ke endpoint Ikiform Anda.

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

- [Daftar tunggu untuk SaaS baru](https://ikiform.com/id/guides/waitlist.md): Bangun daftar tunggu pra-peluncuran di landing page Anda sendiri: formulir, perlindungan spam, duplikat, email konfirmasi, dan ekspor rapi di hari peluncuran.
- [Daftar tunggu Next.js](https://ikiform.com/id/guides/waitlist/nextjs.md): Daftar tunggu Next.js App Router: server action yang meneruskan pendaftaran dengan secret key, useActionState, terima kasih optimistis, dan batas per pengunjung.
- [Daftar tunggu di Carrd](https://ikiform.com/id/guides/waitlist/carrd.md): Kumpulkan pendaftaran daftar tunggu di halaman Carrd: formulir Send to URL di Pro Plus dengan pelacakan UTM, atau formulir HTML yang di-embed dengan honeypot di Pro Standard.
- [Daftar peluncuran Product Hunt](https://ikiform.com/id/guides/waitlist/product-hunt.md): Kumpulkan daftar peluncuran berminggu-minggu sebelum Product Hunt, tandai setiap pendaftaran per channel, dan kirim email hari-H dalam dua gelombang mengikuti reset waktu Pasifik.
- [Pendaftaran beta dengan penyaringan](https://ikiform.com/id/guides/waitlist/beta-signup.md): Formulir lamaran beta dengan pertanyaan penyaringan yang ditegakkan server, dan skrip yang menilai pelamar serta memilih setiap kohort dari daftar.
- [Daftar tunggu aplikasi mobile](https://ikiform.com/id/guides/waitlist/mobile-app.md): Daftar tunggu pra-peluncuran untuk aplikasi iOS atau Android yang menanyakan ponsel yang dipakai, mengirim penguji iPhone ke TestFlight, dan mengisi closed test Play.
