DAILY NEWS

Stay Ahead, Stay Informed – Every Day

Advertisement

I built a startup waitlist landing page in Next.js 15 — here are all the decisions I made



I’ve been building Next.js templates as a side project and selling them on Gumroad. This weekend I shipped the fourth one: Orbit, a startup launch and waitlist landing page.

Here’s a breakdown of every technical decision I made.

Why Next.js 15 with CSS Modules (no Tailwind)

Most templates use Tailwind. That’s fine for customization, but it adds a compilation step and a learning curve for buyers who just want clean CSS they can read and edit.

CSS Modules give you:

Locally scoped class names (no conflicts)
Standard CSS syntax (no utility memorization)
Zero runtime cost
Works with Next.js out of the box

The tradeoff is more verbose than Tailwind for repetitive utilities. Worth it for a product you’re selling.

The bento grid — 1px gap trick

The features section uses CSS Grid with grid-template-columns: repeat(3, 1fr). The first card spans 2 columns via grid-column: span 2.

.bento {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(–color-border-subtle); /* gap IS the border */
border-radius: var(–radius-lg);
overflow: hidden;
}

.bento .card:first-child {
grid-column: span 2;
}

Enter fullscreen mode

Exit fullscreen mode

Instead of adding borders to each card, I set the grid’s background to the border color and use 1px gaps. The cards themselves have no borders. This gives perfectly consistent grid lines with zero extra markup.

Count-up animation with IntersectionObserver

The metrics section triggers a count-up when the section enters the viewport:

const observer = new IntersectionObserver(((entry)) => {
if (entry.isIntersecting && !started.current) {
started.current = true
const startTime = performance.now()

const tick = (now: number) => {
const progress = Math.min((now – startTime) / duration, 1)
const eased = 1 – Math.pow(1 – progress, 3) // cubic ease-out
setCount(Math.round(eased * end))
if (progress 1) requestAnimationFrame(tick)
}

requestAnimationFrame(tick)
}
}, { threshold: 0.4 })

Enter fullscreen mode

Exit fullscreen mode

The started ref prevents re-triggering if the user scrolls away and back. Cubic ease-out feels much more natural than linear. No library — 30 lines of TypeScript.

Infinite logo marquee (CSS-only)

.row {
display: flex;
gap: 64px;
width: max-content;
animation: marquee 24s linear infinite;
}

@keyframes marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}

Enter fullscreen mode

Exit fullscreen mode

The key: duplicate the logos array in the component and animate exactly -50% (half the total width). Seamless loop. Edge fade via mask-image on the parent:

.track {
mask-image: linear-gradient(
to right,
transparent 0%, black 12%, black 88%, transparent 100%
);
}

Enter fullscreen mode

Exit fullscreen mode

Single content file

All editable content — name, copy, nav links, logos, features, metrics, testimonials, FAQ — lives in src/lib/constants.ts. The buyer touches one file and the whole page updates. No hunting through components.

Design tokens in globals.css

8 variables to rebrand the entire template:

:root {
–color-accent: #f59e0b; /* change this → full rebrand */
–color-bg: #09090b;
–font-display: ‘Sora’, sans-serif;
–font-body: ‘IBM Plex Sans’, sans-serif;
–radius-lg: 16px;
}

Enter fullscreen mode

Exit fullscreen mode

Connecting the waitlist form

The form ships with a simulated delay. Replace it with your stack:

/* ConvertKit */
await fetch(`https://api.convertkit.com/v3/forms/${FORM_ID}/subscribe`, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ api_key: KEY, email }),
})

/* Loops */
await fetch(‘https://app.loops.so/api/v1/contacts/create’, {
method: ‘POST’,
headers: { Authorization: `Bearer ${KEY}`, ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ email }),
})

Enter fullscreen mode

Exit fullscreen mode

Live demo: https://orbit-landing-iota.vercel.app/

The template is available on Gumroad for $29: https://devmaya.gumroad.com/l/orbit



Source link

Leave a Reply

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