DEV Community

Tanin Na Nakorn
Tanin Na Nakorn

Posted on

How to set up many landing pages with waitlist in an economical way

I have been launching several landing pages in the past month. All of them need a waitlist functionality where visitors can sign up to be in a waitlist.

One way is to deploy a full-stack app with frontend and backend where the backend connects to a newsletter service like Buttondown. However, hosting a website with a backend is more expensive than hosting a static website with no backend. With a lot of landing pages, that gets a bit expensive.

If there’s no backend, we can host it for FREE on Netlify or GitHub Pages.

Then, I learned about CORS (Cross-Origin Resource Sharing), which is a mechanism that allows you to invoke fetch(..) to a different domain. Since fetch(..) goes to a different domain, this means the current domain that hosts your landing page doesn’t need a backend. This means you can host many landing pages on Netlify or GitHub Pages and invoke fetch(..) to a domain that hosts a waitlist backend.

On the storage side, we can write to Google Sheets, which is free and the final destination for my waitlist emails anyway.

I’ve made an open-sourced self-hostable waitlist backend here: https://github.com/tanin47/wait

Two example landing pages are:

The high-level architecture looks like below:

Implement CORS

Most modern browsers, if not all, support CORS.

When a browser detects that fetch(..) goes to a different domain, it would first send a HTTP request whose method is OPTIONS to that path. Your waitlist backend needs to respond with the below CORS-related headers:

  • Access-Control-Allow-Origin indicates which domain is able to call this endpoint. The value should either be * (means any domain) or the HTTP request header Origin. The HTTP request Origin is the domain where fetch(..) is invoked.
  • Access-Control-Allow-Methods indicates which HTTP method is allowed. We can use POST here.
  • Access-Control-Allow-Headers indicates which HTTP header is allowed. We can use * for simplicity.
  • Vary influences how the response should be cached by the browser. Since this is about allowing a domain to invoke an HTTP endpoint, we can use the value Origin.

The response body is ignored. Therefore, we can use an empty string.

After the browser gets the response back and deems the current domain is allowed to invoke fetch(..) on the waitlist backend’s domain, the browser now sends the actual fetch request.

It’s important that the response of this request contains the same CORS headers.

Implement Google Sheets integration

I won’t go into details on how to implement a Google Sheets integration. There are many resources and examples online.

One thing that surprises me is that it’s not a simple API key. To write to a Google Sheet, you will need a service account and request an access token before you can write to a Google Sheet.

You can see an example in Java here: https://github.com/tanin47/wait/blob/main/src/main/java/tanin/wait/GoogleSheetService.java

The advantages

The low cost is obviously one advantage. You can host one waitlist backend that serves many landing pages.

The other non-obvious advantage is that you own the form and the JS code completely. Because you only call fetch(..) as if it was the backend on the same domain. This makes it easier to style and customize the after actions.

Compared to, for example, Netlify Form, the customization of the after actions is limited (doc). Any other solution that uses iframe or hosted the form elsewhere will have a similar limitation.

Summary

Currently, I have >10 landing pages hosted on Netlify for free. They invoke fetch(..) to my waitlist backend, which is hosted on OVHcloud using Dokploy for ~$4/month.

It’s probably the cheapest way to implement a waitlist for many landing pages.

Top comments (0)