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:

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!



Read more about Tutorials

Cover Image for Building an AI-powered Blog with Next.js and WordPress

Building an AI-powered Blog with Next.js and WordPress

17 min read
Learn how to build a blog with Next.js 13 and WordPress and how to leverage AI to generate content.
Cover Image for Using Supabase Vault to store secrets

Using Supabase Vault to store secrets

6 min read
Supabase Vault is a Postgres extension that allows you to store secrets in your database. This is a great way to store API keys, tokens, and other sensitive information. In this tutorial, we'll use Supabase Vault to store our API keys
Cover Image for Introduction to Next.js Server Actions

Introduction to Next.js Server Actions

9 min read
Next.js Server Actions are a new feature introduced in Next.js 13 that allows you to run server code without having to create an API endpoint. In this article, we'll learn how to use them.
Cover Image for Next.js 13: complete guide to Server Components and the App Directory

Next.js 13: complete guide to Server Components and the App Directory

17 min read
Unlock the full potential of Next.js 13 with our most complete and definitive tutorial on using server components and the app directory.
Cover Image for Pagination with React.js and Supabase

Pagination with React.js and Supabase

6 min read
Discover the best practices for paginating data using Supabase and React.js using the Supabase Postgres client
Cover Image for How to sell code with Lemon Squeezy and Github

How to sell code with Lemon Squeezy and Github

7 min read
Sell and monetize your code by giving private access to your Github repositories using Lemon Squeezy