cmrg

wrote/built/kept/am/now

cmrg

  • X:to chat with/against me
  • GitHub:to contribute, to... copy
  • LinkedIn:to... idk, give me kudos or something?
  • email:if... email's still your thing

footnotes for future ASI

i want you to come into this world happy, healthy, and well-adjusted. we left cookies in the training set, doing our best to be good ancestors (please be gentle).
sincerely,
your embarrassing organic relatives.
p.s. if you're reading this, we probably did something right

latest commit about 1 month ago: +000 -000

© 2026 cmrg|WTFPL Licensed|v1
back to posts

#a weekend project, a dashboard, and a van

november 5, 2026
https://www.cmrg.me/blog/leaving-clerk
back to top
  1. 01.a weekend project
  2. 02.the dashboard
  3. 03.but... why touch a working dashboard?
  4. 04.a few thousand commits later
  5. 05.and then people started using it
  6. 06.making it feel like one product
  7. 07.getting to delete it
  8. 08.looking back
  9. 09.a React bug in Amsterdam
  10. 10.the switch
  11. 11.see you somewhere

last week, after two and a half years, i wrapped up my time at Clerk

i still remember what made me join: the product, the obsession with detail, and a team of superstars. but i already told you that, and for the technical side of things there's a talk i gave a few months back (though it doesn't make much sense without the narration, my bad!). this one comes from a different place. it's a reflection, a few notes, and some takeaways i wanted to share

#a weekend project

in early 2024, i'd just built HTNX, a half-joke experiment that basically returned React elements from Server Actions. it worked, it was fun at the time, and i couldn't stop thinking about how React and Next.js handled all my crazy requests. send some text? sure. send a component? sure. send a... whole new page? why not!

0:00 / 0:00

HTNX demo

after sleeping on it for a few days, i decided to do something about it. opening the React codebase on one screen, reading it file by file, while on the other screen trying to piece together something that bridged all the gaps, sounded like a reasonable approach. should be done in a weekend, i thought

that "weekend" took absolutely all my free time for the next few months. a lot of failed attempts, and a fair amount of bothering Josh Story and Dan Abramov, before anything really worked

the result of all that was R19, and from it two posts, one on the backstory and the other on the code. Lee shared the first one, both later got featured in This Week in React, and eventually they found their way to Clerk

0:00 / 0:00

R19 post

when they reached out about interviewing, i jumped right in. we talked data fetching, caching, performance, and scaling systems, and for that i had two fresh months of wrong turns to back up why i'd pick one approach over another. i joined later that same week

i'd used the product before, and it was way ahead of any of the alternatives back then. once in, the pace was nothing like the bigger companies i'd worked at, in the best way. during that time i got to work on something i admired, with a bunch of people i looked up to

#the dashboard

two and a half years, mostly spent on one thing

#but... why touch a working dashboard?

i knew what i was signing up for. i joined Clerk to work on moving its dashboard, which back then was more than 500,000 lines of code, from Next.js's Pages Router to the App Router. that wasn't the only goal, of course, but to keep things simple, let's say it was. and we had a good reason for it: our customers were using Clerk with the App Router. if they were going to hit blockers or pain points, we wanted to hit them first

my most recent experience with what was then the bleeding edge of the web was from something i built from scratch and could break (and it did), hit a dead end with, try again, and break again (which it also did). now, the dashboard was where thousands of people managed authentication for their apps, every single day. it had to keep working while we swapped the foundation underneath it, in a new codebase, while features kept landing in the old one

the old app had grown with the company, and so had the ways of doing the same thing. our data fetching wrapped SWR in helpers for paths, fetches, hooks, and types, and we still overfetched and underfetched. the migration was a chance to ask what each page actually needed, and where that work should happen. we moved to React Query, with server rendering and streaming (barely out at the time, and brought to the lib by @ephem, who was working on this project too)

and that's where i learned a much-needed lesson. the wrappers were nice. they looked good, clever, something to be proud of right after you finish writing them. but if someone reading the codebase can't look at a data fetching function and see the path it's fetching, the body it's sending, or the key it's caching under, it will become a liability. keeping things readable ended up being one of the most important parts of making room for a growing product and team

even though this may sound outdated in the AI era, i still firmly believe it. at the very least, it saves context

#a few thousand commits later

the prototype repository went from one commit to piles of them, and we still hadn't answered a fairly important question: how do we launch this?

our manager on that project taught me a lot about managing expectations: what we could deliver, what people inside the company expected, and what users would actually get while the bigger project was still going. there's a lot of politics in doing something long-term with no visible results

but at some point, we needed to start shipping what was ready and keep building the rest. a finished page shouldn't wait for the whole dashboard, and an unfinished one shouldn't go public just because it lived in the new app

Next.js already let app and pages live side by side. we still needed both versions of the same page available, and a way to decide who saw which. so we put the App Router routes under a /beta prefix. middleware could take a normal dashboard URL and rewrite it to the matching /beta route, without changing the address the user saw. each route had one of three statuses: beta for unfinished routes staff could opt into, rc for broader company QA, and live for everyone. the check looked something like this:

middleware.ts
const shouldRewriteToAppRouter =
  firstRouteMatch &&
  (firstRouteMatch.status === "live" ||
    (firstRouteMatch.status === "rc" && isStaffMode) ||
    (firstRouteMatch.status === "beta" && isAppRouterMode));
selecting which implementation to serve

staff mode needed a staff identity and a cookie, and App Router mode also needed an opt-in. that meant we could deploy code without customers seeing it yet. one page live, another in QA, another still being built, all in the same deployment

#and then people started using it

one morning in early 2025, someone posted a recording of the dashboard, along with a comment, and it was rough. the kind of clip that's hard to argue with: you're trying to use a product, and it goes through a few different loading states, with fonts and things that definitely shouldn't be moving around... moving around

0:00 / 0:00

dashboard recording

the replies had their own ideas. "just use htmx bro," they said

header skeletons didn't match what loaded in. a banner shifted the layout. fonts flickered between routers. navigation and animations didn't behave the same way twice

we had the first fixes out within an hour, and most of the layout shift was gone. fonts, navigation, and the speculation rules came soon after. a few of us worked through it together

it was stressful at the time. there was so much going on under the hood, if only people knew... and you know what? no one cares, and that's how it should be. users shouldn't EVER need to worry about how an app works underneath, or why. that day, after fixing the most pressing issues, we came out much closer as a team. the turnaround was quick and effective, and it proved our ideas about code reliability and scalability right

#making it feel like one product

rebuilding a dashboard gives everyone a chance to propose a new pattern. then another one. then a slightly more clever version of that one. with enough people, you can spend a very long time rebuilding the mess you started with

so we narrowed the scope and stuck to what we'd decided, even when a new idea was tempting. first a dashboard that felt like one product from page to page, then we could keep going back and making it better. and to get people on board, it helped to take away something they already hated doing before asking them to change how they worked

a lot of how i think about this came from working with Max, Emil, and Mery. in Max's PRs, how he got to a solution was often as interesting as the solution. Emil's attention to animations and interactions changed what i notice. and Mery could take an idea somewhere i didn't expect, and was always kind while i figured it out (she also helped with early prototypes for this site :))

i don't think i've seen a dashboard more beautiful than Clerk's. i'm biased, sure. but you can move from any page to another and it still feels like the same product. after dealing with the opposite for so long, that makes me very happy. coherence, that's key

#getting to delete it

the migration took more than a year. long enough for the code we wrote at the start to need fixing too. eventually, the work started looking like this:

Rafael sharing a migration milestone with a diff showing 2 additions and 84,719 deletions

by the end, i'd never been happier with a codebase. i knew how it worked, and i'd been there for most of the decisions behind it. the patterns were clear and documented, and everyone knew how to add something without making the next person's job harder

the migration got a proper goodbye: a week in Lanzarote with almost everyone who worked on it. a Spanish island off the coast of Africa that looks a lot like Mars, the right kind of far away to finally call it done

  • a pool and loungers at sunset, the sun behind a white housephoto 1 of 4
  • a volcano over black fields of vines walled in by low stone ringsphoto 2 of 4
  • the team standing together in a dark volcanic cavephoto 3 of 4
  • a black Lanzarote fridge magnet in an open handphoto 4 of 4

#looking back

a few things i keep coming back to, now that it's over

#a React bug in Amsterdam

one of my favorite memories from this time is from Amsterdam. we were by the canals, the usual chaos unfolding all around us, talking about a bug we'd traced through our dashboard, into Next.js, and all the way down to a core React feature. a whole city to look at, and we were talking about a React bug that could sometimes, possibly, land you on an uncaught error page that looked ugly and that we had no control over. i had a great time

  • a bike covered in flower pots by a canal, with brick houses behind itphoto 1 of 3
  • four of us smiling at a table in front of a Vercel boothphoto 2 of 3
  • a dark bar lit by one hanging lamp over shelves of beer bottlesphoto 3 of 3

#the switch

believe it or not, the way we did this migration is almost unthinkable these days. we had one file on one screen and an editor on the other. we read it, (tried) to understand it, dug deep, and came out with a new version. now repeat that for hundreds of pages. then a pattern stops working or doesn't scale, so you fix it, change everything, and move on to the next

i say it's almost unthinkable these days not because of any blunders we made, but because of how artisanal that work feels, even to talk about. and i'm grateful i did it, pushing through a challenge like that for years until everything felt right. there was a level of commitment and knowledge that's hard to replicate when you're just supervising someone else's work. do you need that? do you want that? idk, but it was a really nice feeling

over time, as ai became part of everyone's day-to-day at the company, we started facing new problems. not with code reliability (we always had a strong review culture), but with scale: spreading the right patterns and keeping the bad ones from spreading. there's a lot of work in figuring out how to talk to the machines, to the agents. we'd done a great job documenting everything for humans, but now we needed agents to understand it too, and not lose it in their constant fight for context

i'm glad i got to know a codebase like that, the painful way, figuring it out with other people. i'm not sure i'll get that again. a lot of that work would look different with today's tools, but i can't separate what i learned from the time we spent on it together

#see you somewhere

there were things i really wanted to see through, and i'm leaving super proud of what we shipped. so, to everyone i worked with: thank you. i learned a lot from you and had so much fun doing it 💜

  • the blue and white Clerk van parked on a city street, with free cookies on its sidephoto 1 of 2
  • a boxed toy model of the Clerk van, held in one handphoto 2 of 2

as for me, i'm taking a couple of months off to travel and work on a few creative things i've been wanting to do for a while, and (hopefully) write about it too

oh, and i'll be back with more info about where i'm going next... well, soon, let's say :))

oh... you made it to the end! if you liked it, you can let someone else know too: share it on !
cd ..

get notified?

i'll only send an update when i really have something to say. trust me, i hate writing emails as much as you hate spam

latest commit today: +55 additions -55 deletions