
Managing Content on Small Websites: Guide for 2025 & Beyond
22 Nov 2022
Customers are central to your SaaS. While your service is still under development, you can keep potentially interested customers engaged and excited about its launch through a waitlist.
A SaaS waitlist is a lightweight customer relationship management, lead generation, and marketing tool that collects customer contact information in the pre-launch phase of your service. It outlines key updates and news about your service leading up to its launch, details upcoming features, links to social media for external engagement, and can even spur fundraising by demonstrating demand for your product.
These are some ways a waitlist can be useful for your SaaS:
Waitlists keep your customers interested through product updates as they anticipate its launch.
With a waitlist, you get a pool of users willing to test your service and give you actionable feedback.
Through early adopters, you can gauge your product’s reception, see what features users like, and assess pricing strategies to which they may be amenable to.
A waitlist is a community-building tool that gathers enthusiastic customers who will more likely evangelize it, knowledge-share with, and encourage newer users to try it and be a valuable source of feedback post-launch.
A waitlist is essential in marketing and lead generation to promote future features, communicate new offerings of your existing service, or sell separate products you may develop later.
The tangible interest in your service, backed up by a brimming waitlist, shows potential investors that there is indeed demand in the market for it, that you have a ready user base, and that you have secured possible committed revenue. It gives the impression that your service is a sound investment.
For all these reasons, a product waitlist can help your business. But for a product launch, it is not enough to have a waitlist page, it is important to know how to build a high-converting SaaS waitlist.
In this guide, I will build a waitlist for a fictional language-learning app called Lingua Mate. It will collect potential customer names and emails and will provide pre-launch updates on the service. Its front end is built with Next.js and BCMS acts as its back end.
To follow along, you will need:
Node.js installed.
A BCMS account.
Here, you will find all the necessary steps. Let's go.
On the BCMS dashboard, click the “Create new project +” button or visit the “Create new project” page. A project is a collection of templates, entries, widgets, and media, among other things, used to manage your content. Put in the project name as “Lingua Mate Waitlist”.
On BCMS, a template describes the structure of your content. In this case, it defines customer and news content structures used to make entries of each.
On the dashboard, under “Administration”, navigate to the “Templates” page. Create two templates, “Customer” and “News”, by clicking the “Create new template +” button.
The Customer template defines a customer. The News template defines the news and updates about Lingua Mate.
The Customer template has two extra properties aside from the default title and slug:
Name: String, Required
Email: String, Required
The News template has one extra property:
Body: Rich Text, Required
A BCMS Entry is an individual record modeled after a template. Once you’ve created the News template, go to its equivalent “Entries” page, click the “Create new entry” button, and add about three update entries.
API keys enable you to post, fetch, and manage content from your BCMS project on your app with varying permissions for each template you create. Under “Project Settings”, select the “API keys” tab and then click the “Add a new key +” button. Provide a name and description and add the key.
Under “Template permissions”, select “Can create” for the Customer template and “Can get” for the News template. Leave this page open as you will copy these API key values to the Next.js project.
The Next.js app is the frontend of the waitlist. On it, customers enter their emails and names through a form, which is then posted to BCMS. You will list news and updates of the SaaS on it as well.
Create the app on your terminal using the command below.
npx create-next-app@latest
Use these values to answer the prompts from create-next-app:
After the project setup is completed, install these dependencies:
npm i --save @thebcms/cli @thebcms/client @thebcms/components-react @headlessui/react yup formik
This is what each of the dependencies does:
Update your package.json
scripts object to this so that the BCMS CLI tool can pull types for your project.
"scripts": { "dev": "bcms --pull types --lng ts && next dev", "build": "bcms --pull types --lng ts && next build", "start": "bcms --pull types --lng ts && next start", "lint": "bcms --pull types --lng ts && next lint" }
The types are saved in /bcms/types/.
Create a .env file
and place API key values from before in it.
BCMS_ORG_ID="Your BCMS_ORG_ID here" BCMS_INSTANCE_ID="Your BCMS_INSTANCE_ID here" BCMS_API_KEY_ID="Your BCMS_API_KEY_ID here" BCMS_API_KEY_SECRET="Your BCMS_API_KEY_SECRET here"
Create a bcms.config.cjs
file to hold configuration for the BCMS packages. Place the API key values here as well.
module.exports = { client: { orgId: "your orgId here", instanceId: "your instanceId here", apiKey: { id: "your api key id here", secret: "your api key secret here", }, }, }
Update the TypeScript configuration in the tsconfig.json file.
Change the moduleResolution
to node.
Create a BCMS client that you will use to post customer information and fetch news updates. Make a file called app/lib/bcms.ts
and in it place the code below:
import { Client } from "@thebcms/client" const getBCMSClient = () => { const bcmsOrgId = process.env.BCMS_ORG_ID const bcmsInstanceId = process.env.BCMS_INSTANCE_ID const bcmsApiKeyId = process.env.BCMS_API_KEY_ID const bcmsApiKeySecret = process.env.BCMS_API_KEY_SECRET if (bcmsOrgId && bcmsInstanceId && bcmsApiKeyId && bcmsApiKeySecret) { return new Client( bcmsOrgId, bcmsInstanceId, { id: bcmsApiKeyId, secret: bcmsApiKeySecret, }, { injectSvg: true, } ) } throw new Error("BCMS organization ID, instance ID, API key ID or secret is missing") } export const bcms = getBCMSClient()
Create another file, app/lib/schemas.ts
, to hold schemas to validate the customer input in the customer information capture form. Place this in it.
import * as Yup from "yup" export const CustomerSchema = Yup.object().shape({ name: Yup.string() .min(2, 'Too Short!') .max(70, 'Too Long!') .required('Required'), email: Yup.string() .email('Invalid email') .required('Required'), })
In the app/globals.css file, add this styling. It adds a background to the body of the app.
body { color: var(--foreground); background: var(--background); font-family: Arial, Helvetica, sans-serif; background: radial-gradient(farthest-side at -33.33% 50%, #0000 52%, rgba(15, 15, 15, 0.55) 54% 57%, #0000 59%) 0 calc(60px/2), radial-gradient(farthest-side at 50% 133.33%, #0000 52%, rgba(15, 15, 15, 0.55) 54% 57%, #0000 59%) calc(60px/2) 0, radial-gradient(farthest-side at 133.33% 50%, #0000 52%, rgba(15, 15, 15, 0.55) 54% 57%, #0000 59%), radial-gradient(farthest-side at 50% -33.33%, #0000 52%, rgba(15, 15, 15, 0.55) 54% 57%, #0000 59%), #000000; background-size: calc(60px/4.667) 60px, 60px calc(60px/4.667); }
In app/layout.tsx
, change the metadata to this, to reflect the name and description of the SaaS and for SEO purposes.
export const metadata: Metadata = { title: "Lingua Mate: Language Learning App", description: "Your personal AI tutor is ready 24/7 to help you practice, perfect, and progress", }
Create a public/language.svg
file to use as the app logo. Add this to the file.
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> <path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802" /></svg>
Since the BCMS client requires a sensitive API key and secret to post customer information to BCMS and fetch updates, you’ll use server actions to perform these operations. Create a app/actions.ts
file and add this to it.
"use server" import { bcms } from "@/app/lib/bcms" import { NewsEntry } from "@/bcms/types/ts" // fetches news from BCMS const fetchNews = async () => { return (await bcms.entry.getAll("news")) as NewsEntry[] } // creates a customer given their email and name const createCustomer = (customer: { email: string, name: string }) => { return bcms.entry.create("customer", { meta: [{ lng: 'en', data: { ...customer, title: customer.name, slug: '' } }], statuses: [], content: [] }) } export { fetchNews, createCustomer }
The two actions here fetch news updates from BCMS and post customer information from the waitlist form.
Create the form component at app/components/WaitlistForm.tsx
and add this to the file
"use client" import { Field, Form, Formik } from "formik" import clsx from "clsx" import { Button } from "@headlessui/react" import { createCustomer } from "@/app/actions" import { CustomerSchema } from "@/app/lib/schemas" import { useState, ReactNode } from "react" const inputStyles = clsx( "block w-full rounded-lg border-none bg-white/10 py-1.5 px-3 text-sm/6 text-white", "focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data- [focus]:outline-white/25" ) const CustomError = ({ children }: { children: ReactNode }) => { return <div className="text-xs text-red-600 text-start h-[16px] my-1">{children}</div> } const WaitlistForm = () => { const [submitError, setSubmitError] = useState("") const [success, setSuccess] = useState(false) return <Formik initialValues={{ name: "", email: "", }} validationSchema={CustomerSchema} onSubmit={async (values, formikBag) => { const errors = await formikBag.validateForm() if (!errors.name && !errors.email) { createCustomer(values).then(() => { setSuccess(true) formikBag.resetForm() }).catch(() => { setSubmitError("There was a problem adding you to the waitlist.") })} else { setSubmitError("There was a problem with the name or email you entered. Please try again") } }}> {({ errors, touched, isValid }) => ( <Form className="flex flex-col my-3 justify-stretch max-sm:w-[300px] sm:w-[400px]"> <label> <Field id="name" name="name" placeholder="Your name" className={inputStyles} /> </label> <CustomError> {errors.name && touched.name ? errors.name : null} </CustomError> <label> <Field id="email" name="email" placeholder="Your email" type="email" className={inputStyles} /> </label> <CustomError> {errors.email && touched.email ? errors.email : null} </CustomError> <Button type="submit" className="inline-flex items-center gap-2 rounded-md bg-zinc-800 py-1.5 px-3 text-sm/6 font-semibold text-white shadow-inner shadow-white/10 focus:outline-none data-[hover]:bg-zinc-700 data-[open]:bg-zinc-800 data-[focus]:outline-1 data-[focus]:outline-white text-center mt-2 flex justify-center" disabled={!isValid} > Join the waitlist! </Button> <div className="text-gray-500 max-w-[300px] my-3 mx-auto"> For any queries, reach out to us on social media! </div> <div className={`font-light bg-red-500/10 bg-red-700/25 rounded-lg w-100 px-3 py-1 m-1 justify-between flex ${submitError ? "flex" : "hidden"}`}> <div>{submitError}</div> <Button className="ms-3" onClick={() => setSubmitError("")}>𝗫</Button> </div> <div className={`font-light bg-green-500/10 bg-green-700/25 rounded-lg w-100 py-1 px-3 m-1 justify-between ${success ? "flex" : "hidden"}`}> <div>You've successfully signed up for the waitlist.</div> <Button className="ms-3" onClick={() => setSuccess(false)}>𝗫</Button> </div> </Form>)} </Formik> } export default WaitlistForm
This component contains a form with two fields, one for the customer's email and another for their name. When the input is submitted, it is validated against the schema created earlier and then posted to BCMS. Any input validation or submission errors are reported.
Update the home page, app/page.tsx
, with the code below to include the customer information form and the news updates list.
import Image from "next/image" import WaitlistForm from "@/app/components/WaitlistForm" import { fetchNews } from "@/app/actions" import { BCMSContentManager } from "@thebcms/components-react" export default async function Home() { const news = await fetchNews(); return ( <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h- screen p-8 pb-20 gap-16 sm:p-20" style={{ fontFamily: "var(--font-geist-sans)" }} > <main className="flex flex-col gap-2 row-start-2 items-center text-center"> <div className="bg-zinc-700/30 rounded-xl px-6 py-1 text-orange-500">Coming soon! </div> <Image priority src="/language.svg" alt="Lingua Mate logo" width={70} height={70} className="rounded-full p-4 bg-orange-500" /> <h1 className="text-orange-500 text-4xl mb-4">Lingua Mate</h1> <h2 className="text-3xl mb-4 max-w-[600px]">Your personal AI tutor is ready 24/7 to help you practice, perfect, and progress.</h2> <h6 className="text-gray-500 max-w-[400px]">Join the waitlist to get early access to the product and receive updates on the progress!</h6> <WaitlistForm /> <div className="flex flex-col max-w-[600px] items-center gap-4"> <h2 className="text-3xl mt-4 max-w-[600px]">Updates</h2> <h6 className="text-gray-500 max-w-[400px]">Join the waitlist to get early access to the product and receive updates on the progress!</h6> {news.map((item, index) => { return <div key={`update-${index}`} className="bg-white/10 border border-white/15 p-4 rounded-2xl mb-2 text-start"> <span className="flex w-100 justify-between"> <p className="text-orange-500">{item.meta.en?.title}</p> <p className="text-orange-500 text-xs">{(new Date(item.updatedAt)).toLocaleDateString()}</p> </span> <span className="text-gray-300"> {item.meta.en?.body.nodes && <BCMSContentManager items={item.meta.en?.body.nodes} />} </span> </div> })} </div> </main> </div>) }
The news and updates are fetched from BCMS, and each is listed as a card. For every news item, there is a title and the date it was updated to accompany them.
Here is a screenshot of the finished homepage now.
SaaS waitlist example:
Eye-catching, consistent branding as seen in logos, color palettes, typography, and graphics easily captures customers' attention and draws them to the waitlist to see more of your product. A solid description tells potential customers what your service will deliver and the benefits they may get, to excite them to try it. In the sign-up form, include a strong call to action on the sign-up button to persuade them to be part of your exclusive waitlist. Links to social media added to the waitlist let customers further engage with and learn more about your brand on external platforms, increasing their trust and excitement for the upcoming launch.
Although not included in the project, adding feedback from early users who have tested your app, as well as links to third-party media like news organizations that gave your service positive coverage, can go a long way in building credibility. Including a launch date tracker that indicates major events that lead up to the launch date can create anticipation in your customers, exciting them to sign up. If already decided, clearly display the launch date for the service so that customers can know when to follow up and try your service.
Since the news entries are pulled from BCMS whenever the Next.js site is loaded, you can directly add new news entries on the dashboard under “Entries” and then “News”. They will automatically reflect on the site to new visitors.
If you have a larger team and people dedicated to customer relations, you can put them in charge of managing the waitlist emails. These can include team members like marketers, customer support or success specialists, or even salespeople. On the dashboard, under “Project Settings” in the “General Settings” and then “Manage Members”, you can grant them permission to access and manage the waitlist.
Now that your waitlist is complete, you can deploy it on platforms like Vercel, Netlify, or Render that support full-fledged Next.js apps. All you need to do is upload the source code and set the aforementioned environment variables and you are good to go. Follow these guides to deploy your app: Vercel, Netlify, or Render.
You don’t have to stop just here. Include other features on your waitlist, such as an FAQ, newsletter signup, perks for early customers like discounts, and feature lists for your service. You can even connect BCMS to an email marketing platform like Mailchimp or a customer relationship management system like Hubspot and pass the emails to them.
BCMS is a headless CMS with flexible content modeling. It offers composable content, media management, team collaboration, integrations, and extension tools, among other features. Learn more about BCMS and how to use it on its docs site.
Get all the latest BCMS updates, news and events.
By submitting this form you consent to us emailing you occasionally about our products and services. You can unsubscribe from emails at any time, and we will never pass your email to third parties.
There are many actionable insights in this blog post. Learn more: