Case Study
Salsa Con Flow Dance
A full-stack booking platform for a professional Latin dance instructor — built on Next.js and Vercel, then systematically hardened: rate limiting, CSP headers, Vercel Analytics, and a Node.js 20 upgrade shipped before a GitHub Actions deprecation deadline.
Context
Salsa Con Flow Dance needed a professional web presence: booking form, testimonials, contact management, and analytics — all production-grade and maintainable. The stack is Next.js (App Router), TypeScript, Tailwind, and Vercel.
After the initial feature build, the platform needed systematic hardening: the CI pipeline lacked coverage, security headers were absent, and the GitHub Actions Node.js version was facing a June 2026 deprecation. Each of these was an independent work track managed in parallel.
Key Challenges
The POST /api/testimonials endpoint accepted unlimited submissions with no IP-based throttle. A simple form automation could flood the testimonials queue. The fix added a sliding-window rate limiter keyed by IP: 5 submissions per hour per client. Excess requests receive a 429 Too Many Requests with a Retry-After header.
The platform shipped without a Content Security Policy. A CSP audit identified inline script/style risks and missing frame-ancestors directives. The fix added a strict CSP via Next.js headers() config: nonce-based script-src, locked object-src none, frame-ancestors none, plus X-Content-Type-Options, Referrer-Policy, and Permissions-Policy.
GitHub Actions deprecated Node.js 16 runners in June 2026. The CI pipeline used actions/setup-node@v3 with node-version: 18. The fix upgraded to actions/setup-node@v4 with node-version: 20 across all workflow files — shipped 5 weeks before the deadline.
The booking form submission path had zero automated test coverage — a regression in the happy path would be invisible until a real user hit it. Playwright integration tests were added covering the happy path (successful submission), validation errors (empty required fields), and boundary conditions (long inputs, special characters). Tests run in CI on every PR.
Execution Approach
Each work track was independent with its own branch, its own CI validation, and its own merge window. No track blocked another — all could proceed in parallel.
- ›CI-first discipline: every change required all existing checks to pass before merge. No exceptions for “small changes” — the CI gate caught two regressions that would have shipped to production without it.
- ›Deadline-driven prioritization: the Node.js 20 upgrade was fast-tracked because a hard external deadline (GitHub Actions deprecation) made deferral costly. Shipped and in CI before the deadline window opened.
- ›Security-first additions: rate limiting and CSP were added proactively — not in response to an incident. Both are significantly cheaper to add before a platform has real user traffic.
- ›Test coverage as insurance: the booking form tests gave the team confidence to iterate on the form without manually verifying the happy path after every change. Coverage for the core user flow is the floor, not the ceiling.
Quantified Results
All 4 security/hardening PRs merged with full CI pass — no rollbacks, no hotfixes.
Upgraded 5 weeks before GitHub Actions' Node.js 16 deprecation window. Zero CI disruption.
POST /api/testimonials now throttled at 5 req/hr/IP with RFC-compliant 429 + Retry-After headers.
Playwright E2E tests cover happy path, validation errors, and boundary conditions. Runs on every PR.
Tech Stack
Lessons
- ›Security hardening is cheaper early. Adding CSP and rate limiting before the platform had real traffic required no compatibility retrofitting. The same work post-launch would have required auditing live usage patterns for CSP violations.
- ›External deadlines are forcing functions. The Node.js 20 upgrade happened because there was a hard date. Without a deadline, “update the CI” lives in the backlog indefinitely. Build the upgrade into the sprint before the deadline window opens.
- ›Test the form, not just the API. Unit tests on the API handler miss UX-level regressions. The booking form tests caught a field-order validation bug that the API tests would never have seen.