lakebbs
City-aware Chinese local community platform for Thunder Bay, Sudbury, and Northern Ontario — recently rewritten onto Next.js 16 + Drizzle, with daily aggregation bots and GEO-optimized landing pages.
Context
Chinese communities in smaller Canadian cities — Thunder Bay, Sudbury, the rest of Northern Ontario — coordinate housing, jobs, classifieds, immigration questions, and local knowledge almost entirely inside WeChat groups. WeChat is a fine chat client and a terrible permanent record: you can’t search it, you can’t categorize it, group caps lock out new members from existing context, and Google obviously can’t index it.
lakebbs treats that information as a first-class artifact instead of a stream. It’s not a forum clone — it’s a city-aware Chinese local information platform somewhere between forum, classifieds, housing board, and settlement guide.
Approach
City-aware namespace, not a tag
Every surface is scoped by city: /[city]/[section]. Thunder Bay rentals and Sudbury rentals are different feeds, different SEO targets, different landing pages. A post in one city doesn’t contaminate the other, and a query like “rent in Thunder Bay” lands on a real page instead of a generic national one that ranks for nothing.
Daily aggregation keeps the feed alive before critical mass
The hard part of any community product is the cold-start problem — empty feeds kill retention. lakebbs avoids it by running a set of daily aggregation bots on the private side:
- Housing digest — Kijiji + other sources, scraped, deduped, surfaced into
/[city]/realEstate. - Job digest — LinkedIn / Kijiji / City of Thunder Bay postings.
- IRCC immigration briefing — daily Canada immigration updates summarized in Chinese.
- r/ThunderBay digest — hot Reddit threads translated with selected comment highlights.
This turns lakebbs into a “daily-open” surface, not a request-response forum. Even with a small user base, the page has fresh content every morning.
GEO landing pages — built for AI search, not just Google
Dedicated landing pages (/landing/thunderbay-rent, immigration, RNIP, Lakehead, etc.) turn local knowledge into long-lived discovery assets. The product treats Generative Engine Optimization as a first-class concern alongside classical SEO:
- Dynamic
sitemap.xmlcovering root, cities, sections, landings, and the newest 5K posts (1-hour revalidation). - Dynamic
robots.txtwith an explicit AI-crawler allowlist plus aBytespidercrawl-delay. - Structured data + OG + FAQ schema on landing pages.
- Admin-side GEO readiness checklist and citation tracking — measuring whether the site is actually being picked up by AI search.
This is where the product gets defensible: Google indexing a city forum is normal; being one of the cited sources when someone asks an LLM “where should I rent in Thunder Bay” is not.
Stack rewrite (kept the database)
lakebbs started on Vue 2 + Express + Sequelize. It’s now been migrated to a modern TypeScript stack — without a database migration. The MySQL 5.7 schema stayed; Drizzle ORM was pointed at it as-is.
| Layer | Now |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19, Server Components + Server Actions |
| Styling | Tailwind 4, Base UI, shadcn |
| Data | Drizzle ORM over existing MySQL 5.7 |
| Auth | NextAuth v5 (credentials + session) |
| Deploy | systemd lakebbs-next.service on :3477, nginx in front |
Keeping the database fixed during a stack rewrite is the boring-but-correct decision — it means the migration is a code project, not a data project. Live cutover happened with zero schema change and zero user-visible downtime beyond a nginx reload.
Product surface
- City-aware forum with image galleries and 6 standard categories (新鲜事 / 资讯 / 二手 / 房产 / 汽车 / 工作移民)
- Long-form local guides alongside short threads (housing-area picks, immigration playbooks, licensing notes)
- Likes, saves, comments (latest / hottest / oldest), recommended posts at the bottom of every detail page
- 1:1 private messaging, conversation actions (block, archive), in-app notifications
- User profiles with follow / unfollow / blocklist
- Full-text search across the site
- Admin dashboards: geographic audience distribution, per-bot AI-crawler activity (PV, last-seen, purpose), GEO readiness signals
Results & What I Learned
Live in production at lakebbs.ca, serving Thunder Bay, Sudbury, and other Northern Ontario cities.
The thing I underestimated: how much of a community product is just keeping the page alive on day-zero. A forum with empty categories looks dead even if the design is good. The daily aggregation pipeline was originally framed as “nice-to-have content automation” — it turned out to be the load-bearing component. Without it, the SEO landing pages have nowhere to link to and returning users see the same posts they saw yesterday.
The second thing: city-aware namespacing is the single most defensible architectural decision. Tags lose. A user landing on a Toronto-flavored query and finding a Thunder Bay forum will bounce. A user landing on /thunderbay/realEstate finds exactly the content they came for. SEO and product UX point the same direction here.
The stack rewrite was also a lesson in scope. The temptation was to redesign the schema while I was rewriting the app — “as long as we’re rebuilding, let’s fix everything.” I didn’t. Kept MySQL 5.7, kept the table shapes, pointed Drizzle at them, and only modernized the layers that actually needed it. The migration shipped in weeks instead of months.
Tech Stack & Links
Stack: Next.js 16 · React 19 · TypeScript · Drizzle ORM · MySQL 5.7 · NextAuth v5 · Tailwind 4 · Base UI · shadcn
Live: lakebbs.ca
Public overview: github.com/YSKM523/lakebbs-ca (private implementation kept separate)