October 2, 2024
Setting Up a Mailchimp Waiting List or Newsletter Subscription with Next.js 14 and server actions
Learn how to quickly set up a Mailchimp waiting list or newsletter subscription for your product using Next.js and React Server Actions.
Prerequisites
First, you'll need a Mailchimp account. Once you've created one, gather these environment variables:
- Audience ID
- API key
- API server
These can be found in your Mailchimp dashboard (under Audience settings and API keys).
Implementation
1. Set Up Environment Variables
Create or update your .env.local
file.
// .env.local
MAILCHIMP_API_KEY=<YOUR API KEY>
MAILCHIMP_AUDIENCE_ID=<YOUR AUDIENCE ID e.g., 8z2a1234qw>
MAILCHIMP_API_SERVER=<SERVER CODE e.g., us12>
2. Create the Server Action
Create a new file called handleSubscription.ts
.
// actions/handleSubscription.ts
"use server";
import { z } from "zod";
// Import environment variables
const AUDIENCE_ID = process.env.MAILCHIMP_AUDIENCE_ID;
const API_KEY = process.env.MAILCHIMP_API_KEY;
const API_SERVER = process.env.MAILCHIMP_API_SERVER;
export const handleSubscription = async (prevState, formData: FormData) => {
// Validate inputs using Zod
const SubscribeFormDataSchema = z.object({
email_address: z.string().email().min(1).max(100),
status: z.string().min(1).max(100),
});
const parse = SubscribeFormDataSchema.safeParse({
email_address: formData.get("email"),
status: "subscribed",
});
if (!parse.success) {
return {
error: `${formData.get("email")} failed to subscribe`,
};
}
try {
const data = parse.data;
const response = await fetch(
`https://${API_SERVER}.api.mailchimp.com/3.0/lists/${AUDIENCE_ID}/members`,
{
body: JSON.stringify(data),
headers: {
Authorization: `apikey ${API_KEY}`,
"Content-Type": "application/json",
},
method: "POST",
},
);
if (response.ok) {
return {
success: true,
message: `Subscribed successfully: ${data.email_address}`,
};
} else {
const res = await response.json();
// Handle existing members gracefully
if (res.title === "Member Exists") {
return {
success: true,
error: "You are already subscribed.",
};
}
return {
success: false,
error: "Subscription error.",
};
}
} catch (error) {
console.error("Error in handleSubscription:", error);
return {
success: false,
error: "Subscription error.",
};
}
};
3. Create the Newsletter Component
Create a new file called SubscriptionForm.tsx
.
// components/SubscriptionForm.tsx
"use client";
import { handleSubscription } from "@/app/actions/handleSubscription";
import { useFormState, useFormStatus } from "react-dom";
import { toast } from "sonner";
export default function SubscriptionForm() {
const initialFormState = {
message: "",
};
const [formState, formAction] = useFormState(handleSubscription, initialFormState);
// Provide user feedback using toast notifications
if (formState?.message) {
toast.success(formState.message);
}
if (formState?.error) {
toast.error(formState.error);
}
return (
<form action={formAction}>
<label htmlFor="email">Subscribe to our newsletter</label>
<input type="email" id="email" placeholder="Email" name="email" required />
<SubmitButton />
</form>
);
}
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" aria-disabled={pending}>
{pending ? "Sending..." : "Subscribe"}
</button>
);
}
Advanced Usage
You can extend the form to include additional fields by setting new inputs fields on your form and modifying your formData object accordingly.
// inside the server action
const mailchimpData = {
email_address: data.email,
status: "subscribed",
tags: [data.tags],
merge_fields: {
// Add any custom fields here you defined in Mailchimp
PRODUCTREF: data.productRef,
},
};
Benefits
- Progressively enhanced: The form works and can be submitted without JavaScript.
- No race conditions thanks to Next.js' server actions running sequentially.
- Simplified state management without the useState/useEffect dreaded combo.
- Type-safe thanks to Zod server-side validation.
Note for React 19
The stable version of React 19 replaces useFormState
from 'react-dom' with useActionState
from 'react'. The underlying logic remains the same.
This implementation takes advantage of Next.js' implementation of Server Actions and React's latest features to create a robust, type-safe newsletter subscription system.
Back to index