Building Florida Massage Elite: A Self-Running Therapist Directory
The Problem: Trust Is Expensive When You're Manual
Florida's massage industry has a real trust problem online. Unlicensed practitioners list themselves everywhere, and consumers genuinely cannot tell who's legitimate. The vision here was a Florida massage therapist directory where every single therapist is ID-verified before their profile goes live -- not spot-checked, not self-reported, verified.
The catch: the owner is not a full-time operator. They can't be tech support. Can't chase incomplete signups, manually verify documents, or answer the same five questions about pricing tiers every day. The directory had to be a machine -- one that collects therapists, verifies them, takes payment, publishes pages, and only pings the owner when a human decision is actually needed.
That constraint shaped every architectural choice. I kept coming back to it: does this feature require the owner to do anything? If yes, can I route around that? Usually I could.
The Stack and Why Each Piece Earned Its Spot
Next.js 15 and Tailwind on Vercel Pro. Supabase for Postgres and auth. Stripe for payments and identity verification. Google Maps for location. Resend for transactional email. An AI chatbot for front-line support.
Nothing exotic. That is the point. The client owns the GitHub repo -- transferred at handoff, not held hostage. The Supabase project lives under their account, exportable as SQL or CSV anytime. Standard Next.js, zero vendor lock-in. Running cost lands around $20-45 per month once it's live.
I've written before about building a directory platform with 137,000 listings on Next.js and Vercel ISR. This project is smaller in raw data volume but considerably more complex per listing -- verification, subscriptions, approval queues, and structured onboarding that has to feel effortless to a therapist filling it out on their phone at 11pm after a full day of clients. That last part matters more than people expect.
Self-Serve Onboarding: A Six-Step Wizard That Does the Heavy Lifting
Therapists sign up through a guided wizard. Six steps, each one focused, each one saving progress so a half-finished signup doesn't just evaporate.
- Basic info -- name, location, bio.
- Services -- picked from a 20+ massage-type taxonomy (deep tissue, Swedish, prenatal, sports, the full list). This taxonomy also drives the programmatic service pages, which I'll get to.
- Pricing -- duration rows for 30, 60, 90, and 120 minutes, with separate columns for in-call and out-call rates. Sounds straightforward. Getting the UX right for this grid on mobile took more iteration than almost anything else in the build.
- Availability -- a day-by-day hours picker.
- Photos -- drag-and-drop upload, up to six images, with SEO bio guidance baked directly into the interface.
- Verification + payment + submit -- Stripe Identity (ID document plus selfie match), subscription selection, and final submission.
A therapist can start Tuesday and finish Thursday. Progress is there when they come back.
Stripe Identity: Verification Without Storing Sensitive Documents
This was a hard requirement from day one, and honestly one of the more interesting technical problems in the build. Stripe Identity handles the ID check -- the therapist uploads a government-issued ID, takes a selfie, Stripe matches them, result comes back verified or not. No license files are ever stored on our server. None.
A mandatory certification checkbox during onboarding shifts licensing liability to the therapist. They're attesting they hold a valid Florida massage license. The directory verifies identity, not licensure -- an important legal distinction the client's attorney was very clear about, and one I've seen glossed over badly in other builds.
I've worked on other directory builds in the massage and spa space and this is genuinely the cleanest verification flow I've shipped. Stripe does the hard part. We orchestrate it, we don't replicate it.
The Pending-Approval System (and Why Old Data Stays Live)
This is a decision I'm proud of and would make again immediately.
When a therapist edits their profile -- new photo, updated bio, changed pricing -- the changes don't go live. They land in the admin's pending-approval queue. But the old profile stays live and visible to consumers the entire time the edit is pending.
Why does this matter? If a therapist updates their hours at 10pm Saturday, they should not vanish from search results until Monday morning when the owner checks the queue. The previous version of their data keeps serving visitors. The owner sees the diff, clicks approve or reject, optionally types a reason. If rejected, Resend fires an email with the reason and a direct link back to the edit screen. No ambiguity, no support ticket needed.
New signups work the same way. Profile hits the queue, owner reviews, one click.
To keep things from aging unnoticed, a daily digest email fires at 9am if anything has been sitting for more than 48 hours. The owner told me this single feature gives them the most peace of mind of anything in the system. Nothing slips through the cracks.
Founding-30 Pricing: Auto-Switching Tiers
Monetization runs on two membership tiers. Basic at $29/mo gets a live profile with phone, SMS, and WhatsApp contact links. Premium at $49/mo adds clickable social links -- Instagram, Facebook, TikTok, personal website -- and a premium badge on the listing.
The twist is a "Founding 30" rule. The first thirty therapists lock in those rates permanently. Once the count hits 30, Stripe subscriptions for new signups auto-switch to higher-tier pricing. No manual flipping, no remembering to update a config somewhere. The system checks the count at signup time and applies the right Stripe price ID. Clean, and I haven't found an edge case yet.
Programmatic SEO: City Pages, Service Pages, and Schema
This is where the directory website development approach compounds over time. Every city with at least one approved therapist gets a dedicated page -- /therapists/miami, /therapists/fort-lauderdale, /therapists/tampa, and so on. Currently 15-20 city pages. Every massage type in the taxonomy gets a service page -- /services/deep-tissue-massage, /services/swedish, the full list.
All auto-generated from the database. Nobody creates these pages by hand. A new therapist gets approved in Sarasota, and the Sarasota city page either appears for the first time or updates with their listing. Zero intervention required.
Every relevant page carries structured data: LocalBusiness, Service, Person, and FAQPage schema. Static generation means every page ships as plain HTML. Sub-1-second loads on Vercel's edge CDN. Core Web Vitals are green. An auto-generated XML sitemap is submitted to Google Search Console, and conversion tracking fires on signups.
I used a similar pattern on a podcast guest directory project and the results were consistent. Search engines respond well to pages that are real HTML with structured data and fast loads. The pattern also scales without effort -- when the directory reaches 200 therapists across 40 cities, those pages just appear.
The AI Chatbot: Zero-Ticket Support
The chatbot handles FAQ, password resets, profile editing help, and tier comparison questions. It's trained on the site's actual content and policies. The goal is straightforward: the owner should never have to answer "how do I update my photo" or "what's the difference between Basic and Premium."
It won't replace the approval queue -- that requires human judgment and I wouldn't want it to. But for everything that isn't a judgment call, it's the first and usually the last line of support.
Five Weeks, One Thesis
Five weeks, fixed scope. The thesis held: a directory that runs itself. The owner reviews approvals. The system handles onboarding, verification, payments, emails, SEO pages, and support. That's the kind of project I like building -- one where the architecture genuinely respects the operator's time, not just in theory but in every small decision along the way.
FAQ
What does it cost to build a directory like this?
It depends on scope, but a verified directory with self-serve onboarding, Stripe Identity, subscription billing, programmatic SEO, and an admin approval system is a significant build. Expect it to be a real investment -- not a weekend project. Running costs afterward are roughly $20-45/mo.
How long does a project like this take?
This one took five weeks with a fixed scope. Timelines vary with complexity, but five to eight weeks is realistic for a directory with verification, payments, and programmatic pages if the scope is locked down from the start.
Can therapists sign up and onboard themselves?
Yes, completely. A six-step wizard walks them through profile creation, service selection, pricing, availability, photo uploads, identity verification, and payment. The owner only steps in to approve or reject the finished submission.
How is identity verification handled?
Stripe Identity performs document and selfie matching during onboarding. No license files or sensitive documents are stored on the server. A certification checkbox shifts licensing liability to the therapist, verifying they hold a valid Florida license.
Who owns the code and data after the project?
The client owns everything. The GitHub repo is transferred to their account, Supabase runs under their login, and the database is exportable as SQL or CSV at any time. Standard Next.js stack with no proprietary lock-in.