Things that worked out well for us while updating the TicketSwap website.
I joined TicketSwap in early 2018 as the fourth developer on their front-end team. Many things happened since then, but we’re close to a milestone that’s especially dear to me and I thought it might be nice to share it here.
If you live outside the Netherlands there’s a fair chance you haven’t heard of TicketSwap yet. We’re steadily growing in several European countries and are exploring a few far away places like Brazil and Australia, but the Netherlands is by far our biggest, most important market.
After moving to Amsterdam in 2016, new friends I made were going to a party and asked if I wanted to come along. I’ve been trying to find tickets online, but unfortunately it had been sold out. That’s when someone forwarded me a link to this website called TicketSwap. I opened the site, looked up my party and two minutes later I had the tickets in my inbox! I used the service plenty more times after that for both buying and selling all kinds of tickets. Show sold out? TicketSwap. Other plans? TicketSwap! So good.
The legacy stack
When I interviewed for the open position, Frank – one of the founders – was telling me that they’re looking for help extracting the front-end from a monolithic Symfony application. The legacy front-end at the time – mostly Twig templates, jQuery plus React, Sass stylesheets, and a Gulp build process – was experiencing performance issues, unlike the back-end it was largely untested and working on it was a slow and painful process. It was calling for a rewrite. The idea was to turn the web front-end into a client just like the iOS and Android app, consuming our GraphQL API.
A project that had to be planned the right way: There’s more than 80 people working across TicketSwap’s different departments. All with different needs and requirements for the platform that would need to be satisfied on a continuous basis. In order to deliver value for the user beyond just performance gains, our small front-end team would have to replace the foundation, while improving existing features and adding new ones on top.
So we did. Around half a year after starting my new job, we merged the first pull request into the new app. What follows is a list of things that worked well for us in the months to come:
Not reinventing the wheel
Given the brief and our limited resources, when approaching the project my partner in crime Lucas Feliciano and I based our tech-choices on mainly two things:
Basically, our new stack should let us move fast and iterate quickly.
We’ve investigated a few options but eventually decided to build our new app on top of Next.js, a framework for server-side rendered React applications. Parts of the legacy front-end were already built with React, so it made sense to opt for a React framework. The developer experience with Next.js is fantastic and its convention over configuration approach was well aligned with the plans we had in mind. On top of that, it has been around for a while and it gained popularity with big names backing it.
Community-size has been another factor in our choice. Because of the fact that Next.js is quite popular, resources are widely available online, which makes for a low learning curve for new hires who have less or no experience with the framework.
Most importantly though, by choosing Next.js our team wouldn’t have to spend time configuring code-splitting or maintaining a server-side rendering (SSR) strategy, but can instead focus on solving other problems.
Migrating on a per-route basis
How do you roll out updates when rebuilding a large-scale web app from scratch? We didn’t have the answer to that question in the beginning, but we knew it was going to take us a while until we’re completely done, so we decided to gradually migrate on a per-page basis. In our load balancer, we’d define which pages would be served by the legacy stack, and which ones by our new front-end.
This worked well until we encountered too many routes with child paths, making our route configuration overly complex and almost unmanageable.
Next, we tried to migrate on a top-level route basis. Meaning that:
all needed to be rebuilt and ready to migrate before we’d show an all-new
/event/* route to our users. Sounded daunting in the beginning, but worked surprisingly well for us and it’s how we moved forward.
Solar and Comets
This hybrid approach of basically having two front-ends at the same time was a bit weird, but sometimes not even colleagues would know which one they were looking at. While rebuilding our front-end, we initiated two open-source projects that played a role in that.
Our designers had already been working on what you could call a design system at the time, albeit only in the context of how they organized their Sketch files and assets. I’ve helped them get it into a more refined shape and translated its individual parts into a set of React components that we’d later release on npm as Solar. UI icons had undergone a similar process and have been open-sourced under the Comets moniker.
I like to think of Solar and Comets as two boxes of LEGO® bricks which our developers can pick and choose from when building UI’s. It’s fast and it helps maintain a high level of consistency.
Because certain parts of the UI (such as the menubar, the site footer, etc.) had to be shared between the old and the new front-end, maintaining consistency across both would sometimes be a challenge. Installing our component libraries in both projects helped in those situations.
Rebuilding our front-end meant moving a lot of things from A to B. We didn’t want to bring along the entire Sass build configuration from the legacy stack, and since we had installed emotion in both projects already, we’d quickly refactor legacy UI by turning Sass styles into
styled components and dragging them into our new app. Done.
“CSS-in-JS” in general has been the source of many discussions on tech-Twitter, but man has it boosted our productivity.
Personally, I think the biggest argument for tools like styled-components or emotion is how fast they let you build and iterate. Collocated styles and no external stylesheets. No separate build-process and configuration. No pre-processors. No Autoprefixer. No cascade. No BEM methodology and no time wasted coming up with class names.
Making front-end testing more approachable
Like I mentioned earlier, the legacy front-end had been largely untested. A few components here and there had tests included, but they were the exception. The general opinion about front-end testing at the time, was that the effort it took to write tests was out of proportion compared to their usefulness.
All the infrastructure was in place and we had automated testing set up with our CI (continuous integration) service, but if I wanted our new front-end to be more thoroughly tested than its predecessor, I needed to make testing more approachable and demonstrate its usefulness to the people on my team.
Hoping to make testing become more deeply integrated into our culture and the way we work, I’ve tried three things:
1. Create a testing environment that’s nice to work with
Want people to adopt something into their workflow, make it a fun experience. In my opinion there’s one testing framework that’s fun to use and it’s the @testing-library family. I went ahead and removed Enzyme with its adapters, rewrote all existing tests for @testing-library and held a workshop on its superior API. Yay! Writing tests just got less annoying. I think the biggest plus of @testing-library is how it encourages testing-practices that improve the accessibility of your app.
getByAltText don’t work? Hmm, maybe that image needs an
2. Demonstrate by refactoring
Okay, testing is fun now, but writing tests still takes time. How do you make people see that tests can be useful? My personal answer to that is refactoring a component while people are looking at your screen. Refactoring a somewhat complex component and have tests pass during the process is not only the single most satisfying thing to do, but also has other people be like: “Hmm, okay I think I see what you mean.”
3. Advocate for meaningful tests
If you’re following Kent C. Dodds on Twitter or are reading his blog posts, then you’ve probably heard that you should write tests that resemble the way users would use your app. That’s the one thing I’d pass along to everyone who does not follow Kent C. Dodds on Twitter or read his blog posts. Not testing for 100% coverage arguably takes less time. And advocating for writing more meaningful tests has helped make front-end testing more deeply entrenched in our day-to-day work.
Looking at our codebase today and seeing people include tests in their pull requests, I’m happy with how far we’ve come.
Our small team has spent most of 2019 migrating to the new stack, while at the same time improving what’s already been migrated and continuously adding new features. At the time of this writing it’s February 2020 and we’re almost done. The tech-choices we’ve made have paid off. Next.js is very nice to work with. CSS-in-JS has everyone move fast. Components and pages are well-tested and colleagues enjoy using Solar for building new UI’s.
I’m proud of what this team has achieved and now that we’ve got a solid foundation, I’m excited for all the things we’ll build on top of it!