No updates detected yet. GitHub releases will appear here.
Loading the full README. The saved excerpt is shown below.
A feed where African founders post what they're building — with screenshots and
video — and the community likes, comments and reviews. That engagement is what
produces the leaderboard.
Built with Next.js 16 (App Router), CSS Modules, and Firebase (Firestore +
anonymous auth) — all on Firebase's free Spark plan.
The app reads and writes everything through Firestore. Several of these steps
are in the Firebase console and can't be done from the codebase.Read the full README ↗A feed where African founders post what they're building — with screenshots and video — and the community likes, comments and reviews. That engagement is what produces the leaderboard.
Built with Next.js 16 (App Router), CSS Modules, and Firebase (Firestore + anonymous auth) — all on Firebase's free Spark plan.
The app reads and writes everything through Firestore. Several of these steps are in the Firebase console and can't be done from the codebase.
Everything here works on the free Spark plan. Firebase Storage is the one service that now requires the paid Blaze plan, so image uploads go to ImgBB instead — free, no card. See Media for the one key that needs setting.
Console → Build → Firestore Database → Create database. Any region is fine; pick the one closest to your users.
If you plan to seed in the next step without loosening rules, start it in test mode. Otherwise start in production mode and read step 3 first.
Console → Build → Authentication → Sign-in method → Anonymous → Enable.
Likes, comments and reviews are attributed to an anonymous account, so one person gets one like per post without being forced to register. It must be Anonymous specifically — enabling Email/Password or Google instead will not satisfy it.
firebase deploy --only firestore
Two files matter here:
firestore.rules — who can read and write what.firestore.indexes.json — the composite index the
feed's collection-group query needs. Without it the feed shows an error with a
one-click link to create it.You can also paste the rules into the console under Firestore → Rules.
The Firebase API key in
.env.localis a public identifier, not a secret — it ships in the browser bundle by design. These rule files are what actually protect your data, so don't skip this step.
npm run seed
Writes 10 startups, a launch post for each, their reviews, and 5 reviewer
profiles. Safe to re-run: every document has a fixed id, so a second run
overwrites rather than duplicating. Existing startups are skipped unless you pass
--force — which is also how you migrate them after a schema change.
The seed writes directly to startups/ and reviewers/, which the rules in
step 3 forbid by design — it writes records it doesn't own, and reviewer
profiles are editorial. Publish
firestore.seed.rules first, run the seed, then publish
firestore.rules again. The seeding ruleset opens only the
collections the seed touches, so likes and reviews stay protected even during
that window.
npm run dev
npm run check:firebase
Tells you which of the steps above is still incomplete, and what to change in the console for each one. Firebase's own error codes are precise but don't say which setting produced them.
Until the steps above are done the app doesn't show a blank page or a raw Firebase error — it renders the specific setup step that's still missing.
.env.local holds the Firebase web config (already filled in for the
techziminteractive project) and the ImgBB upload key. For a different project,
copy .env.example and fill the Firebase values from Project settings →
General → Your apps → SDK setup.
| Variable | Needed for |
|---|---|
NEXT_PUBLIC_FIREBASE_* | Everything — the app won't load without them |
NEXT_PUBLIC_IMGBB_API_KEY | Uploading images from a device. Optional; without it founders paste links |
| Path | Contents |
|---|---|
startups/{id} | The startup profile, plus its rolled-up engagement counters |
startups/{id}/posts/{id} | A feed entry — text, images, video |
startups/{id}/posts/{id}/comments/{id} | Conversation under a post |
startups/{id}/reviews/{id} | A structured review (3 rated dimensions) |
likes/{postId}__{uid} | One like. The composite id makes double-liking unrepresentable |
helpful/{reviewId}__{uid} | One "helpful" mark on a review |
reviewers/{id} | Editorial reviewer profiles |
The feed is a single collectionGroup('posts') query. Startup identity (name,
slug, logo) is denormalised onto each post so a card renders without a
lookup to its parent, and approved is mirrored from the parent so the query
can filter in one pass. Joining to the parent per post would mean one extra read
per card — which a feed cannot afford.
That denormalisation is the one place this schema trades duplication for speed; everything else is derived.
Engagement in the feed produces the ranking. Weights reflect effort:
| Signal | Weight | Why |
|---|---|---|
| Like | 3 | One tap |
| Comment | 5 | Costs a sentence |
| Review | 12 | Costs thought |
| Avg rating | 40 | Quality, not volume |
A like on a post increments the post and rolls up to its startup in the same transaction, so a startup total can never disagree with the sum of its posts.
Three things are deliberately not stored:
Weekly and monthly like counters are stamped with the period they belong to
(weekKey, monthKey). A counter from a past period reads as zero and resets on
the next write, so the two windows stay honest without a scheduled job.
This is the beginning of the README. Read the rest at the original. Read the full README ↗