We started Cord in 2020 in the middle of a pandemic. Our mission is building absolutely killer real-time and asynchronous collaboration features that work with any existing SaaS tools.
This idea sounds simple enough, but when you dig into what’s required to deliver this, you start to see a lot of unique constraints starting to emerge, which lead us to some very specific technical choices. This post breaks down our choices for the client side of our tech stack.
TL;DR: If you just want to know what tech stack we’re running here’s a quick run down. For more detail, see below. We’ve chosen TypeScript for both server and client; modern React with Hooks for the client UIs; GraphQL for the client-server interaction; Apollo on the client and server using WebSockets; React-JSS for managing styles; Webpack + Babel for transpilation.
We’re building tools that enhance tools. That in itself creates some super interesting constraints for how we have structure our tech stack. For instance, we can’t expect to have control of the page our code runs in. Our implementation has to play very nicely with our neighbours.
We have a heavy focus on both real-time and asynchronous features. On one extreme end, we want to be able to reflect up-to-the-second information like whether or not your teammate is typing a message. On the other end, we want to be able to reflect an entire conversation history in a tool quickly. This means we have to support short-term storage features, a lot like how WhatsApp works, and we have to support long-term storage features like complete-history retrieval, like Slack.
TypeScript is a mixed blessing everywhere you take it. It means that writing the code in the first place will be slower and more annoying. Learning all the finicky interactions between React and TypeScript is definitely a learning curve. Want to create an input event listener outside of your JSX markup? Cool, just learn the magical incantation e: React.ChangeEvent<HTMLInputElement>. Dead simple?! Right!!? Well, yes, once you’ve scratched your head and read docs for a while. Still, once you’ve paid the bill to create the TypeScript code correctly in the first place, it becomes indispensable for maintaining your codebase. I’ve now bounced between TypeScript and non-TypeScript codebases a few times and I miss TypeScript annotation every time.
Wow, no mystery about what properties this component supports!
The downside of the React + TypeScript combo is that you’ve pretty much guaranteed your compilation is going to feel sluggish. If you work locally on a reasonably modern laptop, it’s tolerable. Still, even after some optimisations, we’re seeing warm-recompile times in the low single-digits seconds. That’s just slow enough to feel sluggish. No whizbang tech is free. Come on Deno!
Choosing TypeScript has huge implications for the rest of the stack, so I’ve listed it first. If you start off with TypeScript, you have to think about the TypeScript implications of every next technical choice. DefinitelyTyped will save you some of the time, but some projects are not TypeScript friendly and you’ll be incentivised not to choose them.
It may seem strange to include VS Code in the Tech Stack discussion, but the raw truth is that if you’re using TypeScript, you’re using VS Code. I grew up in the generation of developers who love to hate Microsoft. But I have to admit VS Code is the best IDE I’ve ever used. Ugh. I hate saying that. The ultra rich support for TypeScript is a killer feature. I still work in Vim/iTerm most of the time, but when I’m hacking React, I’m in VS Code (with Vim bindings!).
React was the easiest of the choices for this project. Especially since Hooks have come out, React is just the clear, clear winner over everything else out there right now. In my last role, I ported an existing PolymerJS app into modern React and generally had an excellent experience with React. The tooling support is excellent. The React core team are beasts with incredible velocity. Couldn’t be happier with this choice.
Runners up are thing like AngularJS or VueJS, but honestly, it’s not a remotely close race.
The GraphQL query UI is fantastic. Super useful for debugging and for composing exactly the right query. No Swagger or Postman UIs for our frontend engineers.
Pro Tip™: Always alphabetise CSS declarations.
An oldie but a goodie. I’ve encountered the Metro bundler more recently in interacting with some ReactNative/Expo code and I’m curious how it compares performance-wise with Webpack. Still, I wanted something I knew and something that is reliable. The Webpack plugin ecosystem is extremely hit-or-miss, but the core tech is solid.
Still, when it slows down (which it will with all these bells and whistles and spinning rims), there is a huge body of well-documented Webpack optimisations to employ to reclaim that performance. I’m happy with Webpack for now, but I’d happily jump on something faster and simpler.
Another subtle tradeoff is that we’ve started from scratch. The upside of this choice is that we’re running the best-of-breed everything. Our codebase doesn’t suffer from any legacy ailments or poor technical choices (at least not that we know about yet!). No tech debt to pay off. Clean, linear commit history thanks to the Stacked Diff Workflow. The downside is that we’ve had to fit all the pieces together ourselves. We didn’t start with any boilerplate project helper or create-react-app. So that cost us some time. Still, this tradeoff seems easily worth it.
Like what you’re reading? Come join us! We’re currently hiring for a small number of great people to join our merry band. We’re going to change the way people work. Help us get there. Email me — firstname.lastname@example.org.