DEV Community

Agam
Agam

Posted on

3

Custom BeeHiiv Home Page with Cloudflare

Before I migrated from Ghost to BeeHiiv, I wanted to keep my newsletter's landing page, it got me around 1.5k subs from HN because it was unique - so I knew I need to keep it.

To achieve this I managed to use Cloudfalre, and here is how you can too.

Basically, we will create a subdomain for the landing page:

  • unzip.dev/ → join.unzip.dev - my new home page.
  • unzip.dev/* - my main website for the newsletter (on BeeHiiv).

This tutorial assumes you also want to use your root domain on BeeHiiv instead of www.yourdomain.com

Step 1 - Connect your domain to BeeHiiv.

  1. First I added my domain unzip.dev.
  2. After verifying, add it to the Web Domain section.

Step 2 - Open a Cloudflare account

  1. Register to Cloudflare move your registrar's DNS Nameservers to CloudFlare.

Step 3 - Deploy your new home page

I used vercel.com and next.js. For that I added a CNAME to vercel for join.unzip.dev.

Step 4 - Patching CLoudflare

We want to hijack the requests to the root path. For that I had to add the following Cloudflare rules:
Cloudflare rules

  1. For the archive page I had in Ghost (optional).
  2. For the posts, in Ghost there are at: unzip.dev/[slug] on BeeHiiv they are at unzip.dev/p/[slug].
  3. For the homepage I added a redirect from / to join.unzip.dev (on vercel).

Bonus www -> root

To redirect all www. to root you have to add the following record:

  • Add the following bulk redirects:

WWW cloudflare rules

  • AAAA for www with the value 100:: otherwise cloudflare will not run the bulk rules.

API for landing page

To be able to subscribe people via my external landing page, I've added a cloudflare worker as follows:


export default {
  async fetch(request, env, ctx) {

    // Allow everyone to post to here via CORS
    const corsHeaders = {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "POST",
      "Access-Control-Allow-Headers": 'Content-Type'
    };

    // A hack to fix the OPTIONS response for CORS
    if (request.method === "OPTIONS") {
      // Handle CORS preflight requests
      return new Response(null, {
        headers: {...corsHeaders}
      })
    }


    const publicationId = 'pub_YOUR_PUBLICATION_ID';

    // Extract the subscription details from the request body
    const requestBody = await request.json();
    const { email } = requestBody;

      // Create the subscription
      const response = await fetch(`https://api.beehiiv.com/v2/publications/${publicationId}/subscriptions`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${env.BEEHIIV_KEY}`, // Replace with your actual bearer token
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email,
          reactivate_existing: true,
          send_welcome_email: true, // Always send the welcome email
          utm_source: 'join.unzip.dev', 
          utm_medium: 'web',
          referring_site: 'join.unzip.dev', 
        }),
      });

      if (!response.ok) {
        throw new Error('Failed to create subscription');
      }

    return new Response("Subbe", {
        headers: {
          ...corsHeaders,
        },
      });
    }
};

Enter fullscreen mode Exit fullscreen mode

At this point every time someone visits / they will be 301ed to join.unzip.dev (seeing my amazing landing page). Otherwise, they will be redirected to BeeHiiv to see the posts themselves.

I hope this helps other's that want to customize their BeeHiiv landing page.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs