Next.js & Fluent UI: Newsletter Form
Newsletters are a great means of promoting content to an interested audience.
These notes are based on setup Next.js and FluentUI. Their purpose is to aid in creating a newsletter using Next.js, MailerLite, and the Fluent UI React library.
FluentUI
The FluentUI library is utilized to create the UI for the newsletter subscription form.
The subscription form consists of an email address input field and a call-to-action button. Additionally, the form displays a message upon submission, indicating either successful subscription or an error.
Next.js
Next.js simplifies the creation of API endpoints in React with its Route Handlers feature, which is essential for building a newsletter form that requires API communication.
Note, Route Handlers are only available inside the app
directory.
To prepare Route Handler:
- Create
api
folder inside theapp
directory if it does not exist yet. - Add a
route.js
file to a new folder calledsubscribe
insideapp/api/
.
This is where communication with MailerLite will take place.
MailerLite
MailerLite is an email marketing software that allows to create and send newsletters to subscribers. It can be integrated with Next.js using an API.
Setup
After creating an account at MailerLite, get an API key.
As Next.js does not have newsletter functionality, special software like MailerLite must be used for that purpose. An API key enables communication between a Next.js application and MailerLite.
Upon clicking the button in the newsletter form, the user's email address will be added to a designated group. Thus, the following values are required:
- MailerLite API key.
- Group ID for the mailing list.
Environment variables
To avoid hardcoding MailerLite secret values into API file, set them up as environment variables.
To start, a .env.local
file should be created in a root of the project for local testing purposes:
MAILERLITE_API_KEY=
MAILERLITE_GROUP_ID=
Remember to add .env.local
to your .gitignore
file to avoid committing secrets. Also, if deploying with Vercel, these variables need to be configured accordingly.
Create API request
With the necessary values available as environment variables, it's possible to create an API route at app/api/subscribe/route.js
that adds a new subscribers to the list.
Install MailerLite Node.js SDK
First, make sure to install @mailerlite/mailerlite-nodejs
:
npm install @mailerlite/mailerlite-nodejs
The @mailerlite/mailerlite-nodejs
package is a Node.js library for interfacing with the MailerLite email marketing platform.
It provides a simple way to integrate MailerLite functionality into Node.js applications. The package can also be used with React and Next.js applications, as both of these frameworks are built on top of Node.js.
Then, update app/api/subscribe/route.js
with the following code:
/**
* Import the NextResponse from the Next.js to create and manipulate HTTP responses.
*/
import { NextResponse } from "next/server";
/**
* Import the MailerLite package to interact with the MailerLite API for managing
* subscribers and mailing lists.
*/
import MailerLite from "@mailerlite/mailerlite-nodejs";
// Initialize a new instance of the MailerLite client using the API key from the environment variables
const mailerlite = new MailerLite({
api_key: process.env.MAILERLITE_API_KEY,
});
/**
* Handle POST requests for subscribing a user to a mailing list.
*
* @param {Object} request - The incoming request object containing the user's email.
* @returns {NextResponse} - The response object containing the status and result of the request.
*/
export async function POST(request) {
// Extract the email from the request's JSON payload
const { email } = await request.json();
// Define the parameters for the MailerLite API request
const params = {
email: email,
groups: [process.env.MAILERLITE_GROUP_ID],
status: "unconfirmed",
};
try {
// Attempt to create or update the subscriber in MailerLite using the provided email and parameters
const response = await mailerlite.subscribers.createOrUpdate(params);
// Return a success response containing the result and a message
return NextResponse.json({
message: "You have been subscribed!",
data: response.data,
});
} catch (error) {
// Handle errors that include a response from the MailerLite API
if (error.response) {
return NextResponse.json(error.response.data, {
status: error.response.status,
});
} else {
// Handle other errors and return a generic error message
return NextResponse.json(
{
message: "An error occurred. Please try again.",
},
{
status: 500,
}
);
}
}
}
Create Newsletter component
To gather user input, create a component that sends requests to the API:
"use client";
/**
* Import the 'useState' hook from the 'react' library.
*
* 'useState' is used for creating and managing state variables in functional components.
*/
import { useState } from "react";
/**
* Import the necessary components and utilities from the '@fluentui/react-components' library.
*
* 'makeStyles' is a utility for creating custom CSS-in-JS styles.
* 'shorthands' is an object containing utility functions for shorthand CSS properties.
* 'tokens' is an object containing design token values, such as spacing and colors.
* 'Button' is a UI component for creating buttons.
* 'Card' is a UI component for creating a card container.
* 'Field' is a UI component for creating form fields.
* 'Input' is a UI component for creating form input elements.
* 'Spinner' is a UI component for creating a loading spinner.
*/
import {
makeStyles,
shorthands,
tokens,
Button,
Card,
Field,
Input,
Spinner,
} from "@fluentui/react-components";
/**
* Import the 'PersonMail20Regular' icon from the '@fluentui/react-icons' library.
*
* This icon will be used as a visual indicator for the email input field in the form.
*/
import { PersonMail20Regular } from "@fluentui/react-icons";
/**
* Create a custom 'useStyles' hook to define the styling for the Newsletter component.
*/
const useStyles = makeStyles({
container: {
maxWidth: "400px",
width: "100%",
...shorthands.marginInline("auto"),
},
form: {
display: "flex",
flexDirection: "row",
alignItems: "start",
width: "100%",
...shorthands.gap(tokens.spacingHorizontalS),
},
field: {
flexGrow: "1",
"& > label": {
clip: "rect(1px, 1px, 1px, 1px)",
clipPath: "inset(50%)",
height: "1px",
width: "1px",
...shorthands.margin("-1px"),
...shorthands.overflow("hidden"),
...shorthands.padding("0"),
position: "absolute",
},
"& .fui-Field__validationMessage": {
marginBlockStart: tokens.spacingVerticalMNudge,
"&:empty": {
display: "none",
},
},
},
});
/**
* 'NewsletterForm' is a component that renders a newsletter subscription form.
* When the user submits the form, it sends a subscription request to the server.
*
* @param {object} props - The properties passed to the component.
*/
export default function NewsletterForm(props) {
// Retrieve the styles object from the 'useStyles' hook.
const styles = useStyles();
// Declare state variables for email, message, status, isLoading, and isSubscribed.
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [status, setStatus] = useState("none");
const [isLoading, setIsLoading] = useState(false);
const [isSubscribed, setIsSubscribed] = useState(false);
/**
* Sends a subscription request to the server with the given email address.
*
* @param {string} email - The email address to subscribe.
* @returns {Promise<object>} The response data with 'message' and 'status' properties.
*/
const subscribe = async (email) => {
const response = await fetch("/api/subscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});
const data = await response.json();
return {
message: data.message,
status: response.ok ? "success" : "error",
};
};
/**
* Handles the form submission.
*
* @param {Event} event - The form submission event.
*/
const handleSubmit = async (event) => {
// Prevent the default form submission behavior.
event.preventDefault();
// Set the isLoading state to true to indicate the subscription is in progress.
setIsLoading(true);
try {
const { message, status } = await subscribe(email);
setMessage(message);
setStatus(status);
setIsLoading(false);
setIsSubscribed(status === "success");
} catch (error) {
setMessage("An error occurred. Please try again.");
setStatus("error");
setIsLoading(false);
}
};
// Render the subscription form and its containing elements.
return (
<Card className={styles.container}>
<form className={styles.form} onSubmit={handleSubmit}>
<Field
className={styles.field}
label="Email address"
validationState={status}
validationMessage={message}
>
<Input
contentBefore={<PersonMail20Regular />}
type="email"
value={email}
placeholder="Enter your email address..."
onChange={(event) => setEmail(event.target.value)}
required={true}
disabled={isSubscribed}
/>
</Field>
<Button
type="submit"
appearance="primary"
disabled={isSubscribed || isLoading}
>
{isLoading ? (
<Spinner size="tiny" label="Subscribing" />
) : (
"Subscribe"
)}
</Button>
</form>
</Card>
);
}
After creating the component, ensure that it's imported to the page.
For example, to display the form on the home page, import the NewsletterForm
component in page.js
located in the app
directory.
The complete source code is available on Github.