DEV Community

Cover image for I built a bio link tool that actually sells — here's the full stack behind SellBio
Adonai Dominguez
Adonai Dominguez

Posted on

I built a bio link tool that actually sells — here's the full stack behind SellBio

Most bio link tools are just... lists of links. You click, you leave, nothing happens. No lead capture, no funnels, no analytics that actually matter.

I wanted something different — a bio link that works like a sales funnel. So I built SellBio.

What is SellBio?

SellBio is a funnel/bio-link builder SaaS. Users create flows (linear or branching screen sequences with lead capture, quizzes, VSLs) and a public store (their bio-link page). Think Linktree meets ClickFunnels, but lightweight and designed for creators.

Live at: sellb.io

Here's what it looks like in action:

  • A creator puts their SellBio link in their Instagram bio
  • A visitor taps the link and sees a clean bio page with links
  • But instead of dead links, each one leads to a flow — a sequence of screens that guides the visitor from curiosity to conversion
  • The creator captures emails, qualifies leads with quizzes, shows VSLs, and closes sales — all from their bio link

The Tech Stack

Here's everything under the hood:

Layer Choice Why
Framework Next.js 16 (App Router) Bleeding edge. Server components, no middleware.ts, Turbopack HMR
Language TypeScript 5 (strict) Non-negotiable for a SaaS
Styling Tailwind CSS v4 + shadcn/ui + Radix UI Fast iteration, accessible components out of the box
Client State Zustand 5 + localStorage Simple, performant, persists draft state across navigation
Server State TanStack Query Cache invalidation done right
Forms React Hook Form + Zod v4 Type-safe validation from form to API
Auth Better Auth 1.4 Magic links, passkeys, OAuth — no Supabase Auth
Database PostgreSQL (Supabase) Raw SQL via pg, no ORM. RLS on all user tables
Payments Stripe (Billing + Connect) Subscriptions + creator payouts
Flow Builder React Flow v12 Visual node-based editor for advanced branching flows
Rich Text TipTap v3 Screen content editing
Charts Recharts Conversion analytics dashboards
Animations Framer Motion Smooth transitions between screens in flows
i18n next-intl English + Spanish from day one
Email Resend Transactional emails, magic links
Linter Biome Fast, opinionated, replaces ESLint + Prettier

Architecture Decisions That Mattered

1. No middleware.ts

Next.js 16 dropped middleware.ts. Instead of fighting it, I leaned in:

  • Auth gating lives in a server-side utility (coming-soon-gate.ts) applied inside layouts
  • Domain redirects live in next.config.ts redirects() (production only)
  • This ended up being cleaner than the middleware approach

2. app/ is routing-only

This is the rule I'm most strict about: the app/ directory contains only page.tsx, layout.tsx, loading.tsx, and route.ts. All logic, components, hooks, and utilities live outside in components/, lib/, and contexts/.

No *Client.tsx wrappers in app/. No business logic leaking into routing files. It keeps the codebase navigable even as it grows.

3. Better Auth over Supabase Auth

I use Supabase for the database but not for auth. Better Auth gave me:

  • Magic links + passkeys + OAuth in a single config
  • Full control over session handling
  • No cookie cache issues (disabled session cookie cache to prevent infinite reload loops — learned that one the hard way)

The API catch-all lives at app/api/auth/[...all]/route.ts and every API route uses auth.api.getSession() — never Supabase auth.

4. Raw SQL, No ORM

Controversial, but I went with raw pg queries instead of Prisma/Drizzle. For a product where I need full control over query performance and Supabase's transaction-mode pooler (port 6543), raw SQL was the right call.

The schema lives in a single supabase/01-schema.sql file. RLS is enabled on all user tables. Simple, auditable, fast.

5. Two Types of Flows

SellBio supports two flow types:

  • Basic flows: Linear screen sequences. Simple, fast to create. Think: lead magnet → form → thank you
  • Advanced flows: Built with React Flow v12. Full branching logic, conditional paths based on quiz answers, visual node editor

The routing evaluator in lib/utils/routing-evaluator.ts handles branch evaluation at runtime, resolving which screen to show next based on user responses.

The Subscription Model

I went with a freemium approach:

  • Starter (Free): 1 link, 1 flow, 3 screens, free design presets only
  • Pro: Unlimited everything + premium themes + advanced analytics
  • 14-day card-free trial that auto-starts after onboarding

The key architectural detail: card-free trials have stripe_subscription_id = NULL. This distinguishes them from Stripe-managed subscriptions. A server-side guard (lib/server/require-subscription.ts) checks trial expiry in real-time.

A daily cron job (/api/cron/trial-expiry) handles expiration + reminder emails via Resend.

When users hit trial_expired, canceled, or past_due, they see a non-closeable paywall modal. If they downgrade to Starter and have content exceeding limits, they choose what to keep — the rest gets archived (hidden but preserved for reactivation).

The Design System

Four free presets (minimal, ocean, sunset, forest) defined in lib/design/themes.ts. A single function — getThemeCssVariables(theme, mode) — generates all CSS variables and guarantees WCAG AA contrast ratios.

Pro users get access to premium themes. The upgrade flow uses contextual modals (UpgradeToProModal for design, UpgradeToProFullModal for limits on screens/flows/links).

Draft State & UX Details

Small things that make a big difference:

  • Newly created elements are immediately marked as saved (no draft badge on first open)
  • Draft state persists in localStorage — survives route changes
  • Publishing requires clean state (isDirty === false)
  • Back navigation always triggers UnsavedChangesBackModal when dirty
  • The screen builder has 3 stages: Config → Components → Preview

What I'd Do Differently

  1. Start with i18n from day one — I did, and it saved me weeks of refactoring. next-intl with JSON message files is the way
  2. Pick Biome over ESLint — faster, simpler config, and it handles formatting too
  3. Don't use Supabase Auth if you need flexibility — Better Auth was worth the setup cost
  4. Invest in a real design token system early — the getThemeCssVariables approach paid off massively when adding new themes

What's Next

  • AI-powered flow suggestions based on user's niche
  • A/B testing for screens within flows
  • Stripe Connect for creators to sell directly through their flows
  • More integrations (Calendly embeds, email providers)

Try It

If you're a creator, coach, or entrepreneur tired of bio links that don't convert — give SellBio a shot.

👉 sellb.io — 14-day free trial, no credit card required.

I'd love feedback from the dev community. What would you build differently? What's missing from the stack? Drop a comment — I read every one.


If you found this useful, a ❤️ or 🦄 helps more people discover the post. Thanks for reading!

Top comments (0)