Hi friends đź‘‹ Welcome to issue 50 of Frontend at Scale. Fifty! Can you believe that? It seems like yesterday that I was writing issue number one in 2023, back when X was still called Twitter and the dev community was still allowed to talk about things other than AI.
To celebrate the occasion, today we're going back to our roots with a mostly AI-free edition. In fact, we're going to cover the thing every frontend developer would be excitedly talking about if it weren't for AI stealing its thunder—sync engines.
We also have a bunch of great articles and videos, a new code showdown, and the results of last week's inaugural matchup.
Let's dive in.
MAXI'S RAMBLINGS
The Syncing Era of the Web
The web turned 36 last week, and like most millennials approaching their forties and claiming to be living their best lives despite the constant knee pains, it’s going through some changes.
For as long as web apps have been around, the way we’ve dealt with sharing data between client and server has been via a fetching mechanism: we pull the data we need, when we need it, and we show it to the user. It’s a simple model, and it works reasonably well.
But these days, the status quo is being challenged by the rise of sync engines—systems that keep data in sync between client and server automatically, often in real time. The syncing model solves a fundamental challenge of most modern web apps out there, and it does it in a way that feels, well… better.
Compared to traditional data fetching, syncing gives you:
- Better UX: no more loading spinners as users navigate through the app. Optimistic updates are the norm in apps powered by sync engines, giving them that snappy, native feel.
- Real-time out-of-the-box: syncing means keeping your data in sync at all times, which is perfect for collaborative apps where multiple users need to work on the same data simultaneously.
- Simpler architecture: most of the complexity of dealing with data (syncing, caching, resolving conflicts, error handling, real-time updates) is handled by the sync engine itself rather than the app developer. The actual app code becomes simpler and much more focused on domain logic.
- Offline support: since sync engines also keep a local database, users can continue using the app even without an internet connection.
Sync engines are popular these days thanks to the amazing UX of apps like Linear and Figma, but they aren’t exactly new technology. This might have you wondering: If sync engines have been around for a while and they’re as superior as people claim them to be, why aren’t they the norm yet?
There are a few reasons for this. The main one is that until very recently, our only options for syncing were using a service like Firebase or building our own engine. For many teams, neither of these options is particularly attractive.
These days, we have quite a few open-source options that can be self-hosted and adapted to our app's needs, but we face another challenge: We have too much muscle memory when it comes to fetching. Most of us have been building apps this way for our entire careers, and while we might intellectually understand the advantages of syncing, changing our habits and mental models isn't easy.
Sure, the benefits of sync engines sound great, but unless we’re building a specific type of multiplayer application like Figma or Google Docs, or we really care about offline support, most of these benefits are typically considered “nice to haves.” It’d be great to get real-time for free, of course. But is it really worth the effort of changing 20 years of muscle memory with the fetching model?
Things are changing these days, though. We seem to be reaching a tipping point in sync engine adoption because it’s becoming easier to build apps with them than without them.
Take the recent beta release of TanStack DB, for example. Declaring a TanStack DB collection looks very similar to a TanStack Query query, which a lot of people are already familiar with. But operating on a collection is a lot simpler than, say, operating on a TanStack Query mutation. Just call an .insert
method, and that takes care of all the good stuff for you: optimistic updates, cache invalidation, cancelling previous queries, and so on.
Another benefit of TanStack DB is that it’s incrementally adoptable. It doesn’t give you an entire sync engine, but only the client-side part of it. This means that you can use it with your existing REST or GraphQL endpoints, and if you choose to, incrementally move those to a full sync engine like ElectricSQL to get the rest of the benefits, like real-time sync and conflict resolution.
​LiveStore is another good example. It’s a bit more unconventional under the hood, since it uses events to describe the app’s state, but from a developer experience perspective, using it is as simple as it gets.
If you’re looking for a more batteries-included alternative, there’s no shortage of options as well: Convex, Instant, Supabase, PowerSync, and probably a dozen others I’m not aware of, all give you a ready-to-use sync-wrapped database à la Firebase (for a price, of course) that you can start using with minimal setup.
I find the transition to syncing incredibly exciting, and I’m a big fan of all the fantastic work people are doing in this space. If the tooling continues to get better and the learning curve continues to flatten at this rate, it’s only a matter of time before syncing becomes the default. And who knows. Maybe, just maybe, this is the way for us to finally stop fighting about the best way to manage state in our React applications.
🥊 CODE SHOWDOWN
Reduce vs For Loop
Welcome to the Code Showdown 🥊—the section in which Frontend at Scale readers fight to the death over tiny differences in a piece of code.
This week we have another classic debate: 🅰️ the Array.reduce function vs. 🅱️ a good ol' for loop. Classy and functional or timeless and structured? There can only be one winner. Choose your champion by voting in the poll below—I'll share the results in the next issue.
Voting in polls is only available in the email version of the newsletter. To participate in the next Code Showdown, subscribe to Frontend at Scale below!
WEEKLY SNACKS
Links Worth Checking Out
- Speaking of sync engines, one of the most powerful drivers behind the latest innovations in this space is the Local-First community. At last year's Local-First Conf, Martin Kleppmann (of Designing Data-Intensive Applications fame, and one of the founders of the Local First movement), gave a fantastic talk about the past, present, and future of local-first that I highly recommend watching if you're looking for an overview of what these incredibly smart folks are working on.
- If you read my note on TanStack DB and ElectricSQL above and thought "that's great, Maxi, but how does that actually look in code?", Andre Landgraf has a great example (and video demo) on the Neon blog.
- Una wrote about the new scroll-target-group CSS property and how you can use it to build a scroll-spy with two lines of CSS instead of the usual 12 megabytes of JavaScript.
- Joy Arulraj built a periodic table of system design elements. Perfect for your upcoming system design interview. Not so much for your next Chemistry exam.
- Chris Ferdinandi shared some tips on building extensible frontend systems using CSS variables, cascade layers, web component attributes, and custom events.
- Jono Alderson wrote about how the web isn't URL-shaped anymore, now that machines, not humans, are the main audience of written content on the web. Caveat: Jono is talking specifically about SEO-optimized content, not content like this newsletter, which will always be optimized for humans ❤️
- Short and practical advice from Grant Slatton on how to write a good design document.
- Ben Holmen spent six years building the world's most impractical pixel display out of wood, and it's my favorite thing I've seen this week.
🛠️ NPM Install This
- ​Hashbrown — An open-source framework for building joyful ✨ AI-powered user interfaces.
- ​ForesightJS — A pretty cool library that predicts user intent from mouse and keyboard cues to deliver instant navigation with zero waste.
- ​Apache ECharts — The classic JavaScript visualization library recently released v6.0 with a nice new look and new chart types.
​
🥊 CODE SHOWDOWN — LAST ISSUE'S RESULTS
Optional Chaining vs Guard Clause
The results are in: 2 our 3 readers prefer using optional chaining vs a guard clause—at least for the use case we covered in the last issue.
To be fair, my guard clause example wasn't the best one. As someone pointed out on LinkedIn, there was a bit too much algebra in the second example, so it wasn't necessarily more readable than the optional chaining one. That's definitely on me, I'll make sure we keep it a fair fight going forward.
That’s all for today, friends! Thank you for making it all the way to the end. If you enjoyed the newsletter, it would mean the world to me if you’d share it with your friends and coworkers. (And if you didn't enjoy it, why not share it with an enemy?)
Did someone forward this to you? First of all, tell them how awesome they are, and then consider subscribing to the newsletter to get the next issue right in your inbox.
I read and reply to all of your comments. Feel free to reach out on Twitter, LinkedIn, or reply to this email directly with any feedback or questions.
Have a great week đź‘‹
– Maxi