I run a small e-commerce site in Kampala, Uganda called PowerBulb.ug. We sell rechargeable LED emergency bulbs and power-backup equipment to households affected by UEDCL load shedding. Five months after launch, we rank on page 1 of Google for a growing list of commercial-intent queries. This post is the technical write-up of how — and why the playbook is almost embarrassingly boring.
The premise: zero-competition SEO is a real market
Most SEO advice is written for saturated markets. "You need 500 backlinks, a DA of 60, and eighteen months of content marketing." This is true in California. It is not true in Uganda.
I did a competitive audit of the top 10 results for every commercial keyword in my niche: rechargeable bulb uganda, emergency bulb kampala, voltage stabilizer uganda, etc. Here is what I found:
- No competitor had a valid
Productschema. - No competitor had
LocalBusinessorPlaceschema. - No competitor had
hasMerchantReturnPolicyorshippingDetails. - Most competitor pages were Jiji.ug listings with broken image gallery, no H1, no metadata.
- Two had Core Web Vitals LCP > 8 seconds.
- None had a blog, ever.
This is a market where Google's ranking algorithm is basically yelling for help. Anything halfway-competent ranks. The question is what "halfway-competent" means technically.
Stack
- React 18 + TypeScript + Vite 6
- Tailwind CSS v4
- Framer Motion
- Lucide React (icons — no emoji in production UI)
- Static HTML pages for non-SPA routes (/products/, /kampala/, /blog/)
- Deployed: Kubernetes (Megav Cloud) via Helm + GitHub Actions
- Cloudflare Free plan, geo-block non-UG traffic
Why not Next.js? Honestly because I wanted minimum moving parts for a single-person team. Vite + static HTML for SEO-critical pages is less clever than SSR but infinitely easier to debug at 2am.
The schema strategy
Uganda is not a market where complex schema is needed. It is a market where any valid schema wins. I shipped:
Product schema on every SKU page:
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Rechargeable LED Emergency Bulb 15W E27",
"offers": {
"@type": "Offer",
"priceCurrency": "UGX",
"price": "30000",
"availability": "https://schema.org/InStock",
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "UG",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 7,
"returnFees": "https://schema.org/FreeReturn",
"returnMethod": "https://schema.org/ReturnAtKiosk"
},
"shippingDetails": { /* UG-specific */ }
}
}
Three things tripped me up:
returnMethod— if you shipMerchantReturnPolicy, Google flags it as a non-critical issue unless you specify. I usedReturnAtKioskbecause delivery is by boda-boda (motorcycle taxi) — the rider collects the return in person.ReturnByMailwas wrong; Uganda has no household postal pickup.priceCurrency: "UGX"— Google's Rich Results Test initially refused to show price in SERPs because my shipping rate object was missing acurrencyfield. The fix was addingMonetaryAmountwith explicit currency to every rate.availability— theInStockschema triggers "In stock" rich snippet. Worth every second it takes to configure.
LocalBusiness + Place schema on the homepage and Kampala hub page. Five neighbourhood pages (/kampala/ntinda/, /kampala/naalya/, etc.) each get their own Place schema with latitude/longitude coordinates — this is what makes Uganda-specific "near me" queries surface the site.
BlogPosting + FAQPage schema on blog articles. FAQPage schema is wildly undervalued — it gets you the accordion-style rich snippet below the search result, which eats 30% more SERP real estate.
The hyperlocal pages trick
I noticed that competitors were all targeting generic terms like rechargeable bulb kampala. Nobody targeted neighbourhood queries. So I shipped:
/kampala/ -- hub page
/kampala/ntinda/ -- Ntinda-specific
/kampala/naalya/
/kampala/bukoto/
/kampala/kireka/
/kampala/muyenga/
Each page has:
- Unique H1 with the neighbourhood name
- Unique hero image (AI-generated, but authentic-looking — ethnic and architectural detail matter)
- Local landmarks in the copy (street names, churches, schools — stuff a boda driver would recognise)
- Specific delivery time to that neighbourhood
- Local
Placeschema with coords - 600-900 words of unique copy per page
- Internal links to/from the hub
These rank for rechargeable bulb ntinda, emergency bulb kireka, etc. within days of indexing because there is literally no other English-language page on the internet optimised for those queries. Google has to rank something. It ranks you.
Content cadence that works for a solo team
I cannot write a blog post a week. Nobody in my situation can. The compromise:
One cornerstone article per month, 1,500-2,500 words, targeting a commercial-intent keyword with buying signals. So far I have shipped:
- "UEDCL vs UMEME: complete Kampala guide to the 2026 transition"
- "Types of bulbs in Uganda: rechargeable vs LED vs solar (buyer's guide)"
The pattern: the article answers a real question, mentions the product in context (not as a pitch), and internally links to 3-5 product pages. Google treats the blog as topical authority for the product pages, which pulls them up in SERPs. This is the old cornerstone content model and it still works.
What surprised me
GSC indexing is faster than I expected. New pages are indexed in 24-72 hours with Request Indexing in Google Search Console. In a mature market you wait weeks. In Uganda, Google seems happy to index anything because it improves their results.
Bing matters more than I thought. Uganda has a non-trivial Edge/Bing user base in corporate networks. I submit to Bing Webmaster Tools + IndexNow for every new page — same-day indexing, separate traffic stream, almost no effort.
Cloudflare's geo-block improves crawl quality. I block non-UG human traffic but whitelist Googlebot, Bingbot, and a few academic crawlers. This keeps bot traffic clean, reduces server load, and (I suspect) marginally helps locality signals because Google sees the site serving Uganda consistently.
Image authenticity beats image quality. AI-generated hero images work if they look Kampala-specific — correct architectural detail, skin tones, clothing, streetscape. Generic stock photography tanks engagement because Ugandan users spot "this is not here" instantly.
What I would do differently
Ship structured data first, not last. I added hasMerchantReturnPolicy three weeks after launch. Every day I waited was a day I was invisible to Google Shopping intent signals.
Write the blog in the first week. I was too focused on product pages initially. The blog drives organic discovery for top-of-funnel queries that the product page will never rank for.
Build the sitemap generator first. I manually maintained sitemap.xml for two months. Moving to a build-time generator that reads every page and outputs correct <lastmod> dates cut indexing lag by 40%.
The repo
The site is powerbulb.ug. The repo is private but I am considering open-sourcing the SEO-relevant parts (schema generator, sitemap builder, geo-SEO page template) as a standalone starter. If you would find that useful, reply below — I'll bump it up the queue.
TL;DR
Zero-competition SEO markets are real. They reward basic execution (valid schema, unique copy, hyperlocal pages) that would not even register in saturated markets. If you are building for a developing market, the ceiling is higher and the competition is lower than you think. Ship the boring stuff first.
I is a Kampala-based software engineer and the founder of PowerBulb.ug. Follow me on Dev.to for more write-ups on building for underserved markets.
Top comments (0)