The brief
A passion side project for a coffee shop in Bali. The brief was simple: a single-page company profile that reads beautifully on mobile, communicates the brand within a few seconds of scrolling, and stays current without anyone having to log into a CMS.
No checkout, no booking flow, no admin panel. The site exists to do one job: turn a casual visitor into someone who shows up at the cafe.
What's on the page
Eight sections, all editorial:
- Hero: brand, tagline, and a hero photo of the cafe.
- Signature Favorites: a grid of the items the cafe is known for.
- How It All Started: founding story, in the cafe's voice.
- Operating hours and live status: open or closed indicator that updates against real time.
- Coffee and Drinks menu: full menu with pricing, organized by category.
- Breakfast and Brunch menu: same treatment for the food program.
- Pastries and Sweets: a smaller third menu block.
- What People Are Saying: testimonials sourced from Google Maps reviews.
- Best Time to Visit: a hourly busyness chart.
- Find Us: an embedded map with directions.
Each section is its own component with motion wired through Framer Motion: fade and slide on scroll, with a useReducedMotion guard so the page is readable on devices that opt out of motion.
Why no backend
The cafe already has Google Maps. Reviews, ratings, busy-hours data, and operating hours all live there and are kept current by Google's own data flywheel. Replicating any of that into a custom CMS would mean adding maintenance work for a site that's supposed to be a simple shop window.
The pragmatic answer was a build-time scrape. The site is statically generated through Next.js, hosted on Vercel, and rebuilt on a schedule. No API keys, no rate limits, no admin login, no database. Cost to operate: zero plus the domain.
Stack notes
- Next.js 14 App Router, built statically. No server components needed at runtime since everything is pre-rendered.
- Tailwind for styling. The brand has a warm, café-leaning palette, mapped to CSS custom properties so dark or seasonal variants are a single-file change.
- Framer Motion for the scroll reveals on each section. One
ScrollRevealwrapper used 20+ times. - Vercel for the hosting plus preview deployments on every push.
Lessons
-
A backend isn't a baseline; it's a cost. A site whose data lives somewhere else (Google Maps in this case) doesn't need to copy that data into its own database. A scheduled scrape during the build step keeps the content current without any of the operational overhead.
-
Mobile-first wasn't optional. Most of the audience opens the site from a Google Maps detail page on a phone. Designing the desktop view first and "responsive-ifying" later would have meant fighting the layout for the actual majority use case.
-
One reusable motion wrapper beats per-section animation code. A single
ScrollRevealcomponent handles every reveal on the page, with props for direction and delay. This kept motion consistent across sections and made the reduced-motion override a single edit.
