How to make a Convertkit sign-up form with Next.js

Want to grow your Newsletter list? Learn how to build a sign-up form with Convertkit and Next.js

·5 min read
Cover Image for How to make a Convertkit sign-up form with Next.js

Convertkit is one of the most used services for creating and sending newsletters, or for selling digital products.

Sign-up forms are very important before launching your SaaS because it allows you to contact your prospective customers, get feedback, and generally create an audience for your product: this is why the Makerkit's SaaS starter helps you during the pre-launch phase of your SaaS by giving you the option of using a Convertkit or a EmailOctopus sign-up form, two among the favorite email marketing providers for developers and startups.

While Convertkit does offer various built-in sign-up forms, that means having to host one more script onto your website, which also means a heavier page.

When I built Makerkit, I decided that adding yet another script wasn't worth it: fortunately, Convertkit does allow subscriptions using an API we can use for building a lightweight form without any need for an external third-party script.

Building the Sign-Up Form with Makerkit's UI Components

Retrieve the API Key

First, we have to retrieve the API key from your Convertkit Dashboard, click the link to visit it.

Your API key needs to be added to the Next.js environment variables of your application so that we can access it as process.env.CONVERTKIT_API_KEY.

Retrieve the Form ID

Before being able to headlessly subscribe users to a form, we need to create one in Convertkit. In fact, to subscribe users with the Converkit API we need to specify a form ID.

To do so, visit the forms page in Convertkit, create a form, and grab the ID of the form: this is available from the URL as the last segment, or by clicking on "Publish" where it will be available within the script coding block with the data-ui attribute.

Grab the form ID and store it somewhere in your environment or your configuration.

Let's write the sign-up form's UI

It's time to write the UI of our sign-up form. The code below will look just like the one used in this website:

Writing the Component's UI

The Form's UI is very simple: a form that when submitted will call its handler onSubmit.

<>
  <form
    target="_blank"
    className={`space-around flex w-full flex-grow justify-center`}
    onSubmit={onSubmit}
  >
    <input
      type="email"
      className="TextInput w-full !rounded-tr-none !rounded-br-none border-r-transparent py-1 text-sm hover:border-r-transparent md:w-80 md:text-base"
      name={name}
      aria-label="Your email address"
      placeholder="your@email.com"
      required
    />

    <button className="Button min-w-[6rem] rounded-tl-none rounded-bl-none text-sm md:text-base">
      {children ?? 'Sign up'}
    </button>
  </form>

  <p className={'mt-2 text-center text-sm md:text-xs'}>
    Subscribe to our newsletter to receive updates
  </p>
</>

Submitting an HTTP request to the API

When the user subscribes and the onSubmit is called, we have to make an HTTP request to our API endpoint at /api/convertkit/subscribe.

To do so, first we create a state property success:

  • if it's false, the request did not work
  • if it's true, the request worked

Here is the handler's code:

const [success, setSuccess] = useState<boolean | undefined>();

const onSubmit: FormEventHandler = useCallback(
  async (event) => {
    event.preventDefault();

    const target = event.target as HTMLFormElement;
    const data = new FormData(target);
    const email = data.get(name);

    const body = JSON.stringify({
      formId,
      email,
    });

    const headers = new Headers({
      'Content-Type': 'application/json; charset=utf-8',
    });

    try {
      await fetch(`/api/convertkit/subscribe`, {
        method: 'POST',
        mode: 'cors',
        cache: 'no-cache',
        headers,
        body,
      });

      setSuccess(true);
    } catch {
      setSuccess(false);
    }
  },
  [formId]
);

Depending on the result of the request, we display either a success message or an error.

The full Form Component

Below, you can find the full component's code:

import { FormEventHandler, useCallback, useState } from 'react';

const ConvertkitSignupForm: React.FC<
  React.PropsWithChildren<{
    formId: string;
  }>
> = ({ formId, children }) => {
  const name = 'email';
  const [success, setSuccess] = useState<boolean | undefined>();

  const onSubmit: FormEventHandler = useCallback(
    async (event) => {
      event.preventDefault();

      const target = event.target as HTMLFormElement;
      const data = new FormData(target);
      const email = data.get(name);

      const body = JSON.stringify({
        formId,
        email,
      });

      const headers = new Headers({
        'Content-Type': 'application/json; charset=utf-8',
      });

      try {
        await fetch(`/api/convertkit/subscribe`, {
          method: 'POST',
          mode: 'cors',
          cache: 'no-cache',
          headers,
          body,
        });

        setSuccess(true);
      } catch {
        setSuccess(false);
      }
    },
    [formId]
  );

  if (success === false) {
    return <p>Apologies, an error occurred</p>;
  }

  if (success) {
    return <p>You&apos;re in! Thank you for subscribing.</p>;
  }

  return (
    <>
      <form
        target="_blank"
        className={`space-around flex w-full flex-grow justify-center`}
        onSubmit={onSubmit}
      >
        <input
          type="email"
          className="TextInput w-full !rounded-tr-none !rounded-br-none border-r-transparent py-1 text-sm hover:border-r-transparent md:w-80 md:text-base"
          name={name}
          aria-label="Your email address"
          placeholder="your@email.com"
          required
        />

        <button className="Button min-w-[6rem] rounded-tl-none rounded-bl-none text-sm md:text-base">
          {children ?? 'Sign up'}
        </button>
      </form>

      <p className={'mt-2 text-center text-sm md:text-xs'}>
        Subscribe to our newsletter to receive updates
      </p>
    </>
  );
};

export default ConvertkitSignupForm;

Building the API to subscribe the user to a sign-up form

Below is the API handler's code that takes care of calling the Convertkit API and subscribe the user:

import { NextApiRequest, NextApiResponse } from 'next';

const API_KEY = process.env.CONVERTKIT_API_KEY;
const BASE_URL = 'https://api.convertkit.com/v3';

function subscribeToForm(
  params: { formId: string; email: string }
) {
  const url = [BASE_URL, `forms`, params.formId, 'subscribe'].join('/');

  const headers = new Headers({
    'Content-Type': 'application/json; charset=utf-8',
  });

  return fetch(url, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      api_key: API_KEY,
      email: params.email,
    }),
  });
}

async function subscribeToFormHandler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const body = req.body;

  // best to validate this with Zod...

  await subscribeToForm({
    formId: body.formId,
    email: body.email,
  });

  return res.send({ success: true });
}

export default subscribeToFormHandler;

And that's it! If you like me don't like the idea of adding yet another huge JS bundle to your client, you can use the script above and create a Convertkit Sign-up form quickly and easily.

Of course, the code above is already included in the Makerkit's SaaS starter for Next.js. Take a look!


Stay informed with our latest resources for building a SaaS

Subscribe to our newsletter to receive updatesor

Read more about

Cover Image for Authenticating users with Remix and Supabase

Authenticating users with Remix and Supabase

·16 min read
Learn how to use Remix and Supabase to authenticate users in your application.
Cover Image for How Makerkit helps boost your SaaS SEO

How Makerkit helps boost your SaaS SEO

·4 min read
Learn how Makerkit can help boost your SaaS SEO thanks to its optimized codebase and SEO-friendly features.
Cover Image for How to sell code with Gumroad and Github

How to sell code with Gumroad and Github

·7 min read
Sell and monetize your code by giving private access to your Github repositories using Gumroad
Cover Image for Migrating to Next.js Server Components Layouts

Migrating to Next.js Server Components Layouts

·6 min read
A simple guide to migrating your _app.tsx component to the new Server Components released with Next.js 13
Cover Image for Getting Started with Next.js Server Components

Getting Started with Next.js Server Components

·8 min read
A simple introduction to using Server Components and the new Layouts Folder Structure with Next.js 13
Cover Image for Counting a collection's documents with Firebase Firestore

Counting a collection's documents with Firebase Firestore

·2 min read
In this article, we learn how to count the number of documents in a Firestore collection using a custom React.js hook.