JK
All work

Rural Outreach Africa

Humanitarian website with donation processing, CMS-driven content, and multi-payment support.

ReactTypeScriptExpressSanityPaystack
Live site ↗

Overview

Rural Outreach Africa is a full-stack website for a non-profit organisation working in rural community development across Africa. It surfaces their programmes and impact metrics, handles donations across multiple payment methods, and lets the content team manage everything through a CMS without touching code.

Homepage hero — "Empowering Rural African Communities" with full-bleed image
Homepage hero — "Empowering Rural African Communities" with full-bleed image

The Problem

The organisation had no centralised web presence. Programme updates were shared informally, donation processing was manual, and there was no way for international supporters to find or fund specific initiatives. The site needed to serve both a Kenyan audience (M-Pesa) and international donors (card payments), with content editors who aren't developers.


Technical Decisions

Monorepo with pnpm workspaces

Frontend, backend API, and Sanity Studio live in the same repository with shared scripts. A single pnpm dev starts all three. This made it straightforward to keep the API and frontend in sync during development without managing separate repos.

Backend as CMS intermediary

The React frontend never calls Sanity directly. All content goes through the Express API, which fetches from Sanity, caches responses for 5 minutes, and falls back to mock data if Sanity is unavailable. This keeps API keys off the client and means a CMS outage doesn't take the site down.

React Query for data fetching

All content fetching goes through React Query with 5-minute stale times. Donation status polling uses a 5-second interval — after a user initiates an M-Pesa payment, the UI polls until the backend confirms completion or times out.

Multi-payment donation flow

The donation page supports Paystack (card), M-Pesa, Airtel Money, and bank transfer. Currency selection (KSH / USD) adjusts the payment method options shown. Each payment method routes through the Express backend so confirmation emails are sent regardless of which method was used.

Semantic colour system

Custom Tailwind theme with brand-primary, content-primary, surface-primary tokens rather than raw colour values. This made it straightforward to maintain visual consistency across pages without hunting for hex codes, and leaves a clear path to a dark mode if needed.


Key Features

  • Programmes — CMS-driven programme pages with impact metrics, stories, and photo galleries
  • Blog — full blog with search, category filtering, reading time, and Portable Text rendering
  • Donations — preset and custom amounts, currency selection, multiple payment methods, confirmation emails with ticket IDs
  • Team and biography pages — structured content from Sanity
  • Contact form — with email notifications to admin and auto-responder to the submitter
  • Interactive map — office locations via Leaflet
  • Partners slider — logo carousel with Keen Slider
Partners carousel and community success stories section
Partners carousel and community success stories section
News and blog section with featured article and story grid
News and blog section with featured article and story grid
Contact page with Leaflet map showing the Nairobi office location
Contact page with Leaflet map showing the Nairobi office location

Security

The Express API applies rate limiting (100 requests/hour per IP), Helmet security headers, XSS sanitisation, and NoSQL injection protection via express-mongo-sanitize. CORS is whitelisted to specific origins rather than open.


Tradeoffs

The in-memory cache on the Express server resets on every deploy, which means the first request after a deployment hits Sanity cold. A Redis cache or Sanity's CDN edge caching would handle this better for a higher-traffic site.

The Paystack integration is wired up but waiting on live API keys — the payment flow works end-to-end in test mode.


Retrospective

The CMS intermediary pattern was the right call. Content editors can update programmes, blog posts, and team members without any code changes, and the fallback to mock data meant development could proceed independently of whether the Sanity project was set up. The thing I'd revisit is the polling approach for M-Pesa confirmation — a webhook from the payment provider would be more reliable than client-side polling.