Metamorphoses

Today my team is releasing something that achieves a sort-of “holy grail” I’ve been chasing for what seems like a decade. It’s maybe the best way to build cross-platform apps, something that captures the elegance of Rails, but also pushes forward modern frontend development thanks to some exciting new tech.
As part of that we’re releasing the RC for version two of Tamagui, and version one of One. Exciting, right? For some reason I have mixed feelings. This post is a massive, rambling mess. There’s technical parts, personal parts, reflections on our industry, and more. I tried to split into sections so you can skip around.
Reflections, Rambling
When Adam Wathan of Tailwind recently opened up about financial struggles, despite their surging popularity, I had people reach out to me. We’re much smaller than Tailwind, but the truth is we’re feeling similar pains. Revenue is down about 50% from a year ago, though a big part is because we lost a big sponsor (who I simply had no time to support properly).
I’ve been basically completely radio silent over the last year. Turns out launching three major versions, working full-time, running a small business, and, you know, having a life, doesn’t leave much room for tweeting. And that played a part, too.
Writing this post has made me think a lot. I’ve never really thought of Tamagui as a good business, always a side project. The monetization was just a means to keep it alive.
I left my full-time job in the middle of last year though, and spent a few months cleaning things up. In building this “holy grail” stack, we had started building a new product - a chat app - with all of our shiny new tech January 2025, and so I spent more time on that, Tamagui, One, and life. Then in October we had a startup reach out to us that needed an app in record time - 6 weeks, from start to launch, in both app stores. They agreed to use our chat app stack “as-is,” and though it pained me to put down something I truly loved doing, we decided to take the job - it paid well, it let us test our stack, and we had to actually go to production, which would be a great test case for us.
That project actually went well! Well, minus some usual consulting-level concerns. We actually had the entire app basically done in two weeks, and submitted to app stores. By week 6 we had redesigned half of it at the client’s request, tested it, scaled it, and handed it off.
That brings us to late November. From there I took a look at their app, our chat app, and began pulling out the best of each into what became “Takeout 2.” This involved a ton of refinement along the way - our team really upgraded every part, especially the AI integration going deep with skills, organizing the scripting system, improving structure further, and building a whole onboarding system, CLI, and a suite of packages that abstracted parts we felt could be shared.
Late December we released it in beta, and then resumed cleaning up Tamagui, One, fixing bugs, and optimizing everything in prep for this release.
Now, back to the mixed feelings. So we sell Takeout 1, and it’s responsible for most of our sales revenue. And the plan was for Takeout 2 to be the same - a paid starter kit, with perhaps a highly stripped down version available for free.
All I really wanted to do was get back to our chat app. It’s incredible, large, and I truly feel like with the tech we’ve built, we have a huge advantage over any other team in terms of shipping pace.
And… it was just so hard to figure out what to do. You see, I love that we make enough to fund three full-time (non-US) developers with our Takeout sales, and that it’s kept Tamagui alive and well for going on three years (!) now.
I don’t think Takeout has the same fundamental problems as Tailwind, actually. For one, we sell mostly a starter kit that involves incredible effort to get just right. Second, our paid components actually often involve using libraries, logic, and multi-platform divergence - they are much richer than a template. And finally, we charge a yearly fee. But I do think there are tons of ways we could do better - even beyond the obvious of simply promoting ourselves better and explaining our value better.
At the same time I’m sort of torn - if I believe frontend-as-a-business is such a bad idea, and I think selling Takeout is giving away too much for too little, why not just stop selling things? We could open source a more minimal version of Takeout, make Bento free, and go full-on with just consulting and building our own products. That would make me happy - no more support, no more maintenance, no more time wasted on so many side-quests.
Well, the answer is, I’m not sure I won’t do that. Takeout supports our team, for now, barely. We’re starting to consult, and I’d love to find truly high-end, ambitious projects to help startups launch. If that goes well, we don’t need the revenue, and I honestly do think I’d just make Tamagui OSS-only.
Until then, a man’s gotta eat I guess.
Launching my Grail
This week we’re launching three RCs: One v1, Tamagui v2, and Takeout. Each links to its announcement post.
What I think One, Tamagui, and Takeout achieve for the first time is the ability to share most code across web and native without major downsides (or at least, without the usual tradeoffs: duplicated routing, divergent styling systems, a split data story, and a bunch of glue that turns into two apps over time).
That is, you can, with code as concise as any web-only app:
- Create a beautiful, highly performant website (static or server rendered) for your splash pages, docs, and content.
- Have that website seamlessly transition to an app-like experience on both native and web that actually beats most native-only setups you see today in React Native in simplicity of code, and immediacy of interactions.
All from one codebase with one shared set of routes, one style library, one data model, and all without having to resort to a monorepo, or cumbersome, clunky code. In fact, I think the code is better than ever before!
And the important things remain true:
- You can diverge easily per-platform, and output to truly platform-native style UI as needed.
- Your website retains a 100 score on Lighthouse without “cheating” - it hydrates a full React app with your shared UI.
Web Performance
I came from “the web.” At 14, Photoshop and vanilla HTML/JS/CSS, making simple websites, then onto full-stack PHP and Rails, then Backbone/Knockout, then React. About 5 years ago I started playing with React Native, and from that built Tamagui. At the time, our startup needed to ship a cross-platform app, but we weren’t Facebook, so writing our apps twice was hard to justify.
Tamagui was born (much later) because it was clear it was possible to share most styling code - so long as you had an optimizing compiler. Tamagui still is the only library that truly “solves” sharing code this way, and this well. Beyond the compiler, a truly incredible amount of work was needed to fully support SSR while also supporting spring animations and React Native rendering, meanwhile also supporting the many nice web styling features that CSS had spoiled us with on web.
So Tamagui solved a big part of sharing code. It brought us from maybe 25% shared code to 50%. To support Tamagui itself, we built out a paid starter kit, Takeout. At the time it was our best effort, but the truth is, it still wasn’t easy, or pretty. Takeout v1 was launched about two years ago.
Outside of styling, when comparing to a single-platform app, many things sucked - mostly, having two frameworks, two bundlers, a monorepo, a ton of glue code, and ultimately having to patch in optimistic mutations for your data for anything to feel remotely native.
So, we made One. We first built vxrn as an experiment to see if we could get Vite to support React Native. When that worked, we continued to test building a framework. Luckily, around that time, Expo released Expo Router, which had great API surfaces and solved things nearly exactly as we were doing internally. So, we changed course and instead ported Expo Router to vxrn, and when that worked, iterated on it to add rendering modes, loaders, middleware, redirects, a production server, a whole bunch of ecosystem compatibility improvements, and such. That eventually became One.
So - we solved the mess, mostly. Except that data problem.
Enter Zero. One of the founders, Aaron, became a friend of mine while I worked at Vercel as he happened to live down the street from me. And Zero happened to be my dream solution to data - for both web and native.
We set out to rebuild Takeout with Tamagui, One and Zero, and see how close we could get to the “holy grail” - nearly fully shared code, excepting various platform-specific UI, without a shitty end-result.
A Tsunami is Coming
I’ve done a ton of LLM-driven development this last year, and it’s incredible how things have changed.
Here’s how I think that shakes out: it’s more possible than ever for anyone to build anything with software, dramatically so. It’s also way easier to maintain software. AI has been an absolute blessing in this regard, to such a huge degree it’s shocking. Tamagui is 200 packages, it runs on four platforms. The burden of maintaining it is immense, but oftentimes, and frankly mostly, that burden consists of just having to boot up many different pieces, build them, check them, debug them, add tests, etc. Or these burdensome chores like making cross-cutting changes across how we build or package things across the entire huge monorepo.
A lot of that work is now dramatically cheaper — and even more importantly, parallelizable for the first time.
The rate of shipping in Tamagui and One over the last 6 months has felt almost exponential. We went from a shoddy native test suite with low coverage to one that’s basically unrecognizable - robust, fleshed out, reliable. We went from losing our CI ✅ often and leaving it broken to reliably maintaining passing tests across every repo.
I’m fixing bugs at a rate that simply was untenable before because it was exhausting - booting up a Github issue, running the reproduction, inspecting the source code, finding where things may break, writing tests, fixing them, etc. This was arduous work, and yet with an LLM the only thing I do now is yell at it occasionally to do things better, make sure it finds the right areas, give it a few tips to clean up and test properly, and read over the code at the end. This is a 10x reduction in effort. For the version two release of Tamagui I wanted to go in and fix as many v1 bugs as possible first, and in a few days we went from nearly 200 open issues to about 50 - with good fixes, honestly better than if I’d gone slowly and manually. That’s in part because I often just make the LLM do many passes, debugging from various angles, inspecting things, etc.
The labor of maintenance isn’t gone, but the bottleneck has moved: less typing, more directing and reviewing.
And so is a lot of the labor of building features - so long as I know what I want and how it should be done, which is easy as I built the library originally and so know the internals well, adding features is a matter of orchestration, not labor.
I’m conducting, not mining.
It’s fun. And boy, have things accelerated.
One has gone from rough around the edges to polished - fixing basically all the major issues, adding a ton of really nice little features, optimizing all sorts of things (I spent a few days just deeply investigating Lighthouse dumps, JS bundles, Vite and more, and basically brought the average bundle size and performance scores of One apps up dramatically with 10% of the effort I normally would’ve spent doing that).
This has left me reeling a bit. I’ve been thinking a lot about what it means.
And recently it hit me, connecting to another idea I’ve been thinking about this year.
Being Right
When I was working at Vercel, right before I left, RSC was announced. It was interesting and exciting, and also a bit hard to grok. In fact it took me months to really get a feel for what it was doing, and why it had such interesting limitations and constraints. It also changed a lot in the early period which made it hard to come to a conclusion about it for quite a while.
I did eventually come to a conclusion though after I played with it a bit, and even tried implementing some pieces of it in our framework. And my conclusion was that it wasn’t the right set of trade-offs. It chose complexity and power, when it should have chosen a slightly less powerful, but more intuitive (and honestly better performing) model that works better for 80% of cases.
To be fair: I get why it exists. There are real scenarios where a server-first, deeply integrated model is worth paying for — extremely complex apps, huge teams, hard constraints, and cases where pushing more orchestration to the server is a net win.
But for most product teams, I think the complexity-to-benefit ratio is off. I also think it nudges your data and routing toward the server in a way that isn’t the future I want.
To me the future of websites and apps was clear - it had been since Meteor was announced - we needed optimistic mutations and simple, client-side querying, anywhere in the tree, with less complexity, not more.
Explaining why I think RSC is wrong would take a whole blog post, and I don’t have the energy for that. There are surface-level ways to see it’s not good, like adoption, or community division. There’s other ways too - like the fact that even after the promoters of it spent years writing extensive series of blog posts, beautifully hand-drawn diagrams, multitudes of analogies tried again and again, and people are still struggling to understand it, there’s something fundamentally wrong. But again, my point here isn’t why, it’s the meta:
I came out early against it, because I simply thought the complexity to benefit ratio was off. I was one of the few early detractors. Eventually a wave of critics came to share my view, but early on there weren’t many.
RSC had a ton of “hype” online for a long time, and I saw many creators of frameworks smartly ride that hype wave. They were “looking into” RSC, or working on it. When the community seemed to largely come out against it much later, many of them then turned around and capitalized on being against it now.
I didn’t really capitalize much on either - I said I was against it, and kept that position. In fact the ones who embraced it, only to later reject it, often without really a principled position in either case, got all the credit for being smart to see the right path, which was fascinating.
So how does this all tie into AI and code?
Well, it does and it doesn’t. It does because features are easier than ever to build. Frameworks, even. But we can see with Next.js and its loss of goodwill in the last year that even if you have all the resources in the world to build any feature you want, it doesn’t matter - features alone aren’t enough. You have to have something clear, and easy to understand, you need the right set of features.
Or does it?
I find that my being “right” about RSC early on and having a principled stand on it has benefited me almost zero so far. And in fact the people who sort of capitalized on ‘both sides’ - hyping it up, then hyping it down, only when broad sentiment seemed to change - actually benefited the most!
The frontend community is a bit unserious in this way, perhaps humans in general. We see what’s in front of us.