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

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:

ConvertkitSignupForm.tsx
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!