The thing nobody warns you about with booking sites
A booking website looks like a weekend project until you actually try to build one. The UI is easy — a service list, a calendar, a "Book now" button. Then you hit the real question: is this slot actually available?
That answer isn't a row in a calendar table. It depends on which staff member can do this service, which location they're at, how long the service runs, whether they're already booked, days off, buffers between appointments, and — for classes — how many seats are left. Get it slightly wrong and you double-book a stylist, which is a great way to lose a customer on day one.
I did not want to build that engine again. I've built scheduling logic before and it's the kind of thing that's "done" five times before it's actually done. So this time I let something else own the hard part and spent my weekend on the parts users actually see.
The stack
| Layer | Choice | Why |
|---|---|---|
| Framework | Next.js 15 (App Router) | Server components for the data fetches, fast deploys to Vercel |
| Language | TypeScript | The API responses are typed end-to-end via the SDK |
| Styling | Tailwind CSS | The template ships with it; rebranding is mostly editing tokens |
| Booking backend | Opencals Storefront API | Availability, staff, locations, cart, Stripe checkout, customer accounts |
| Payments | Stripe (through the API) | No payments backend of my own to build or maintain |
| UI | Claude (Opus 4.8) | I described the screens; it wrote the React + Tailwind components against the typed SDK |
| Hosting | Vercel |
git push and it's live |
The part I want to be honest about: the Opencals Storefront API is the reason this was a weekend and not a month. It's the booking infrastructure behind Opencals (a service-commerce platform I work on), exposed as a plain REST API with a typed TypeScript SDK. Services, availability, staff, locations, cart, checkout, customer self-service — all endpoints I'd otherwise have had to design, build, and debug myself. With the scheduling logic owned by the API and a small typed surface to write against, Claude handled most of the UI components cleanly.
Step 1 — Clone the template
I didn't start from create-next-app. There's an open-source salon template — Haar — that already wires the whole flow to the API (announcement post · docs):
git clone https://github.com/letsopencals/template-haar.git
cd template-haar
npm install
It's template-haar — Next.js 15, TypeScript, Tailwind, MIT license. Services (with variants), staff, multi-location, real-time availability, add-ons, Stripe checkout, and customer accounts are all already connected. There's a live demo if you want to click through it first. My weekend was mostly rebranding and tweaking, not plumbing.
Step 2 — Create a store, seed it with Haar data, and deploy
This is the part that makes it fast. The template reads everything from one Opencals store, identified by a Storefront API key — and you can have a live, populated store in three steps, no code required:
- Create a free Dev Store in the Opencals dashboard.
- Choose the "HAAR Salon" seed dataset when prompted. It's built specifically for this template — it pre-loads the exact services, staff, and locations from the live demo, so your store isn't empty and the cloned site renders real content from the very first load.
-
Create a Storefront API Key under Settings → API Keys (it starts with
sfk_).
Then deploy: hit Deploy with Vercel in the README, paste your sfk_ key when Vercel asks for the environment variable, and it builds in about two minutes. That's a working booking site before you've written a line of code.
Running it locally instead? Drop the key into .env.local:
OPENCALS_API_KEY=sfk_your_key_here
Keep the key server-side — it identifies your store and not recommended to ship to the browser. The quickstart covers it in more detail.
Step 3 — Listing services and fetching availability
The whole booking flow is seven typed calls (list services → pick staff → fetch slots → create appointment → cart → add-ons → checkout), and with the @opencals/storefront-sdk each one is basically a one-liner. You initialize the SDK once and the key stays server-side:
npm install @opencals/storefront-sdk
// lib/opencals.ts — run once, server-side only
import { setupOpencals } from "@opencals/storefront-sdk";
setupOpencals({
apiKey: process.env.OPENCALS_API_KEY, // sfk_… from Settings → API Keys
});
Then the two calls that drive the whole front page — the service menu and real-time slots:
import "@/lib/opencals";
import { ProductService } from "@opencals/storefront-sdk";
// 1. List the bookable services (your menu)
const { data } = await ProductService.list({ query: { take: 50 } });
const services = data!.data; // Product[] — title, price, duration, staffMembers…
// 2. Real-time availability for one service on a given day
const { data: slots } = await ProductService.getCurrentAvailabilities({
path: { productId: services[0].id },
query: {
date: "2026-07-01",
timezone: "America/New_York", // slots come back converted for display
},
});
That getCurrentAvailabilities call is the part I was dreading building. It already accounts for which staff can do the service, at which location, for how long, and what's already booked — pass an optional staffMemberId and it filters to one person. I render the slots and move on with my life.
From there it's create the appointment, drop it in a cart, optionally attach add-ons, and check out through Stripe. The full sequence is documented step by step in the Build a booking page guide, so I won't paste all seven here.
Step 4 — Customer accounts (the part I almost skipped)
I assumed self-service rescheduling would be the thing I'd cut for v1. It wasn't — the API handles customer auth (JWTs) and the account actions, so the template already had a customer portal where someone can log in, see their bookings, and reschedule or cancel without emailing the salon. For a real salon that's the difference between a booking tool and a front desk that never sleeps.
Step 5 — Make it yours
Rebranding is one file. Everything salon-specific — name, tagline, logo, hero, team, hours, contact, social — lives in lib/site-config.ts, and the brand colors are CSS custom properties in app/globals.css. Edit those and Haar stops looking like Haar.
@theme {
--color-accent: #E8530E; /* your brand color */
--font-display: 'Playfair Display', Georgia, serif;
--font-body: 'Inter', system-ui, sans-serif;
}
You already deployed in Step 2, so a rebrand is just editing that config and pushing — Vercel redeploys on every commit. That's the weekend.
What you actually get
- A real Next.js booking website, not a calendar embed
- Real-time availability across multiple staff and locations
- Add-ons, Stripe checkout, and deposits without a payments backend
- Customer accounts with self-service reschedule/cancel
- One codebase you can rebrand per client — MIT, no royalties (relevant if you're an agency)
Try it without committing
You don't need an account to look around. The interactive API reference lets you browse every endpoint in the browser, no key required. If you want to build:
- 🧬 Clone the template: github.com/letsopencals/template-haar · live demo
- 📖 Template write-up + docs: the Haar announcement · Haar docs
- 🚀 Quickstart (5 min): opencals.com/docs/quickstart
- 📦 SDK:
@opencals/storefront-sdk
This is Part 1 of a series where I rebuild the whole thing piece by piece — next up, the API calls in full, then Stripe deposits, then rendering availability in React the right way. If you build something with it, drop a comment — I read all of them and I'm curious what verticals people try beyond salons.
— Stan, building Opencals





Top comments (1)
A bit of the why behind this, for anyone curious...
Beauty might be the industry most underserved by booking software. Most tools funnel salons into non-branded marketplaces: you get a listing, a "book now" button, and a page that looks identical to the hundred competitors sitting right next to you. Your brand disappears, and you end up effectively renting customers from a platform that's also selling your competitors on the same screen.
The alternative is your own branded site with real booking, it has always been expensive. And even when a salon can afford a custom website, the booking logic underneath (availability across staff and locations, deposits, reschedules, cancellations) gets rebuilt from scratch every single time. That's the part nobody should be paying to rebuild.
So we open-sourced it. Haar is a real, deployable salon site, and the hard booking logic lives behind the Opencals API. In practice that means: clone it, spend up to $50 of AI tokens shaping it to your brand, deploy free on Vercel, point a domain at it - and you've got a genuinely nice working booking website for roughly the price of the domain.
And if you're not a salon: the template doubles as the best documentation we have for how the API and SDK actually work. Read it, copy the patterns, build your own thing on top.
Covering more verticals in the future - barbershops and fitness are already in progress.