Daftar tunggu untuk SaaS baru

Daftar tunggu untuk SaaS baru5 menit baca

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.

Preet SutharPendiri, Ikiform

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.
LapisanYang ditambahkanJika tidak ada
Formulir HTMLMengirim email, redirect ke thanks.htmlTidak ada yang berfungsi, jadi ini yang pertama
CSSHalaman yang mudah dibaca di mode terang dan gelapFormulir polos tapi berfungsi
SkripTerima kasih inline, error per field, tanpa pindah halamanKiriman biasa dan redirect
Tiga jalur yang bisa ditempuh pendaftaran di halaman daftar tunggu HTML biasaTanpa JavaScriptSkrip dimuatSkrip tidak bisa selesaikiriman formulirurlencodedIkiformredirect 303thanks.htmlhalaman AndasendirifetchAccept:application/jsonIkiform201 atau errorpesan inlinehalaman samafetcherror jaringan atauperlu pemeriksaanform.submit()kiriman biasahalamanpemeriksaan,lalu thanks.html_redirectJSONfallbackTiga jalur yang bisa ditempuh pendaftaran di halaman daftar tunggu HTML biasaTanpa JavaScriptSkrip dimuatSkrip tidak bisa selesaikiriman formulirurlencodedIkiformredirect 303thanks.htmlhalaman Anda sendirifetchAccept: application/jsonIkiform201 atau errorpesan inlinehalaman samafetcherror jaringan atau perlupemeriksaanform.submit()kiriman biasahalaman pemeriksaan, laluthanks.html_redirectJSONfallback
  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
<!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
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
<!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
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

MasalahYang 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 spamDomain 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 spamHalaman file:// tidak mengirim origin. Sajikan folder dengan server statis lokal dan tambahkan localhost ke Allowed domains.
Kalimat terima kasih tidak pernah muncul, tetapi pendaftaran masukSkrip 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_reachedWorkspace belum punya paket dan sudah memakai 50 tes localhost gratisnya bulan ini. Pilih paket, atau gunakan Send test di dashboard.

Dokumentasi formulir HTML mencantumkan setiap field yang dicadangkan, dan template daftar tunggu membuat versi dengan nama dan peran. Mengekspor daftar dan email peluncuran dibahas di panduan utama.

Pertanyaan

Mentok di salah satu langkah, atau ingin panduan untuk situasi Anda? Email hello@ikiform.com.