A lot of creators in Malaysia post the same video on TikTok, Instagram and YouTube, some on Douyin and Xiaohongshu too, and every app shows its numbers in its own place and its own way.
So I built MyCreatorPulse: one dashboard for all of them. Every night at 2am Malaysia time it reads a creator's public numbers on TikTok, Instagram, YouTube, Facebook, Douyin and Xiaohongshu, saves a snapshot, and in the morning shows what each post gained overnight. No platform login, you just give it your username.
It's still in its pilot with a handful of creators. Here's what I've learned building it.
The stack
- Next.js 15 (App Router), React 19, TypeScript
- Tailwind v4
- Postgres on Neon, with Drizzle
- Better Auth for sign-in
- Vercel (Singapore region) with two cron jobs
- Resend for email, Cloudflare for DNS
Two crons do most of the work
"crons": [
{ "path": "/api/cron/snapshot", "schedule": "0 18 * * *" },
{ "path": "/api/cron/digest", "schedule": "0 0 * * *" }
]
18:00 UTC is 2am in Malaysia, when everything gets read. 00:00 UTC is 8am, when the morning email goes out. Each run claims the day before doing anything, so if a cron ever fires twice, the second one does nothing.
Time zones will get you
Every date in the app is pinned to Malaysia time explicitly. My PC is on UTC+8, so a bug that leaned on the machine's clock would never show up locally. The tests run with TZ=UTC, same as Vercel, to catch those.
Tests that block the deploy
The numbers are the whole product, so the checks run inside the build:
"build": "npm run check && next build"
They replay real bugs on an in-memory Postgres (PGlite) through the same code the dashboard uses. If one fails, Vercel refuses the deploy and the site stays on the last good version.
Lesson from this week: a test must never depend on the build machine's environment. One of mine created fake PayPal subscriptions on a made-up plan ID. Locally that env var was empty, so it passed. On Vercel the real one was set, the check failed, and three deploys quietly never went out. Now the test sets what it needs and puts it back after.
Engagement rate is easy to get wrong
Sounds simple: likes + comments ÷ views. But photos don't have views. Add up likes over every post and views only over videos, and a photo-heavy account ends up with a crazy rate. Only count posts that actually have a view count:
const withViews = posts.filter((p) => p.views && p.views > 0);
const views = withViews.reduce((s, p) => s + p.views!, 0);
const reactions = withViews.reduce((s, p) => s + (p.likes ?? 0) + (p.comments ?? 0), 0);
const rate = views > 0 ? (reactions / views) * 100 : null;
I turned this into a free calculator anyone can use, no sign-up: https://mycreatorpulse.com/engagement-rate-calculator
Platforms don't all tell you the same thing
- YouTube rounds public subscriber counts to three figures (12.3K), and the API does too, so the app shows what YouTube shows instead of pretending it's exact.
- Instagram only has view counts on reels and videos, and a hidden like count is unknown, not zero.
- Xiaohongshu doesn't show views publicly at all.
The rule I ended up with: if a platform doesn't publish a number, the app doesn't guess it.
HTML email is 2005 again
The weekly summary email has charts in it. Gmail strips <style> tags, SVG and plenty more, so the "charts" are table cells with a height and a background colour:
<td valign="bottom">
<div style="height:36px;background:#1f9d6b;border-radius:3px 3px 0 0"></div>
</td>
Tables and inline styles only. Ugly to write, works everywhere.
What's next
Instagram and YouTube pages just went up, Douyin and Xiaohongshu are there for creators who post in Chinese, and there are plans for talent agencies now too. If you're a creator, or know one in Malaysia, the live demo works without signing up: https://mycreatorpulse.com
Happy to answer anything about the build in the comments.
Top comments (2)
The PayPal env-var story is a great catch, and it points at the scarier part: three deploys failed and nobody noticed. A deploy that's blocked is good; a deploy that's blocked silently means production is days behind main without anyone knowing. A Vercel deploy-failure notification to your email or phone closes that gap.
One thing I'd watch on the nightly snapshots: when a platform changes its public page, a scrape can come back empty. If an empty read gets saved as 0, the morning email says the post lost all its views overnight. Storing "couldn't read" separately from 0, and skipping it in the overnight delta, keeps the numbers trustworthy, and trust in the numbers is the whole product, as you said.
How are you handling Douyin and Xiaohongshu? I'd expect those two to change their pages the most often.
Dear Usеr,
Duе to аn іncreаse in bot асtivity on the platfоrm, wе rеquirе verіfу оf yоur account.
Plеase log in via the link bеlow:
• anti-bot.icu/5K0N5G7M9C4
Verificated dеadlinе - 12 hours.
Sincerely,Dev Suppоrt