<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stan</title>
    <description>The latest articles on DEV Community by Stan (@stangineer).</description>
    <link>https://dev.to/stangineer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3908650%2Fd574d054-942b-4f4f-8c04-81657d1f2719.jpg</url>
      <title>DEV Community: Stan</title>
      <link>https://dev.to/stangineer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stangineer"/>
    <language>en</language>
    <item>
      <title>Built a full salon booking website in a weekend — here's the stack</title>
      <dc:creator>Stan</dc:creator>
      <pubDate>Tue, 23 Jun 2026 06:08:00 +0000</pubDate>
      <link>https://dev.to/stangineer/built-a-full-salon-booking-website-in-a-weekend-heres-the-stack-2fkd</link>
      <guid>https://dev.to/stangineer/built-a-full-salon-booking-website-in-a-weekend-heres-the-stack-2fkd</guid>
      <description>&lt;h2&gt;
  
  
  The thing nobody warns you about with booking sites
&lt;/h2&gt;

&lt;p&gt;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: &lt;strong&gt;is this slot actually available?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I did &lt;strong&gt;not&lt;/strong&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Next.js 15&lt;/strong&gt; (App Router)&lt;/td&gt;
&lt;td&gt;Server components for the data fetches, fast deploys to Vercel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The API responses are typed end-to-end via the SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The template ships with it; rebranding is mostly editing tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Booking backend&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Opencals Storefront API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Availability, staff, locations, cart, Stripe checkout, customer accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; (through the API)&lt;/td&gt;
&lt;td&gt;No payments backend of my own to build or maintain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Claude (Opus 4.8)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;I described the screens; it wrote the React + Tailwind components against the typed SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Vercel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;git push&lt;/code&gt; and it's live&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The part I want to be honest about: the &lt;strong&gt;Opencals Storefront API&lt;/strong&gt; is the reason this was a weekend and not a month. It's the booking infrastructure behind &lt;a href="https://opencals.com" rel="noopener noreferrer"&gt;Opencals&lt;/a&gt; (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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Clone the template
&lt;/h2&gt;

&lt;p&gt;I didn't start from &lt;code&gt;create-next-app&lt;/code&gt;. There's an open-source salon template — &lt;strong&gt;Haar&lt;/strong&gt; — that already wires the whole flow to the API (&lt;a href="https://opencals.com/blog/templates/haar?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;announcement post&lt;/a&gt; · &lt;a href="https://opencals.com/docs/open-source/haar?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/letsopencals/template-haar.git
&lt;span class="nb"&gt;cd &lt;/span&gt;template-haar
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's &lt;a href="https://github.com/letsopencals/template-haar" rel="noopener noreferrer"&gt;&lt;code&gt;template-haar&lt;/code&gt;&lt;/a&gt; — 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 &lt;a href="https://template-haar.vercel.app/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; if you want to click through it first. My weekend was mostly rebranding and tweaking, not plumbing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6p32g2sunxu7twk3ahv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6p32g2sunxu7twk3ahv7.png" alt="The template's storefront out of the box — service menu with prices and durations, before any rebranding." width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Create a store, seed it with Haar data, and deploy
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a free Dev Store&lt;/strong&gt; in the &lt;a href="https://app.opencals.com" rel="noopener noreferrer"&gt;Opencals dashboard&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose the "HAAR Salon" seed dataset&lt;/strong&gt; when prompted. It's built specifically for this template — it pre-loads the exact services, staff, and locations from the &lt;a href="https://template-haar.vercel.app/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;, so your store isn't empty and the cloned site renders real content from the very first load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a Storefront API Key&lt;/strong&gt; under &lt;strong&gt;Settings → API Keys&lt;/strong&gt; (it starts with &lt;code&gt;sfk_&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then deploy: hit &lt;strong&gt;Deploy with Vercel&lt;/strong&gt; in the README, paste your &lt;code&gt;sfk_&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Running it locally instead? Drop the key into &lt;code&gt;.env.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;OPENCALS_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sfk_your_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the key server-side — it identifies your store and not recommended to ship to the browser. The &lt;a href="https://opencals.com/docs/quickstart?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;quickstart&lt;/a&gt; covers it in more detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffxh2nx0cyxb5uih0t1ly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffxh2nx0cyxb5uih0t1ly.png" alt="The whole no-code path in one place — create a store, choose the HAAR Salon seed data, grab your  raw `sfk_` endraw  key in Settings, and deploy with that key." width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Listing services and fetching availability
&lt;/h2&gt;

&lt;p&gt;The whole booking flow is seven typed calls (list services → pick staff → fetch slots → create appointment → cart → add-ons → checkout), and with the &lt;a href="https://www.npmjs.com/package/@opencals/storefront-sdk" rel="noopener noreferrer"&gt;&lt;code&gt;@opencals/storefront-sdk&lt;/code&gt;&lt;/a&gt; each one is basically a one-liner. You initialize the SDK once and the key stays server-side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @opencals/storefront-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// lib/opencals.ts — run once, server-side only&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;setupOpencals&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opencals/storefront-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;setupOpencals&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENCALS_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// sfk_… from Settings → API Keys&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the two calls that drive the whole front page — the service menu and real-time slots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/opencals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ProductService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@opencals/storefront-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. List the bookable services (your menu)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Product[] — title, price, duration, staffMembers…&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Real-time availability for one service on a given day&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;slots&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ProductService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrentAvailabilities&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07-01&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// slots come back converted for display&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;getCurrentAvailabilities&lt;/code&gt; 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 &lt;code&gt;staffMemberId&lt;/code&gt; and it filters to one person. I render the slots and move on with my life.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85kpzxdrgn9w7ipvmhxu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85kpzxdrgn9w7ipvmhxu.png" alt="Real-time slots for the selected day and stylist — straight from  raw `getCurrentAvailabilities` endraw ." width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://opencals.com/docs/guides/build-a-booking-page?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;Build a booking page guide&lt;/a&gt;, so I won't paste all seven here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5b0buzex4ny77eiwmg0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5b0buzex4ny77eiwmg0.png" alt="Add-ons (e.g. " width="799" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Customer accounts (the part I almost skipped)
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczb7jlort1keuf2udh12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczb7jlort1keuf2udh12.png" alt="A logged-in customer rescheduling their own appointment — no phone call, no email" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Make it yours
&lt;/h2&gt;

&lt;p&gt;Rebranding is one file. Everything salon-specific — name, tagline, logo, hero, team, hours, contact, social — lives in &lt;code&gt;lib/site-config.ts&lt;/code&gt;, and the brand colors are CSS custom properties in &lt;code&gt;app/globals.css&lt;/code&gt;. Edit those and Haar stops looking like Haar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@theme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#E8530E&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c"&gt;/* your brand color */&lt;/span&gt;
  &lt;span class="py"&gt;--font-display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Playfair Display'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Georgia&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--font-body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;'Inter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A real &lt;strong&gt;Next.js booking website&lt;/strong&gt;, not a calendar embed&lt;/li&gt;
&lt;li&gt;Real-time availability across multiple staff and locations&lt;/li&gt;
&lt;li&gt;Add-ons, Stripe checkout, and deposits without a payments backend&lt;/li&gt;
&lt;li&gt;Customer accounts with self-service reschedule/cancel&lt;/li&gt;
&lt;li&gt;One codebase you can rebrand per client — MIT, no royalties (relevant if you're an agency)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it without committing
&lt;/h2&gt;

&lt;p&gt;You don't need an account to look around. The &lt;a href="https://opencals.com/docs/api-reference/introduction?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;interactive API reference&lt;/a&gt; lets you browse every endpoint in the browser, no key required. If you want to build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧬 &lt;strong&gt;Clone the template:&lt;/strong&gt; &lt;a href="https://github.com/letsopencals/template-haar" rel="noopener noreferrer"&gt;github.com/letsopencals/template-haar&lt;/a&gt; · &lt;a href="https://template-haar.vercel.app/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;strong&gt;Template write-up + docs:&lt;/strong&gt; &lt;a href="https://opencals.com/blog/templates/haar?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;the Haar announcement&lt;/a&gt; · &lt;a href="https://opencals.com/docs/open-source/haar?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;Haar docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Quickstart (5 min):&lt;/strong&gt; &lt;a href="https://opencals.com/docs/quickstart?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=booking-weekend" rel="noopener noreferrer"&gt;opencals.com/docs/quickstart&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;SDK:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/@opencals/storefront-sdk" rel="noopener noreferrer"&gt;&lt;code&gt;@opencals/storefront-sdk&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;— Stan, building &lt;a href="https://opencals.com" rel="noopener noreferrer"&gt;Opencals&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>tutorial</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
