← All posts

Building LinkedIn Growth OS: A 10,000-Line Personal CRM I Never Planned to Build

June 2025Full-Stack · AI

LinkedIn's native analytics are broken. Not broken like a bug — broken like they were designed by someone who's never tried to grow an audience. You get a vague impressions number. Maybe a reaction count. No memory. No benchmarking against yourself. No way to know if a post performed well or just triggered a notification spike that decayed in four hours.

So I built a spreadsheet. Then I built a script that pulled data from the spreadsheet. Then I built a dashboard. Then I looked up and it was 10,000 lines of TypeScript.

The Scope Creep Problem

LinkedIn Growth OS has 19 API routes, 7 data models, and 41 React components. None of that was planned. It grew because every time I used the early version, I found something else that was annoying about LinkedIn that I could fix.

The original spec: log a post, record its metrics, see a table. The final product: Day 1/7/30 checkpoint tracking, a normalized scoring engine, four AI features powered by the Claude API, a content ideas vault, a voice recorder for ideas, and a post autopsy system that tells you exactly why something worked or didn't.

The Day 1/7/30 System

Here's the insight that made this worth building: measuring a LinkedIn post once is almost meaningless. Impressions decay. A post might get 80% of its reach in the first 24 hours, or it might keep climbing for a week. You can't tell from a single snapshot.

So every post gets three checkpoints: Day 1 (initial spike), Day 7 (whether it held), Day 30 (long tail). The system flags which checkpoints are overdue and surfaces them on a dashboard. You pull the current number, log it, and move on. Takes 30 seconds per post.

This alone made the tool worth using. I now know which of my posts are evergreen and which peaked immediately. That changes how I think about what to write next.

The Scoring Engine

Absolute impression numbers are misleading — especially when your audience size is changing. A post with 500 impressions when you have 200 followers is better than 500 impressions when you have 2,000. The scoring engine normalizes against your own historical 30-day average, which means a 'good' post is always defined relative to your current baseline.

The formula weights six signals: impressions (35%), reactions (20%), saves (20%), profile viewers driven by the post (15%), comments (5%), and new connections (5%). Saves are weighted heavily because they signal genuine intent — someone found it useful enough to want to come back to it.

The formula is unstable with fewer than 5 posts. I'm saying that plainly. With a small dataset, the 'baseline' is too noisy to normalize against. It gets more useful over time.

The Four AI Features

All four are powered by the Claude API with heavy prompt engineering. I spent more time on the prompts than on the API integration.

Engineering Decisions

SQLite over Postgres. This is a single-user tool. There's no reason to stand up a Postgres instance, manage connections, or pay for a hosted database. SQLite runs in the same process, the file is portable, and it's fast enough that I've never hit a query over 20ms.

I put all modal state in a single modals.tsx file. Every modal in the app — and there are a lot — lives there. This is a deliberate violation of co-location orthodoxy. In practice, modals are referenced from many places and having them scattered made refactoring painful. One file, one truth.

In-memory rate limiting for the AI endpoints. 20 lines of code. Resets on server restart. Not suitable for production at scale — but this is a personal tool running locally. Redis would be 200 lines plus a container plus config. The 20-line version handles the one person using it.

The Honest Limitations

No auth. If you deploy this, anyone with the URL can use it. It's designed for local use or behind a private tunnel.

No tests. I should write some. I haven't.

The rate limiter resets on restart. If you restart the server, the window resets. Acceptable for personal use.

The scoring formula is unstable with fewer than 5 posts. Already said this. Saying it again.

I built this for myself. It works. It's the most complete thing I've shipped solo. That's enough.

View all projects →