Client Work — Next.js 16 + React 19
April – June 2026
The public marketing site for Bonita Cafe — a family-owned Colombian coffee and brunch spot in Briskeby, Oslo. I designed and built the entire site myself: a fast, single-page Next.js experience covering the story, chalkboard-style menu, and takeaway ordering, with a live Instagram gallery that degrades gracefully when the feed is unavailable. Deployed to a self-hosted Docker + Caddy stack set up by a teammate.

Bonita Cafe website — bonitacafe.no, the cafe's public menu, story, and ordering site.
Technical Implementation
Key Features
A tabbed, chalkboard-styled menu with the cafe's real items and prices — breakfast, smoothies, coffee, pastas, and salads.
Fetches the latest posts and reels from @bonitacafe_oslo server-side, cached via ISR and revealed six at a time with a "Show more" button.
If the Instagram token is missing, invalid, or the feed runs dry, the gallery falls back to a curated set of cafe photos — so the page never looks broken.
Direct Click & Collect ordering alongside Wolt and Foodora delivery links, all surfaced from the nav and hero.
Split hero layout with a hand-drawn sticker graphic and an animated marquee strip highlighting the cafe's story.
Opening hours, contact details, and an embedded map for the Briskeby, Oslo location.
Development Process
I designed and built the whole site myself — no external Figma or brief to work from. That meant deciding the brand direction (the Colombian-inspired colour palette, the Fraunces/Caveat/DM Sans type pairing, the hand-drawn chalkboard motif) and then implementing it as a single scrollable page: hero, story, menu, takeaway, visit, gallery, and footer. The menu uses real items and prices rather than placeholder content.
Brand-True but Usable
Owned the full design-to-code path — custom Tailwind v4 tokens carry the brand colours and typography throughout, with no separate design handoff.
The chalkboard menu tabs had to look hand-drawn while remaining keyboard- and screen-reader-friendly.
The gallery pulls from the Instagram Graph API server-side, cached with ISR and refreshed roughly hourly. Instagram's API only returns posts authored by the account — not collaboration posts — and tokens expire, so the feed needed to degrade gracefully rather than break.
Graceful Degradation
Curated fallback images in public/images/ are shown whenever the token is missing, invalid, or the live feed is empty — and are appended once the live feed runs out.
A separate token-refresher service rotates the 60-day Instagram access token automatically every 14 days.
The site is deployed as a small Docker stack (Next.js standalone + token-refresher) behind bonitasites-edge, a Caddy reverse proxy shared across the cafe's other sites — infrastructure my teammate Jerome built and maintains. Getting the app to run cleanly in that setup meant building it as a standalone Next.js output and keeping the Instagram integration resilient to a headless, no-Vercel environment.
CI/CD Pipeline
Push to prod triggers: npm ci → lint → tsc --noEmit → next build → rsync to the server → docker compose up --build -d.
The app publishes no host ports — the shared edge Caddy is the only public entrypoint, handling TLS for bonitacafe.no.
Challenges & Solutions
Challenge: Instagram tokens expire, the API excludes collab posts, and the feed can come back empty — any of which could leave the gallery broken.
Solution: Built a curated fallback gallery that kicks in automatically, paired with an ISR revalidate window and an automated 14-day token rotation service so the live feed rarely lapses in the first place.
Challenge: The site needed to run on the cafe's own infrastructure, sharing a server and TLS termination with other Bonita sites rather than each having its own public entrypoint.
Solution: Jerome built and maintains the Docker + Caddy edge infrastructure; my part was making sure the app itself — the Instagram integration especially — behaved correctly as a standalone build in that shared, no-Vercel environment.
Challenge: The brand called for a playful, hand-drawn chalkboard look for the menu, which risked working against usability for a real menu customers need to read quickly.
Solution: Kept the visual styling in Tailwind's design layer while the tab navigation itself follows standard accessible patterns — real prices, clear categories, and no illegible decorative fonts on actual menu text.
Planned Enhancements
Add an online reservation flow directly on the site instead of relying on phone or Instagram DMs.
Formalise the current English/Norwegian content into proper localised routes instead of a single mixed-language page.
Connect the gift card and ordering flows to a lightweight loyalty or rewards system for regulars.