Live · Processing real orders since July 2026

Bad Batch Bakes

Bad Batch Bakes is my own small-batch sourdough bakery in Fayetteville, AR. I designed and built the entire site myself — brand identity, marketing homepage, and a full ordering flow with real Stripe payments. Customers browse the weekly menu, pick a pickup time, and pay online, live and processing real orders since July 2026.

Role Owner, Designer & Full-Stack Developer
Company Bad Batch Bakes (my own business)
Stack React, Vite, Supabase, Stripe, Vercel
01 — Overview

My bakery's ordering flow, top to bottom

Bad Batch Bakes is my own small-batch sourdough bakery, based in Fayetteville, AR. Alongside running the business itself, I designed and built its entire site: a marketing homepage that introduces the brand, and a full weekly-drop ordering flow behind it. Customers browse the current menu, pick a pickup time, and pay online — no separate app, no third-party ordering platform.

Unlike the client and enterprise work elsewhere on this site, this is my own business end to end: I own the brand identity, the interface design, and the entire technical build, including the parts that don't show up in a screenshot — payment processing, order validation, and the tooling I rely on to run the ordering flow day to day.

02 — Design

A brand built around one reusable mark

The brand centers on a single custom asterisk mark, used everywhere in place of bullets, stars, or decorative flourish — in the logo, as a list marker, as a loading indicator. Rather than exporting a dozen one-off graphics, I built it once as an SVG and reused it site-wide via a CSS mask with currentColor, so the same asset recolors cleanly against pink, cream, or black backgrounds without duplicated files.

Bad Batch Bakes' custom asterisk brand mark, in pink. The asterisk mark — the one recolorable asset behind every bullet, marker, and accent on the site.
03 — Design System

The brand system, as documentation

The module below documents Bad Batch's own design values and components as labeled content — colors, type, shape, and a few live component demos — the same way a case study can show off a client's visual identity without the case study page itself becoming that brand. Nothing here restyles this page; every demo is scoped to its own contained box.

Colors, Type & Shape
Color
Pink
#FF0099
Primary accent
Blush
#FCB1BB
Secondary panel
Cream
#FFF8E7
Base background
Orange
#FF5722
Second accent
Ink
#1A1A1A
Body text
Black
#000000
High-contrast blocks
Type

Sourdough
That Slaps

Display face: Bertho Block, custom-licensed, chunky/condensed — headlines only. Shown here in a bold fallback face since Bertho Block isn't embedded in this documentation.

400Small-batch bakes, big energy.
500Small-batch bakes, big energy.
600Small-batch bakes, big energy.
700Small-batch bakes, big energy.

Body face: Inter, weights 400–700 — everything but headlines.

RoleFaceSizeWeight
Hero titleBertho Block6rem / 96px400
Section titleBertho Block~4.2rem / 67px700
Menu headingBertho Block~2.2rem / 35px700
BodyInter1.1rem / 17.6px400
Eyebrow labelInter0.72rem / 11.5px700, uppercase, tracked

Shape language: fully-rounded pill buttons (999px radius), 16–22px card radius throughout, and the custom asterisk mark standing in for bullets and stars everywhere instead of default list styling.

Live Component Demos
Buttons
Order Now → See the Menu
Hover Me →
Nav Bar
Menu Card

Cookies

The Ultimate Chocolate Chip Cookie ✦ bestseller

Sea salt, chocolate, crispy edges and chewy center.

Step Cards
01

Order Anytime

24 hours' notice, that's it.

02

We Bake Fresh

Small batches, baked to order for your pickup day.

03

Pick Up in Fayetteville

Grab your bakes at the time you picked, still warm.

Signature Interaction Pattern

Every section on the live site carries a data-section attribute, and a single IntersectionObserver watches which one is currently in view to repaint the fixed side rail and nav bar to match, in real time — no manual scroll-position math. The same observer-driven approach powers the scroll-reveal card animations and the looping marquee divider between sections, and all three respect prefers-reduced-motion, falling back to a static, unanimated layout when a visitor has that preference set.

04 — Build

A static marketing site in front of a real app

The homepage is static HTML/CSS/JS. The ordering flow behind it is a separate React 18 single-page app (built with Vite), backed by Supabase (Postgres with row-level security), Stripe Checkout, and Vercel serverless functions — all deployed together on Vercel, with Resend handling order confirmation emails.

  • The server re-validates every price against the database on checkout — it never trusts prices sent from the client.
  • Webhook handling is idempotent, so Stripe's automatic retries can't create duplicate order confirmation emails.
  • Pickup-time validation is timezone-aware and correctly handles daylight saving transitions.
  • A "vacation mode" toggle lets me pause ordering myself whenever I need to, with no deploy required.
05 — Tech Stack

How the money-handling parts actually work

The stack above is the summary; these are the specific decisions underneath it that made the ordering flow reliable rather than just functional.

Product variants without a separate variants table

Products with sizes (e.g. "S'mores Cookie — 6-Pack") are stored as plain rows in the products table, using an em-dash naming convention. The client groups them back into a single card with a size picker at render time — no schema complexity for what's really just a display grouping, and I can add a new size by adding one row in Supabase's Table Editor, no code change.

Cart state: localStorage + server as source of truth

The cart persists across page reloads via localStorage, but every price shown client-side is explicitly "display only" — the checkout endpoint re-fetches real prices from the database before creating the Stripe session, so a stale or tampered cart can never change what's actually charged.

A local API shim that mirrors Vercel's runtime exactly

npm run dev runs a small Node HTTP server that wraps the same handler functions used in production (api/create-checkout-session.js, api/webhook.js) with Vercel-compatible req/res helpers — including reading the webhook's raw request body instead of parsed JSON, since Stripe's signature check needs the exact bytes. The dev and prod code paths are identical; only the server wrapper differs.

Split build pipeline

vite build only bundles the /order React pages. The marketing homepage is copied into dist/ byte-for-byte by a small post-build script — it's deliberately never touched by a bundler, keeping the two halves of the site independent.

Test coverage on the money-handling path

The checkout endpoint has unit tests (Vitest, with Stripe and Supabase mocked) covering: unavailable products, missing/invalid pickup times, sub-24-hour pickup rejection, non-POST requests, and — notably — a test asserting the store fails open (stays orderable) if the store_settings row is ever missing, rather than accidentally locking out every customer.

Keeping a free-tier database alive

A daily Vercel Cron job (api/keepalive.js) pings Supabase with a trivial read — Supabase's free tier auto-pauses a project after 7 days with no activity, which would otherwise take the whole ordering flow down silently.

06 — Live

What it looks like in production

The homepage introduces the brand and the weekly drop; the order page turns the menu into an actual checkout. Visit badbatchbakes.com ↗ to see it live.

Bad Batch Bakes homepage hero, pink background, headline reading Sourdough That Slaps.
Homepage — hero section
Bad Batch Bakes order page, cream background, white menu cards for the weekly drop.
Order page — this week's drop
07 — Outcome

Live, and actually used

Live since Jul 2026

Processing real customer orders and payments for my own operating bakery.

Solo build

Brand, design, and full-stack implementation — including payments — built and shipped by me alone.

Self-run

I can pause ordering myself through a vacation-mode toggle whenever I need to, with no engineering support required.