TutorialsHow 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 updates

Read more about
Tutorials

Cover Image for Caching a Next.js API with Redis

Caching a Next.js API with Redis

·5 min read

Find out how to cache a Next.js Serverless API with Redis

Cover Image for The complete guide to Stripe and Next.js

The complete guide to Stripe and Next.js

·20 min read

Learn everything you need to start collecting payments for your Next.js application with Stripe Checkout

Cover Image for Improve your Next.js website Core Web Vitals

Improve your Next.js website Core Web Vitals

·7 min read

In this post, we share how to optimize the performance of your Next.js website and improve your Core Web Vitals

Cover Image for How to call an API with Next.js

How to call an API with Next.js

·8 min read

Learn how to call API endpoints in your Next.js application

Cover Image for When to use SSR with Next.js

When to use SSR with Next.js

·4 min read

Learn when to use SSR or SSG with your Next.js application

Cover Image for Blocking authentication with Firebase Auth Functions

Blocking authentication with Firebase Auth Functions

·2 min read

Firebase has introduced functions that allow us to write server logic before or after authenticating. Let's see how to use them.