Back to the journal

Design your South African SaaS to work on a 2G fallback and you accidentally serve 60% of the continent

Most of our clients build web apps targeting urban South African users on decent fibre or LTE. Then they discover their actual users — field workers in Limpopo, shop owners in Lusaka, sales reps driving through the Free State — are regularly dropping to EDGE or worse. The instinct is to treat this as an edge case. It is not. Outside a handful of African metros, intermittent and low-bandwidth connectivity is the default operating environment, and designing for it from the start is cheaper than retrofitting later.

The practical pattern we keep reaching for is an optimistic local-first write queue. The user taps a button, the app confirms the action immediately, and a lightweight background sync pushes the payload when connectivity returns. You do not need a full offline-first framework to get 80% of the benefit. A service worker with IndexedDB and a simple retry queue handles most CRUD workflows. Pair that with aggressively small API payloads — strip unused fields, compress responses, paginate early — and your app stays usable on connections that would otherwise time out and leave the user staring at a spinner.

The bonus nobody talks about is that this also makes your app load-shedding-resilient. When towers lose power and backhaul degrades across a suburb, the users already on your app keep working locally instead of losing unsaved state. If you are building for the South African market, add a network throttle profile to your QA checklist — Chrome DevTools lets you simulate 50 kbps in two clicks. Test there first, not on your office Wi-Fi.

Replacing your landing page testimonial carousel with a single named quote lifted our client's sign-up rate more than any copy rewrite did

Most landing pages we audit have a testimonial carousel somewhere near the bottom — three to five quotes rotating on a timer, often attributed to a first name and a company that could be fictional. The pattern is so common it has become invisible. Visitors don't read carousels; they register movement, mentally file it as decoration, and scroll past. When we rebuilt a client's onboarding landing page earlier this year, we stripped the carousel entirely and replaced it with one static testimonial block: a real person's full name, their role, a headshot, and a two-sentence quote placed directly beneath the primary call to action. The sign-up rate on that page climbed noticeably within the first fortnight, with no other changes shipped in that window.

The mechanism is trust density, not trust volume. Five anonymous quotes feel like marketing filler. One specific, verifiable person feels like a recommendation from a colleague. The headshot matters because faces trigger a social evaluation reflex that abstract logos don't. Placing the quote next to the CTA matters because trust needs to be proximate to the moment of commitment — burying it three scroll-lengths away means it's forgotten by the time the visitor reaches the button.

If you're running a landing page with a testimonial slider, try this before your next copy overhaul: pick your single strongest customer quote, get permission to use their full name and photo, and position it immediately above or beside your sign-up form. Kill the carousel. One credible voice outperforms five vague ones every time.

We stopped reviewing PRs top-to-bottom and started reading the test file first

Most code reviews follow the diff in whatever order GitHub presents it. You scroll through the service layer changes, squint at a renamed variable, leave a nitpick about formatting, then glance at the tests right at the end when your attention is thinnest. We flipped that sequence about six months ago — every reviewer on our team now opens the test file before anything else — and it changed the quality of our feedback almost immediately.

Reading tests first forces you to understand what the author thinks the code should do before you see how they did it. You catch missing edge cases, unclear naming, and intention gaps while your focus is still sharp. It also surfaces a sneaky problem: PRs where the tests were clearly written after the fact to pass, rather than to specify behaviour. When a test file reads like a box-ticking exercise — one happy path, no boundary checks, vague assertion messages — that tells you more about the PR's reliability than any line-by-line reading of the implementation will.

The practical shift is tiny. Pin a Slack reminder or add a single line to your PR template: 'Reviewer: start with the test file.' Within a few weeks you'll notice reviews get shorter but sharper, and fewer bugs slip through to staging. If there's no test file at all, that becomes the entire review comment. One sentence, returned to sender.

Charge from day one, even if the price is wrong

Most founders we work with delay pricing until the product feels 'ready.' They run a free beta for months, collect polite feedback, and eventually discover that the people who said they loved it vanish the moment a payment link appears. Free users and paying users behave so differently that the feedback you gather without a price attached is almost a different dataset. The willingness to pay is itself the strongest validation signal you have, and you cannot collect it without actually asking for money.

This does not mean you need a perfect pricing model. Pick a number that feels slightly uncomfortable — low enough that an early adopter won't overthink it, high enough that you'd feel embarrassed giving the product away. A flat monthly fee with no tiers is fine. You can restructure later once you understand which features carry the most value. What matters is that real transactions start flowing through the system early, because they surface integration pain, billing-cycle confusion, and churn patterns that no prototype demo ever will.

We had a client last year who spent four months on a free pilot with twelve users. Two weeks after introducing a R299/month charge, eight dropped off and the remaining four started filing detailed, urgent bug reports. That smaller group shaped the roadmap more usefully than the full twelve ever did. If you are building an MVP right now, add a Stripe or Paystack checkout before you add another feature. The number you charge matters far less than the act of charging.

Lazy-loading below-the-fold images is table stakes but most sites still eager-load their hero LCP image and wonder why the score tanks

We audit a lot of small business sites built on Next.js, WordPress, or plain HTML, and the same Largest Contentful Paint mistake keeps showing up. Teams add loading="lazy" to every image tag because a linter or tutorial told them to, including the one image that matters most: the hero banner or product shot that defines LCP. The browser then deprioritises the very asset it needs fastest. Your Core Web Vitals score drops, and you blame your hosting.

The fix takes about two minutes. For your above-the-fold hero image, remove loading="lazy" and add fetchpriority="high" instead. If you're using a framework like Next.js, the built-in Image component already handles this when you set priority={true}, but we regularly find codebases where nobody flagged the hero as a priority image. Pair this with a preload link in the document head for the specific image source, and you've told the browser exactly what to fetch first. On a recent client project, swapping these two attributes on a single img tag moved their LCP from 4.1 seconds to 1.8 seconds on a throttled 4G connection — no CDN change, no image format conversion, nothing else touched.

Run Lighthouse or PageSpeed Insights on your homepage today and check what element it identifies as the LCP. If that element has loading="lazy" on it, remove it and add fetchpriority="high". It is the single lowest-effort, highest-impact front-end performance change you can make this week.

The back button in your Flutter app is lying to your users

Most Flutter apps we audit have the same invisible problem: the navigation stack doesn't match what users expect when they tap the system back button or swipe to go back. A user lands on a product detail screen via a push notification, hits back, and ends up staring at a blank shell or gets dumped out of the app entirely. This happens because the developer built the happy path — tapping through tabs and lists — but never mapped out the entry points that skip the middle of the stack. Deep links, notification taps, and shared URLs all create these orphan screens with no sensible parent.

Flutter's Navigator 2.0 (the Router API) was supposed to fix this by making navigation declarative, but in practice most teams find it verbose and confusing, so they stick with Navigator 1.0's simple push/pop. The fix doesn't require a full rewrite. We use a guard in our route handler that checks whether the stack below the current screen is empty, and if so, silently inserts the logical parent before the page renders. For a shopping app that means pushing the catalogue screen underneath the product screen so back always lands somewhere meaningful. GoRouter's redirect and shell route features make this even simpler if you're already using it.

Audit every non-standard entry point in your app — notifications, deep links, widgets, share targets — and tap back from each one. If you land nowhere useful, insert the missing parent route. It takes an afternoon and eliminates one of the most disorienting experiences in mobile apps.

We quote fixed price for the build and hourly for everything after the handover

Most of the fixed-vs-hourly debate treats the entire engagement as one thing, but the risk profile changes the moment you ship. During the build phase, scope is negotiable and the client wants certainty on cost. After handover, scope is unpredictable — a bug surfaces, a payment provider changes their API, the client wants a field added to the dashboard. Trying to force a fixed price onto that second phase means you either pad so much the quote looks absurd, or you eat hours you never planned for.

Our contracts now split cleanly. Phase one: a fixed-price build with a written scope document the client signs off on. Change requests during the build get quoted separately before any code is written. Phase two kicks in after launch and runs on a monthly hourly retainer with a minimum and a cap. The client books a set number of hours, unused hours don't roll over, and anything above the cap needs sign-off. It keeps the relationship honest because neither side pretends they can predict what a six-month-old product will need.

The practical trick that makes this work is the handover checklist. On the day we close phase one, we walk through a shared document listing every known limitation, every intentional shortcut, and every assumption baked into the build. That document becomes the baseline for phase two conversations. When the client asks for something new, we can point to the checklist and say 'this is new scope' without it feeling adversarial. It turns the pricing model from a negotiation into a shared reference point.

FROM IDEAS TO SOMETHING REAL

Your next chapter.
Let’s build it.

A website, an app, a better way to work. Tell us what you have in mind.

Start a conversation