Skip to content
Back to blog
Freelance
2026-09-14 6 min read

Case Study: A Local-SEO-Driven Clinic Website with a Real Booking Funnel

The Gupta Dental Care build, Bootstrap 5 grid, IntersectionObserver scroll reveals, a symptom-to-booking funnel, and schema.org local-business markup aimed at Kushinagar.

Bootstrap 5
Local SEO
Core Web Vitals
IntersectionObserver
Case Study: A Local-SEO-Driven Clinic Website with a Real Booking Funnel

Building a web presence for a dental clinic is a different beast from building an app: the user is a patient, and the site's job is to convert concern into an appointment. For guptadentalcare.cyou, I designed the site as a conversion funnel, not a brochure.

Funnel Architecture

  • Home, outcome-led hero, services grid, social proof (testimonials), and a persistent "Book Appointment" CTA.
  • Symptom pages (symptoms.html), content pages that target the searches patients actually type. One canonical URL per condition, each with a CTA back into booking.
  • Booking (booking.html), a single-purpose conversion page: name, phone, preferred time. Minimal fields, no login, no multi-step wizard.

The principle: every page has a job to do, and the navigation hierarchy encodes that.

Bootstrap 5, Used Deliberately

The layout rides Bootstrap 5's 12-column grid with its standard breakpoints (sm 576, md 768, lg 992, xl 1200). Why a framework kit instead of a bespoke design system? Because the client's maintenance needs come first: any web developer can pick up a Bootstrap layout, and the 5.x JS bundle is dependency-free. I used the grid and the components, then overrode the theme tokens with the clinic's brand palette.

Scroll Reveals Without the JS Weight

Page animations use IntersectionObserver-driven reveals (AOS-style): elements register with a threshold and a rootMargin, and receive an opacity/translate transition on intersection. Two details matter:

const io = new IntersectionObserver(
  (entries) =>
    entries.forEach((e) => {
      if (e.isIntersecting) {
        e.target.classList.add("revealed");
        io.unobserve(e.target); // fire once, then done
      }
    }),
  { threshold: 0.15, rootMargin: "0px 0px -10% 0px" }
);
  • Once-semantics, unobserving after reveal means no re-trigger on scroll-back and no work spent on off-screen elements.
  • The -10% bottom rootMargin reveals elements slightly before they hit the viewport, so nothing pops in at the last moment.

The Local-SEO Layer

This is where a clinic site actually wins or loses:

  • On-page, geo-qualified keywords in title/meta; the H1 carries service + city.
  • Structured data, schema.org LocalBusiness / Dentist JSON-LD: address, geo coordinates, opening hours, phone. That markup is what earns rich results (hours, phone) in the local pack.
  • NAP consistency, Name, Address, Phone in one canonical shape across the footer, the schema, and the booking page.

Core Web Vitals

  • LCP, hero is a WebP with fetchpriority="high" (or a text LCP with zero blocking requests).
  • CLS, every image box has a fixed aspect-ratio, so the page never reflows mid-load.
  • TBT, all interactivity is under 2 KB of inline JS: no framework, no third-party runtime.

What I Learned

A clinic site is an SEO and funnel exercise wearing a design skin. The schema layer and the booking funnel do more for the client than any animation.