Building a “Coming Soon” Page with a Countdown in Next.js

Ever wanted to put up a sleek “Coming Soon” or “Under Maintenance” page while you prep your next big thing? Maybe you’ve got a new product launching, or your site is undergoing updates. Either way, creating a friendly, professional page can help manage expectations and keep your audience engaged.

Today, I’ll show you how to do precisely that using Next.js and the @heroui/react UI kit. We’ll create a stylish page with a countdown timer using Card and a custom Countdown component.

Let’s dive in!

Setting Up the Next.js Project

Creating the Page Layout

Let’s design the structure of the coming soon page.

Create a file: app/coming-soon/page.tsx

Here’s the basic layout:

"use client";
import { Card } from '@heroui/react';
import Countdown from '../../components/Countdown';

export default function CommingSoon() {
  return (
    <div className="min-h-screen bg-gray-100 flex items-center justify-center">
      <Card className="max-w-md w-full p-8 shadow-xl">
        <p className="mb-4 text-center">
          We're Launching Soon 🚀
        </p>
        <p className="mb-6 text-center text-gray-600">
          Our website is under maintenance. We're working hard to bring you a better experience.
        </p>
        <Countdown targetDate="2025-12-31T00:00:00" />
      </Card>
    </div>
  );
}

We’re using the Card component for a polished container. Inside, we place a heading and a short message using Typography Tailwind. The magic happens with the Countdown component, which we’ll build next.

Feel free to style this however you want! HeroUI gives you flexibility with utility classes and their props.

Creating the Countdown Component

Let’s now create our countdown timer.

Make a new file at components/Countdown.js:

import { useEffect, useState } from 'react';

type CountdownProps = {
  targetDate: string | Date;
};

type TimeLeft = {
  days: number;
  hours: number;
  minutes: number;
  seconds: number;
};

export default function Countdown({ targetDate }: CountdownProps) {
  const calculateTimeLeft = (): TimeLeft => {
    const difference = new Date(targetDate).getTime() - new Date().getTime();

    const timeLeft: TimeLeft = {
      days: Math.floor(difference / (1000 * 60 * 60 * 24)),
      hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
      minutes: Math.floor((difference / 1000 / 60) % 60),
      seconds: Math.floor((difference / 1000) % 60),
    };

    return timeLeft;
  };

  const [timeLeft, setTimeLeft] = useState<TimeLeft>(calculateTimeLeft());

  useEffect(() => {
    const timer = setInterval(() => {
      setTimeLeft(calculateTimeLeft());
    }, 1000);

    return () => clearInterval(timer);
  }, [targetDate]);

  return (
    <div className="grid grid-cols-4 gap-2 text-center">
      {Object.entries(timeLeft).map(([unit, value]) => (
        <div key={unit}>
          <div className="text-2xl font-bold">{value}</div>
          <div className="text-sm uppercase text-gray-500">{unit}</div>
        </div>
      ))}
    </div>
  );
}

This simple component takes a targetDate and displays days, hours, minutes, and seconds. It updates every second using setInterval.

Each time unit is styled with Tailwind. It’s readable, elegant, and works across devices.

Coming soon page

Tips to Make It Shine

Now that your base is ready, let’s polish it up.

1. Make It Responsive

Use Tailwind’s responsive classes:

<Card className="w-full max-w-md sm:max-w-lg p-8 shadow-xl">

This makes the card adjust beautifully on small screens.

responsive

2. Add a Logo or Image

Add this before the heading:

"use client";
import { Card, CardBody, CardHeader, Image } from "@heroui/react";
import Countdown from "../../components/Countdown";

export default function CommingSoon() {
  return (
    <div className="min-h-screen bg-gray-100 flex items-center justify-center">
      <Card className="w-full max-w-md sm:max-w-lg p-8 shadow-xl">
        <CardHeader className="pb-0 pt-2 px-4 flex-col items-start py-2">
          <p className="mb-4 text-center text-2xl font-bold text-gray-800">
            We're Launching Soon 🚀
          </p>
          <p className="mb-6 text-center text-lg text-gray-600 leading-relaxed">
            Our website is under maintenance. We're working hard to bring you a
            better experience.
          </p>
          <Countdown targetDate="2025-12-31T00:00:00" />
        </CardHeader>
        <CardBody className="overflow-visible py-2">
          <Image
            alt="HeroUI hero Image"
            src="https://heroui.com/images/hero-card-complete.jpeg"
            width={300}
          />
        </CardBody>
      </Card>
    </div>
  );
}

You can replace this with your brand’s logo or even a coming soon graphic.

Image

3. SEO Optimization

4. Add a Contact or Notify Form

You can integrate a form using tools like Formspree or ConvertKit. A simple form might look like:

"use client";
import { Card, CardBody, CardHeader, Image } from "@heroui/react";
import Countdown from "../../components/Countdown";
import { useState } from "react";

export default function CommingSoon() {
  const [email, setEmail] = useState("");
  const [submitted, setSubmitted] = useState(false);

  const handleSubmit = (e: any) => {
    e.preventDefault();
    console.log("Subscribing email:", email);
    setSubmitted(true);
    setEmail("");
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-gray-100 via-blue-100 to-indigo-100 flex items-center justify-center py-10">
      <Card className="w-full max-w-md sm:max-w-lg rounded-xl shadow-xl overflow-hidden bg-white/90 backdrop-blur-sm">
        <CardHeader className="relative pt-10 pb-8 px-8 sm:px-16 flex justify-center">
          <h2 className="mt-4 text-center text-3xl font-semibold text-gray-900">
            Coming Soon
          </h2>
          <p className="mt-2 text-center text-gray-600 leading-relaxed font-light">
            We're brewing something amazing. Stay tuned!
          </p>
        </CardHeader>
        <CardBody className="p-8 sm:px-16 pb-8">
          <div className="relative rounded-lg overflow-hidden shadow-md aspect-w-16 aspect-h-9 mb-6">
            <Image
              alt="Modern abstract graphic representing launch"
              src="https://images.unsplash.com/photo-1518051870910-a46e30d9db16?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHwwfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80"
              className="object-cover w-full h-full"
            />
            <div className="absolute inset-0 bg-black/10" aria-hidden="true" />
          </div>
          <div className="mb-8">
            <Countdown
              targetDate="2025-12-31T00:00:00"
            />
          </div>
          <div className="w-full text-center">
            <h3 className="text-xl font-semibold text-gray-900 mb-4">
              Get Notified
            </h3>
            <p className="text-gray-600 mb-6 font-light">
              Be the first to know when we launch.
            </p>
            {!submitted ? (
              <form
                onSubmit={handleSubmit}
                className="flex flex-col sm:flex-row gap-3"
              >
                <input
                  type="email"
                  placeholder="Enter your email"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  required
                  className="flex-grow p-3 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-all duration-200"
                />
                <button
                  type="submit"
                  className="w-full sm:w-auto px-6 py-3 bg-indigo-600 text-white font-semibold rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-200"
                >
                  Subscribe
                </button>
              </form>
            ) : (
              <p className="text-green-600 font-medium">
                You're on the list! 🎉
              </p>
            )}
          </div>
        </CardBody>
      </Card>
    </div>
  );
}
Notify
Subscribe

Finally

You’ve just built a fully functional Coming Soon page using Next.js and @heroui/react. You used Cards and Typography Tailwind to create a clean layout, and a custom Countdown component to give it purpose and urgency.

Now, whenever your site is under maintenance or you’re gearing up for a launch, you’re ready.

Don’t forget — branding matters. You can add your brand colors, fonts, and personal touches to make this page your own.

Feel free to bookmark this tutorial and come back to it when needed.

Next.js Coming Soon Page Infographic

Building a Next.js Countdown “Coming Soon” Page

1

Project Setup & Page Layout

Goal: Establish the base Next.js page (e.g., `/coming-soon/page.tsx`).

Key Technologies: Next.js, @heroui/react Card component, and Tailwind CSS for initial centering and styling.

2

Create the Countdown Component

Goal: Calculate the remaining time in real-time.

Implementation: Create a reusable component (`Countdown.js`). Use React’s useState for time data and useEffect with setInterval(..., 1000) to re-calculate the difference every second until the `targetDate` is reached.

3

Render Time Units

Goal: Present the calculated time clearly (Days, Hours, Minutes, Seconds).

Implementation: Use a simple grid layout and map over the calculated time object to display each unit with bold numbers and descriptive labels, ensuring responsive styling with Tailwind.

4

Enhancements & SEO

Goal: Maximize impact and engagement before launch.

Polish: Ensure responsiveness across devices, add branding (logo/image). Engagement: Integrate an email subscribe form to capture leads. Visibility: Optimize for SEO.

This article was originally published on Medium.

Leave a Comment

Your email address will not be published. Required fields are marked *