Build a Waitlist Page with Analytics

Ship a live waitlist page with PostHog analytics and a conversion funnel.

Introduction

30 Second Summary

Every app you use tracks whether you click, scroll, or bounce. Product engineers use this data to decide what to build next.

In this project, you will build a waitlist landing page, wire it up with product analytics, and deploy it live. You will measure your page's conversion rate to answer one question: is this page turning visitors into signups?

What You'll Build

A Next.js 16 waitlist landing page for a fictional newsletter called "Ship It," instrumented with PostHog analytics and deployed to Vercel.

By the end of this project, you'll have:

  • A polished landing page built with Next.js 16 and Tailwind CSS that captures email signups.
  • PostHog product analytics tracking every visitor interaction with custom event tracking.
  • A live deployment on Vercel accessible via a public URL with production analytics flowing.
  • A conversion funnel dashboard showing visits-to-signups, the same type of dashboard product engineers use daily.
  • Secret Mission: Add a feature flag A/B test that shows different headlines to different visitors, then measure which one converts better.

Are there any prerequisites?

You should be familiar with HTML, CSS, and JavaScript. You will also need a GitHub account.

Set Up Your Development Environment

To build your waitlist page, you need a JavaScript runtime that can execute code outside the browser. You also need a project framework that handles routing, styling, and deployment configuration out of the box.

In this step, you will install Node.js (the runtime) and scaffold a Next.js 16 project (the framework). By the end, you will have a running starter app on your local machine.

In this step, get ready to:
  • Install and verify Node.js v24 LTS.
  • Scaffold a Next.js 16 project with TypeScript and Tailwind CSS.
  • Start the development server and confirm the app loads.
Install Node.js v24 LTS

Node.js lets you run JavaScript outside the browser. Next.js requires Node.js v20.9 or higher to run. You will install v24 LTS, the current long-term support release.

  • In VS Code, open the built-in terminal by pressing Ctrl+` (macOS) or Ctrl+` (Windows). The backtick key is located above the Tab key.
  • Check if Node.js is already installed by running this command:
node --version

✔️ I see version 24 or higher

Node.js is ready. Confirm npm (the package manager bundled with Node.js) is also available by running:

npm --version

You should see a version number printed in the terminal. You are all set for the next substep.

ⓧ I see an older version

Your installed version of Node.js is below v24. You need to upgrade to avoid compatibility issues with Next.js 16.

  • Visit the Node.js download page in your browser.
  • Download the LTS installer for your operating system (the page auto-detects your OS).
  • Run the installer and accept all defaults. This will replace your existing Node.js version with v24.
  • Close and reopen your VS Code terminal so the new version is picked up.
  • Run node --version again to confirm you see v24 or higher.

Why does the old version linger?

Your operating system may cache the path to the old binary. Closing and reopening the terminal forces it to find the newly installed version.

ⓧ Command not found

Node.js is not installed on your machine. Let's fix that.

  • Visit the Node.js download page in your browser.
  • Download the LTS installer for your operating system (the page auto-detects your OS).
  • Run the installer and accept all defaults.
  • Close and reopen your VS Code terminal after the install finishes.
  • Run node --version to confirm you see v24 or higher.
  • Run npm --version to confirm the package manager is also available.

Still seeing command not found after installing?

Make sure you fully closed and reopened your terminal. If the problem persists, restart VS Code entirely.

On macOS, if you previously installed Node.js via a version manager like nvm, it may conflict with the standalone installer. Uninstall the version manager or use it to install v24 directly.

Help me fix my Node.js installation

Scaffold the Next.js project

Next.js is a React framework that handles routing, server rendering, and styling configuration for you. The create-next-app tool generates a fully configured project in seconds so you can skip manual setup.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your Next.js project by running this command:
npx create-next-app@latest waitlist-page

What will this command ask me?

The scaffold tool will prompt you with: "Would you like to use the recommended Next.js defaults?" Select Yes, use recommended defaults. This enables TypeScript, ESLint, Tailwind CSS, and App Router automatically.

If you see a prompt asking to install create-next-app, type y and press Enter to continue.

After a few seconds, you should see a success message in the terminal and a new waitlist-page folder on your Desktop.

  • Confirm the project folder was created by running:
ls

You should see waitlist-page listed in the output.

  • Navigate into your new project and start the development server by running these commands:
cd waitlist-page
npm run dev

What does npm run dev do?

This command starts a local development server using Turbopack. It watches your files for changes and automatically rebuilds the app as you edit code.

The server runs at http://localhost:3000 and stays running in your terminal until you stop it.

  • Open your browser and visit http://localhost:3000.

You should see the default Next.js starter page with the Next.js logo and links to documentation.

Don't see the starter page?

Check your terminal for error messages. If you see a port conflict, another process may already be using port 3000. Stop the other process or close the terminal and try again.

If you see module not found errors, make sure you ran the command from inside the waitlist-page folder. Run pwd to check your current directory.

Help me fix my dev server

Your development environment is ready. Next up, you will replace this default page with a polished waitlist landing page that captures email signups.

Build the Waitlist Landing Page

Your development environment is ready and the default Next.js starter page is running locally. Now it is time to replace that placeholder with something real: a focused landing page designed to convert visitors into email signups.

The page you build in this step is your product's first touchpoint with potential users. Every element on it serves a single purpose: get the visitor to enter their email. You will use Tailwind CSS utility classes to style the page and React state to handle the form interaction.

In this step, get ready to:
  • Replace the default homepage with a waitlist signup form.
  • Update the site metadata to match your newsletter branding.
  • Test the form locally to confirm the success message appears.
Replace the homepage

The default Next.js starter page is a demo showcasing the framework. You need a single-purpose landing page instead: a headline, a value proposition, and an email capture form.

  • Install the PostHog client library by running this command in your terminal (you will configure it in the next step):
npm install posthog-js

Why install this now?

The page component imports posthog-js to track signup events. Installing the package now means the import resolves without errors. The tracking calls will be harmless no-ops until you initialize PostHog in the next step.

  • Open app/page.tsx in the VS Code sidebar under the waitlist-page folder.
  • Select all the existing content and replace it with this code:
'use client'

import { useState } from 'react'
import posthog from 'posthog-js'

export default function Home() {
  const [email, setEmail] = useState('')
  const [submitted, setSubmitted] = useState(false)

  // Handle form submission: track the event and show success
  function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    posthog.capture('waitlist_signup', { email })
    setSubmitted(true)
    setEmail('')
  }

  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-8 bg-gradient-to-b from-slate-900 to-slate-800 text-white">
      <div className="max-w-2xl text-center space-y-8">
        <h1 className="text-5xl font-bold tracking-tight">
          Ship Products Users Actually Want
        </h1>
        <p className="text-xl text-slate-300">
          A weekly newsletter for product engineers who care about outcomes, not
          just outputs. Get real-world case studies, metrics frameworks, and
          build strategies.
        </p>
        {submitted ? (
          <div className="bg-green-900/50 border border-green-500 rounded-lg p-6">
            <p className="text-green-300 text-lg font-medium">
              You are on the list! We will be in touch soon.
            </p>
          </div>
        ) : (
          <form
            onSubmit={handleSubmit}
            className="flex flex-col sm:flex-row gap-4 justify-center"
          >
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="your@email.com"
              required
              className="px-6 py-3 rounded-lg bg-slate-700 border border-slate-600 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full sm:w-80"
            />
            <button
              type="submit"
              className="px-8 py-3 rounded-lg bg-blue-600 hover:bg-blue-500 font-semibold transition-colors"
            >
              Join the Waitlist
            </button>
          </form>
        )}
        <p className="text-sm text-slate-500">
          Join 0 other product engineers. No spam, unsubscribe anytime.
        </p>
      </div>
    </main>
  )
}

What does this code do?

  • 'use client' tells Next.js this component runs in the browser (needed for React state and event handlers).
  • useState tracks two pieces of data: the email the visitor types, and whether the form has been submitted.
  • handleSubmit prevents the default page reload, fires a tracking event, flips the submitted flag to true, and clears the input field.
  • The ternary in the JSX shows either the form (when not submitted) or a green success banner (when submitted).
  • Tailwind classes like bg-gradient-to-b from-slate-900 to-slate-800 create the dark gradient background without writing custom CSS.
  • Save app/page.tsx.

Seeing a module not found error?

If your terminal shows an error about posthog-js not being found, make sure you ran npm install posthog-js inside the waitlist-page folder first.

Also confirm your terminal is in the waitlist-page directory (check the path shown in your terminal prompt). Help me fix a module not found error in my Next.js project

Update the site metadata

The page title and description that appear in browser tabs and search results still say "Next.js." Updating the metadata makes your page look professional when shared.

  • Open app/layout.tsx in the VS Code sidebar.
  • Select all the existing content and replace it with this code:
import type { Metadata } from 'next'
import './globals.css'

// Set the page title and description for browser tabs and search results
export const metadata: Metadata = {
  title: 'Ship It - Product Engineering Newsletter',
  description: 'A weekly newsletter for product engineers who care about outcomes, not just outputs.',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

What does this code do?

  • The metadata export sets the page title (shown in the browser tab) and description (shown when the link is shared on social media or in search results).
  • The RootLayout wraps every page in your app with the basic HTML structure. Removing the default font imports and extra styles keeps the layout clean for Tailwind to handle all styling.
  • Save app/layout.tsx.
  • Check your browser tab. It should now display "Ship It - Product Engineering Newsletter" as the page title.

Browser tab still showing the old title?

Hard-refresh the page with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows). Layout changes sometimes require a full reload rather than a hot-module update. Help me fix my layout metadata not updating

✔️ Awesome, I've got everything!

Great. Make sure both files are saved before moving on.

ⓧ I'd like to double check the full code

Here is the complete code for both files at this point.

'use client'

import { useState } from 'react'
import posthog from 'posthog-js'

export default function Home() {
  const [email, setEmail] = useState('')
  const [submitted, setSubmitted] = useState(false)

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    posthog.capture('waitlist_signup', { email })
    setSubmitted(true)
    setEmail('')
  }

  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-8 bg-gradient-to-b from-slate-900 to-slate-800 text-white">
      <div className="max-w-2xl text-center space-y-8">
        <h1 className="text-5xl font-bold tracking-tight">
          Ship Products Users Actually Want
        </h1>
        <p className="text-xl text-slate-300">
          A weekly newsletter for product engineers who care about outcomes, not
          just outputs. Get real-world case studies, metrics frameworks, and
          build strategies.
        </p>
        {submitted ? (
          <div className="bg-green-900/50 border border-green-500 rounded-lg p-6">
            <p className="text-green-300 text-lg font-medium">
              You are on the list! We will be in touch soon.
            </p>
          </div>
        ) : (
          <form
            onSubmit={handleSubmit}
            className="flex flex-col sm:flex-row gap-4 justify-center"
          >
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="your@email.com"
              required
              className="px-6 py-3 rounded-lg bg-slate-700 border border-slate-600 text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full sm:w-80"
            />
            <button
              type="submit"
              className="px-8 py-3 rounded-lg bg-blue-600 hover:bg-blue-500 font-semibold transition-colors"
            >
              Join the Waitlist
            </button>
          </form>
        )}
        <p className="text-sm text-slate-500">
          Join 0 other product engineers. No spam, unsubscribe anytime.
        </p>
      </div>
    </main>
  )
}
import type { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
  title: 'Ship It - Product Engineering Newsletter',
  description: 'A weekly newsletter for product engineers who care about outcomes, not just outputs.',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
Test the form locally

The code is in place. Time to confirm the form works end-to-end in your browser.

  • Switch to your browser and visit http://localhost:3000.
  • You should see a dark gradient page with the headline "Ship Products Users Actually Want" and an email input form.
  • Type a test email (like test@example.com) into the email field.
  • Click Join the Waitlist.
  • Verify that a green success banner appears saying "You are on the list! We will be in touch soon."

Page still showing the default Next.js starter?

Make sure you saved app/page.tsx (check for the dot indicator on the file tab in VS Code, which means unsaved changes). Also confirm your dev server is still running in the terminal. If you stopped it, run npm run dev again from the waitlist-page directory.

Help me troubleshoot why my waitlist page is not showing up

Your landing page is live locally and the form works. Next up, you will wire it to PostHog so every signup is tracked as a real analytics event.

Integrate PostHog Product Analytics

Your landing page looks great, but right now you have no idea if it works. Is anyone signing up? How many visitors leave without entering their email?

These are the questions product engineers answer with analytics. In this step, you will wire up PostHog to track every visitor interaction on your page. PostHog is a product analytics platform with a generous free tier of 1,000,000 events per month.

In this step, get ready to:
  • Create a PostHog account and grab your project token.
  • Install the PostHog SDK and configure environment variables.
  • Verify that signup events appear in your PostHog dashboard.
Create a PostHog account and get your project token

PostHog needs a project token to know where to send your events. You will create a free account and find this token in your project settings.

  • Open https://us.posthog.com/signup in your browser.
  • Sign up with your email or GitHub account. No credit card is required.
  • Complete the onboarding prompts. PostHog creates a Default Project for you automatically.
  • Once you land in your PostHog dashboard, click Settings in the left sidebar.
  • Select Project, then General.
  • Find the Project token field (it starts with phc_). Copy this value and keep it handy.

What is a project token?

The project token is a write-only identifier that tells PostHog which project incoming events belong to. It is safe to use in client-side code because it can only send data to PostHog, not read anything back.

What about the API host?

Your API host is https://us.i.posthog.com for US Cloud accounts. You will use this alongside your project token in the next substep.

Install and initialize PostHog

With your token in hand, you can install the PostHog JavaScript SDK and tell your Next.js app how to initialize it on every page load.

  • Install the PostHog SDK by running this command in your VS Code terminal:
npm install posthog-js

Seeing a permission error?

Make sure your terminal is inside the waitlist-page folder. Run cd waitlist-page if you navigated elsewhere.

Help me fix this permission error.

  • Create the environment variables file by creating a new file called .env.local in the root of your waitlist-page folder (the same level as package.json), then paste the following:
NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=your-project-token-here
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
  • Replace your-project-token-here with the project token you copied from PostHog (the one starting with phc_).

What do these variables do?

Next.js exposes any environment variable prefixed with NEXT_PUBLIC_ to client-side code. NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN identifies your project. NEXT_PUBLIC_POSTHOG_HOST tells the SDK where to send events.

Storing these in .env.local keeps them out of your source code and makes it easy to swap values for different environments later.

  • Create the PostHog initialization file by creating a new file called instrumentation-client.ts in the root of your project (same level as .env.local), then paste the following code:
import posthog from 'posthog-js'

posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
  api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
  defaults: '2026-01-30'
})

What does this file do?

Next.js automatically runs instrumentation-client.ts once on every page load. This makes it the ideal place to initialize PostHog so that tracking starts immediately without wrapping your entire app in a provider component.

The defaults: '2026-01-30' option activates PostHog's latest default settings, including automatic pageview tracking for single-page app navigation.

  • Save both .env.local and instrumentation-client.ts.

✔️ Awesome, I've got everything!

Great. Double check that you have saved both files in the root of your project (next to package.json).

ⓧ I'd like to double check the full code

Here is .env.local:

NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=your-project-token-here
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com

Here is instrumentation-client.ts:

import posthog from 'posthog-js'

posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN!, {
  api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
  defaults: '2026-01-30'
})
Add custom event tracking

Your app/page.tsx already imports posthog and calls posthog.capture('waitlist_signup', { email }) when the form is submitted. Now that PostHog is installed and initialized, this call will start sending real events.

All that remains is restarting the dev server (so it picks up the new environment variables) and confirming the events arrive.

  • Stop your dev server by pressing Ctrl+C in the terminal.
  • Restart it by running:
npm run dev
  • Open http://localhost:3000 in your browser.
  • Enter a test email address (like test@example.com) and click Join the Waitlist.
  • Confirm you see the green success message.
  • Switch back to your PostHog browser tab.
  • Click Activity in the left sidebar.
  • Look for a waitlist_signup event in the list. It should appear within a few seconds.

What should I see?

The Activity tab shows a live feed of events. You should see at least one $pageview event (captured automatically when you loaded the page) and one waitlist_signup event (captured when you submitted the form).

Expanding the waitlist_signup event shows the email property you passed in.

Not seeing any events?

  • Make sure you restarted the dev server after creating .env.local. Next.js only reads environment files on startup.
  • Check that instrumentation-client.ts is in the root of your project (the same folder as package.json), not inside the app folder.
  • Open your browser's DevTools console and look for network requests going to us.i.posthog.com. If you see none, double-check that your token in .env.local matches the one from PostHog settings.

Help me debug why PostHog events are not appearing in my Activity tab.

Your page is now sending real analytics data to PostHog every time someone visits or signs up. Next up, you will deploy your page live to the internet so real visitors can find it.

Deploy to Vercel

Your waitlist page works locally. Now it needs to reach real users.

In this step, you will push your code to GitHub and deploy it to Vercel. Vercel auto-detects Next.js projects and gives you a live public URL in minutes.

In this step, get ready to:
  • Push your project to a GitHub repository.
  • Deploy to Vercel with your environment variables configured.
  • Verify PostHog receives events from your live production URL.
Push to GitHub

Vercel deploys from a Git repository. You will create a GitHub repository and push your code so Vercel can pull it automatically on every future commit.

  • Initialize a Git repository in your project by running this command:
git init
  • Stage and commit all your files by running these commands:
git add .
git commit -m "Initial commit"

What about .env.local?

The .gitignore file generated by create-next-app already excludes .env*.local files. Your PostHog token will not be committed to GitHub. You will add it separately in Vercel's settings instead.

  • Open GitHub in your browser at https://github.com.
  • In the upper-right corner, click the + menu and select New repository.
  • Enter waitlist-page as the repository name.
  • Choose Public or Private visibility.
  • Leave all other options unchecked (no README, no .gitignore, no license).
  • Click Create repository.

GitHub will show a Quick Setup page with commands for pushing an existing repository.

  • Copy the remote URL from the Quick Setup page and add it as your remote by running:
git remote add origin [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/waitlist-page.git"]]
  • Push your code to GitHub by running:
git branch -M main
git push -u origin main

What do these commands do?

The git branch -M main command renames your default branch to main. The git push -u origin main command uploads your code to GitHub and links your local branch to the remote.

  • Refresh your GitHub repository page. You should see all your project files listed.

Push failed or permission denied?

If you see a permission error, make sure you are signed into GitHub in your terminal. You may need to authenticate with a personal access token or SSH key.

If the push hangs, check your internet connection and try again.

Help me fix my git push to GitHub.

Deploy with Vercel

Vercel connects directly to your GitHub repository. Every time you push new code, Vercel rebuilds and redeploys automatically.

  • Open https://vercel.com in your browser.
  • Click Sign Up (or Log In if you already have an account).
  • Select Continue with GitHub to connect your GitHub account.
  • Authorize Vercel to access your GitHub repositories when prompted.

What is Vercel?

Vercel is a deployment platform built for frontend frameworks. It gives you automatic HTTPS, a global CDN, and zero-config deploys for Next.js projects. The free Hobby plan includes 100 deployments per day with no credit card required.

  • On your Vercel dashboard, click Add New... and select Project.
  • Find waitlist-page in the repository list and click Import.

Vercel will show you a configuration page. You need to add your environment variables here before deploying.

  • Expand the Environment Variables section on the configuration page.
  • Add your first variable with the name NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN and paste your PostHog project token as the value.
  • Add a second variable with the name NEXT_PUBLIC_POSTHOG_HOST and set the value to https://us.i.posthog.com.
  • Click Deploy.

Why add environment variables here?

Your .env.local file is not committed to GitHub (it is in .gitignore). Vercel needs its own copy of these values to inject them at build time. This keeps your credentials out of your repository while still making them available in production.

Vercel will build and deploy your project. This usually takes 30 to 60 seconds.

  • Wait for the deployment to complete. You will see a "Congratulations" screen with a preview of your live site.
  • Copy your deployment URL from the Vercel dashboard. It will look something like waitlist-page.vercel.app.

Build failed?

Check that both environment variables are spelled exactly as shown: NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN and NEXT_PUBLIC_POSTHOG_HOST. A missing NEXT_PUBLIC_ prefix will cause the values to be undefined at build time.

If you see a TypeScript error, double-check your code matches the files in the previous steps.

Help me debug my Vercel deployment failure.

Verify the live deployment

Your page is live on the internet. The final check is confirming that PostHog receives events from your production URL.

  • Open your deployed URL in a new browser tab.
  • Enter a test email address and click Join the Waitlist.
  • Confirm you see the success message: "You are on the list! We will be in touch soon."
  • Switch to your PostHog dashboard tab.
  • Go to the Activity page.
  • Look for a waitlist_signup event with a URL matching your Vercel deployment (not localhost).

No events appearing from production?

PostHog events can take a few seconds to appear. Refresh the Activity page after 10 to 15 seconds.

If events still do not appear, check that both environment variables are set correctly in your Vercel project settings. You can find them under Settings > Environment Variables in your Vercel project dashboard. After updating variables, you need to redeploy for changes to take effect.

Help me figure out why PostHog is not receiving events from my Vercel deployment.

Your page is live and tracked. Next up, you will build a product analytics dashboard that turns these raw events into actionable insights.

Build Your Product Analytics Dashboard

Your waitlist page is live and sending real events to PostHog. Raw events are useful, but they do not answer the question every product engineer needs answered: "is my page converting visitors into signups?"

In this step, you will build a dashboard that turns raw event data into a clear answer. You will create a conversion funnel showing how many visitors become signups, a trend chart tracking signups over time, and a single dashboard that pulls it all together.

In this step, get ready to:
  • Create a conversion funnel insight.
  • Create a trends insight with a breakdown.
  • Assemble a shareable dashboard.
Create a conversion funnel insight

A funnel measures how many people progress from one event to another. Your funnel will have two steps: landing on the page (Pageview) and submitting the form (waitlist_signup). The gap between those two numbers is your conversion rate.

  • In PostHog, click Product Analytics in the left sidebar.
  • Click the + New Insights tab at the top.
  • Select Funnel from the dropdown.

PostHog shows an empty funnel with a placeholder for your first step.

  • For the first step, search for and select the Pageview event. This represents every visitor who lands on your waitlist page.
  • Click Add step to add a second step.
  • For the second step, search for and select the waitlist_signup event. This represents visitors who submitted the form.
  • Click Save in the top right to save this insight.

You should see a funnel visualization showing how many visitors completed each step. The percentage between the two steps is your conversion rate.

What is a conversion funnel?

A conversion funnel measures how many users progress through a sequence of events. Each step filters down to only users who completed the previous step.

In your case, if 100 people view the page and 5 submit the form, your conversion rate is 5%. Product engineers use this number daily to decide whether a page needs improvement.

Don't see your waitlist_signup event in the dropdown?

PostHog only shows events it has received. Make sure you submitted at least one test signup on your live Vercel URL in the previous step. Events can take a few seconds to appear.

Why can't PostHog find my waitlist_signup event?

Create a trend insight

A trend shows how an event changes over time. This lets you answer "are signups going up or down?" at a glance.

  • Click Product Analytics in the left sidebar again.
  • Click the New insight button in the top right. Trends is the default insight type, so this creates a trends insight automatically.
  • In the event selector, search for and select the waitlist_signup event.
  • Click + Add breakdown below the event.
  • Search for Referring Domain and select it. This splits your signups by where visitors came from (Google, Twitter, direct traffic, etc.).
  • Click Save in the top right to save this insight.

You should see a line chart showing your waitlist_signup events over time, broken down by referring domain.

Why add a breakdown by referring domain?

Breakdowns segment your data by a property so you can compare groups. Referring domain tells you where your traffic is coming from.

If most signups come from Twitter but none from Google, you know where to focus your marketing. This is the kind of data-driven decision product engineers make every day.

Assemble your dashboard

Individual insights are useful, but a dashboard puts them side by side so you can get the full picture in one glance. This is what you would present to your team in a product review.

  • Click Dashboards in the left navigation.
  • Click New Dashboard.
  • Select Blank Dashboard.
  • Name it Waitlist Performance and press Enter or click Save.

Now add your two insights to this dashboard.

  • Navigate back to Product Analytics in the left sidebar.
  • Open your saved Funnel insight.
  • Click Add to dashboard and select your Waitlist Performance dashboard.
  • Go back to Product Analytics and open your saved Trends insight.
  • Click Add to dashboard and select Waitlist Performance again.

Navigate back to Dashboards and open Waitlist Performance. You should see both insights displayed side by side.

What is this dashboard for?

This is the same type of dashboard product engineers check every morning. The funnel tells you whether the page is working. The trend tells you whether things are improving or getting worse.

Together they answer: "Is my page converting visitors into signups, and is that trend going in the right direction?"

Share your dashboard with a link

Click the Share button in the top right of your dashboard to get a shareable link. This is what you would send to your team in a real product review.

Dashboard looks empty?

Make sure you saved both insights before trying to add them to the dashboard. Unsaved insights do not appear in the "Add to dashboard" list.

Help me troubleshoot my empty PostHog dashboard

Secret mission

A/B Test Your Headline with Feature Flags

You have a live page and real analytics. But how do you know your headline is the best one? Product engineers never guess. They test. In this secret mission, you will create a feature flag that shows a different headline to 50% of visitors, then measure which one converts better.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses free tiers for all services (PostHog, Vercel Hobby, GitHub), so there are no ongoing costs.

Resources you used:

  • Local waitlist-page project folder on your Desktop.
  • GitHub repository (waitlist-page).
  • Vercel project (hosting your deployed site).
  • PostHog project (analytics dashboard, feature flag, insights).
  • Node.js v24 LTS.

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your waitlist page and analytics.

  • Your Vercel site stays live at its public URL.
  • PostHog continues collecting analytics data within the free 1,000,000 events/month allowance.
  • Your local project files remain on your Desktop for further development.
  • Node.js stays installed on your machine for future projects.

Pause - I'll come back to this later

Stop running processes to free up memory. Your files and cloud resources stay intact for when you return.

  • In VS Code, open the terminal where your dev server is running and press Ctrl+C to stop it.
  • Your Vercel deployment stays live (no cost on the Hobby plan). No action needed there.
  • PostHog keeps your dashboard and insights saved. No action needed.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each item below to fully clean up.

  • Delete the local project folder. On macOS/Linux, run:
rm -rf ~/Desktop/waitlist-page

On Windows, run:

rmdir /s /q "$HOME\Desktop\waitlist-page"
  • Delete your GitHub repository. Go to your repository page on GitHub, click Settings, scroll down to the Danger Zone section, and click Delete this repository. Confirm the deletion by typing the repository name.
  • Delete your Vercel project. In the Vercel dashboard, open your waitlist-page project. Click Settings in the project navigation. Scroll to the bottom and click Delete Project. Confirm by typing the project name.
  • Delete your PostHog project. In PostHog, click Settings in the left sidebar. Scroll to the Danger Zone section at the bottom. Click Delete project and confirm.
  • Uninstall Node.js. On Windows, open Settings, select Apps, then Installed apps. Find Node.js and click Uninstall. On macOS, if you installed via the official installer, remove Node.js by running:
sudo rm -rf /usr/local/bin/node /usr/local/lib/node_modules /usr/local/include/node

If you installed Node.js using a version manager like nvm, use your version manager's uninstall command instead.

That's a Wrap!

That's a Wrap!

Nice work! You just shipped a live waitlist page, wired it up with product analytics, and built a dashboard that answers the question every product engineer asks: is this thing actually working?

You've learned how to:

  • Built a Next.js 16 landing page with Tailwind CSS and deployed it live to Vercel using GitHub integration.
  • Integrated PostHog product analytics with custom event tracking to capture every signup interaction.
  • Defined success metrics and built a conversion funnel dashboard to measure whether your page converts visitors into signups.
  • Secret Mission: Ran an A/B test with feature flags to optimize your headline and measure which variant converts better.

Ready to quiz yourself?