Remixing the CarGurus Front-end Experience

47:07

Summary

Alex Shopov discusses how CarGurus decided which framework would best support the needs of their front-end development teams and evolving application ecosystem.

Bio

Alex Shopov is a full-stack Principal Software Engineer on the Developer Experience team at CarGurus. He is a co-recipient of the 2006 Innovative Technical Achievement Sports Emmy for his work developing on-air graphics for CBS Sports, and moonlights as a teaching assistant for several foundational Computer Science classes at the Harvard Extension School.

About the conference

InfoQ Dev Summit Boston software development conference focuses on the critical software challenges senior dev teams face today. Gain valuable real-world technical insights from 20+ senior software developers, connect with speakers and peers, and enjoy social events.

INFOQ EVENTS

- Live Webinar and Q&A

Jan 22nd, 2025, 1 PM EST

- Designing Low-Latency Systems with Rust: An Architectural Deep Dive

Presented by: Felipe Cardeneti Mendes - Technical Director at ScyllaDB

SPONSORED BY SCYLLADB

Transcript

Shopov: My name is Alex Shopov. I'm a principal software development engineer here at CarGurus, a local company. I've been with the company for about four years as a principal software development engineer on our developer experience team. In that time, I've worked on a number of projects, including architecture and development, various services for the maintenance and management of static assets. Deconstructing our monolithic code base into more of a microservice and smaller service-based architecture system.

The development of a branded component-based application shell for use by many of our applications, front and backend. We'll be talking a bit about that. Before CarGurus, I worked in a series of other startups over the last number of years. Before that, I think I was working in image processing for a while, high speed video analysis, a little bit of everything. I've really dipped my toes in a lot of different parts of development. I'm also definitely a nerd. I'm a hardcore gamer in game dev. I like to do a lot of my own personal projects.

This is 100% not a solo project that I worked on. I was not the only person involved in the work I'm going to talk about. It's very important for me to make some acknowledgements. The first acknowledgement was to a small team of engineers, little skunkworks that we put together at CarGurus last year that we called the Frontend Freedom Fighters Initiative. The Frontend Freedom Fighters Initiative was a cross-team collaboration between the engineering platform department as well as our product and wholesale platform departments. The idea was that we were given a mandate to spearhead a new paradigm for doing frontend development at the company. CarGurus, brief history, has been around since about 2007. It began as basically a blog for the automotive industry, a lot of statically rendered pages, not a lot of interactivity.

Over the years, that has grown and developed first into Java Spring based apps that, again, were statically rendered with some functionality, and then later on, more React based single page apps, a mishmash of technologies, going back over about 18 years at this point, which is not unusual for a company our size. It's been around as long as we have. What we wanted to do was really modernize this system. The five of us, myself, as well as my colleagues from the platform engineering team, Bill Blanchette, and Samar Guleria, and our digital retail colleagues, Ryan Canulla and Nischit Ranganath from our frontend platform team, got together with the idea that we could do something better. We wanted to create a new experience for frontend development at CarGurus, and we consulted with a lot of other devs and engineers across the organization.

A Brief History of Frontend Development

How do we get to this point? I want to start out with a brief history of frontend development. I was there in what I like to think of as the prehistory of web dev and the first browser war. It was in 1995 to 2001, what I've heard referred to recently as the late 1900s. For anyone who remembers those days, JavaScript was a fun little toy language. We were using it to do rollovers for buttons and simple little animated images. Because JavaScript, when it was introduced in '95, as a collaboration between Netscape and Sun Microsystems, wasn't really a fully-fledged platform for really interactive web apps. The technology wasn't there. Also, despite ECMAScript standardization, it wasn't a standard distribution of JavaScript across the board.

Microsoft introduced their own version called JScript that only ran in Internet Explorer. CSS came out about a year later in '96, and Internet Explorer, oddly enough, was the first one to really have large scale support for that. IE was really dominating the web scene at that time, so much so that the U.S. government ended up filing an antitrust suit against Microsoft when they integrated Internet Explorer directly into the Windows operating system. It was a very seamless experience between the Windows Explorer and IE. It was definitely interesting to see how all of this was playing out. We had those fun little banners on many sites that said, best viewed with Netscape Navigator or Internet Explorer, because there was such a different way of developing pages.

We survived those days, and we got into the dotcom bubble, and it seemed like the sky was the limit, and everything burst. We entered what I like to think of as the Dark Ages. Again, we had browsers all doing their own thing. It used to be that anyone who had dotcom in their name would have money thrown at them, but then that evaporated. Just as the Dark Ages in Europe gave rise to something better through the preservation of knowledge, we started to see some things improve during what I think of as the web dev Dark Ages. jQuery released in 2006 gave us an opportunity to finally have some attempt to do cross-platform code. We could write it once and run it in multiple browsers, versus having to have polyfills and solutions for different browsers. Netscape, unfortunately, we lost in 2008. They were really one of the prime movers for web tech, but they were replaced later on that year by Chrome, which is still a ubiquitous solution that I'm sure many of us use.

Just as historically the Dark Ages gave way to the Renaissance, we saw the same thing happen in our industry sometime around 2010 to 2016, which also coincided with the second browser war, when the competition between Chrome and Firefox led to massive improvements in JavaScript development and use and performance to the point that single page apps now became feasible. Everything didn't have to be just a static page that was loaded one after another. We could have single page apps that gave functionality that was not unlike what people would expect to see in a desktop app. This gave rise to the Software as a Service movement, and things really took off from there.

Frameworks for single page apps became very widespread. Backbone and Angular were first released in 2010, Ember later on in 2011, and then React in 2013. Has anyone used anything besides React? I know that's the ubiquitous winner right now, but do we have any Backbone, Ember? Everyone remembers what it was like in those days where you had to really figure this out as you went along. The frameworks did some things to help us out, but they weren't the be all, end all solution.

That brings us to the modern day, where we're in a whole new paradigm of client-side versus server-side versus statically rendered sites. It's all the same technology that we've been using, but updated and arguably cleaner and more developer friendly ways to get to the same location. I like to think of this time as a circle, because right now, we thought we left static sites behind 20 years ago, but there's still a good reason to have statically rendered sites. We'll take a look at that a little bit later in the presentation. Client-side versus server-side rendered code, why do you want to use one versus the other? These are important questions that we need to think about when we're starting any new project, just as we've thought about so many times over the preceding years.

Client-Side vs. Server-Side

Let's talk about client-side versus server-side a little bit. Client-side rendering is where an empty page is delivered from the server and additional calls are made to load code and style bundles. The page is blank until the content is rendered by the browser. If you're using React, client-side, this is exactly what you're seeing. You're going to send a blank page up to your user so that something can load right away. Chances are you might have a little loading spinner that will take up some time while everything else is being processed, although these days, that is pretty quick, but everything is offloaded to the user's machine. This is great for complex single page apps. The processing can be offloaded, that saves us money, that saves us server time. The user's machine carries the processing load. Versus server-side rendering, where the content is generated on the server and the complete HTML page is delivered to the client.

Additional calls could be made to retrieve more code bundles, but the page is visible while the code hydrates. When we say hydrates, what that means for those who have not worked with server-side rendering, is that the base page is delivered, but then a second call is made to load any JavaScript that's necessary to create interactivity in the page. That's what we call hydration, that process. In this case, our server, or your server, carries the processing load, and so this can increase server costs as your user base grows, but it also is a much faster experience for delivering an experience to the user. SSR pages can be very easily crawled and indexed, which improves your SEO and visibility.

Depending on the page and what you're working on, this can actually be a very important consideration. Server-side rendering is a powerful tool for certain use cases, but it doesn't fully replace client-side rendering. Server-side rendered pages don't have a lot of the more fluid routing capabilities that client-side rendered pages do. There are still things that you can do in the client that you want to offload processing to a client's GPU or take advantage of their machine, things that server-side rendered pages just can't do. Neither solution works in every case. CarGurus does both. Our legacy server-side rendered pages very often came out of FreeMarker templates served up by Spring Boot backends on the Java side. Our client-side pages were React. We have a very nice in-house system for content delivery and for components.

Forging New Frontend Frontiers

We've done both, but we always wanted to ask ourselves, is there a better way to do this? This is where we set out with the intent of forging a new frontend frontier. This took a lot of work. We put together the little skunkworks team that I mentioned earlier, but we really had to push our management and C levels to see the value of this project and to let us take a chance on creating a new frontend experience for the company. We actually joked, the five of us, when we set off, that the first thing we needed to do was update our resumes, just in case this didn't work. Fortunately, it did work, otherwise I would not be here speaking to you. Our goal was to allow our product teams to build an independently deployable, flexible, high-performance, and SEO friendly web application. That's not a huge ask. What were the requirements that we needed? We needed to be able to server-side render React components.

Most of our sites, our modern sites are already built in React. Our devs know how to use this. We didn't want to have to rewrite everything from scratch. We wanted to reuse what we could on the React side. We also needed to optimize data fetching, because we are making a lot of REST calls to get search information, product information, user testimonials and information. There's a lot of data being passed back and forth, and we want this to be as optimized as possible. Dynamic routing is important, because we do want to simulate that single page app experience, even if we are doing something that's fully server-side. I said a moment ago that that wasn't the easiest thing in the world to do with SSR, but it's not impossible. Likewise, our developer experience and productivity is absolutely paramount. We needed to have an easy setup and workflow.

One of the most common complaints from our devs was that working with older technology, working with some of the Spring apps, and the FreeMarker templates, and struts routing, and all of these things that were absolutely top of the line when they were introduced around 2009, 2010, 2011, maybe aren't the best choice anymore, but it still underpins so much of what we do. Our devs were suffering from this because they wanted to work with the latest and greatest, the things that they were trained on, that includes things like hot reloading, automatic code splitting, and long-term maintenance and support. We didn't want our devs to be in a situation where they're using the tool that somehow, all of a sudden becomes deprecated and they don't know what to do with it anymore. We want this entire process of developing a site from start to finish to be as smooth and seamless as possible.

Very much, in this case, the devs are our stakeholders. They're the ones that we are beholden to, because we're building this for them. We want the sites to be performant and optimized. We want to be able to prefetch our pages. We want to be able to have lazy loading, image optimization, asset bundling, all of the things that we take for granted in many client-side rendered apps now, things that are ubiquitous but that we couldn't just leave as an afterthought, because very often in a code base as old and complex as ours, things just get mishmashed on there. I'm reminded a little bit of the old essay, The Cathedral and the Bazaar, about the rise of open-source software versus large monolithic pieces of software. The Bazaar is this concept that you have all these people coming in and putting their own spin on different projects.

A code base this old definitely starts to resemble that, where a new feature is added because we need something to work, but maybe it doesn't always play nicely with something that was in there previously. Now you need to create a new middleware layer to communicate between them, and now you're introducing additional tech debt and problems that only just grow more out of control the longer you let this go. We needed to make sure that any tool that we had would have the long-term support and the support for all of these different features that we had come to take for granted, and we didn't want to have to do ourselves. Most importantly, we needed to have SEO capabilities. Given that a big portion of our site is a search engine, and we're returning information about different vehicles, we wanted to make sure that dynamic meta tags were easily supported, and that our site could be properly categorized by search engines no matter who's looking at it.

We had a choice to make, do we roll our own custom solution, or do we leverage an existing SSR framework? To solve this problem, we decided that we were going to have a bake off. We considered one custom solution and two established SSR frameworks. We started with the ubiquitous Node, Express. This is a solution that we rolled from scratch. Has anyone rolled their own web app with Node and Express, doing it the old-fashioned way? This was actually an idea that I championed initially, because, being an old school hacker, I love the idea of things that are not automagic. I like knowing why things work the way they do. I like being able to write things and knowing every step of the way that it's doing what I want it to do, and just bringing in little pieces of middleware or helper modules when necessary.

The pros of Node and Express is that it allowed us to do that. It gave us the highest possible degree of developer flexibility. Whatever we knew we needed, we could just write it. I was able to quickly spin up a PoC that was architecturally analogous to our existing Spring Boot based Java applications. I did this over the course of about a day or two. We have this concept at CarGurus called Pit Stop Days, where once a month we'd take a day to just work our own projects. This was a Pit Stop project for me, where I started, I think, on a Monday, and I finished it on Tuesday, and I had this quick little Node, Express demo that worked very nicely for what it needed to show off.

The cons is that many of the cutting-edge optimizations and advanced capabilities that we knew that we would need would have to be built and maintained in-house. As much as I said a moment ago, that I do enjoy building these things from scratch, and I think there's value in doing so, at the same time in a production environment, depending on what that middleware or module is, there's definitely value in using someone else's solution. Particularly if it's an open-source solution that has had a lot of eyes looking at it, making sure there's no malware getting in there. You don't always want to build everything from scratch, as much fun as that might be at times. This was definitely a con to the Node, Express solution.

The next option that we looked at was Next.js. Next.js, pros, this is a very mature framework with an established developer base, which actually includes one of our CarGurus' subsidiary companies. They had been using Next.js to great success, and so we brought them in to consult on that one and give us some insight into how they were using it. Next.js includes advanced functionality, including code splitting, static site generation, and image optimization, all the things that we said earlier we needed. It also has a large plug-in ecosystem so we don't have to reinvent the wheel on a lot of this stuff. We could just pick and choose from solutions that other Next.js engineers and dev houses had already come up with.

The cons are that Next.js is highly opinionated, you have to do things the Next way. It also really obfuscates the lower-level functionality, making it really difficult to integrate some CarGurus specific functionality. There were times where we needed to be able to access the page on the lowest level, basically just straight up DOM manipulation, and Next.js really frowns upon that. At least at the time of this writing, they didn't make that core HTML page that everything is rendered into, easily accessible. Very often, it didn't even exist. It was just dynamically generated on the fly. We needed to have something that gave us more low level control. This was definitely not a pro in Next's favor.

The last one that we looked at was Remix, which, again, on the pros, it was a modern, full stack framework that was supported by Shopify. We know Shopify has a good service that they provide. They have some fantastic open-source tools. It leverages React Router, which was a technology that we were already using in our single page apps. CarGurus React devs already had some experience in this one. Remix also has mature data handling for efficient page loading. We said again that this was a hard requirement for us. We needed pages to be able to load as quickly and efficiently as possible.

On the other hand, Remix was a newer framework missing some of the quality-of-life features that we took for granted or that we knew that we would like or need. Most importantly, it had no native support for CSS preprocessors. A lot of our styling libraries were built in using Less or Sass, and so we had to go through and make some changes to make these compatible with Next as we were playing around. It also didn't support hot module reloading, which was something that our devs really took for granted, especially when they were working on larger code bases where they were just changing one little thing, and they wanted to be able to see the results of that change very quickly.

Across the board, three options, three tools that we built, three sites that we put together, one using each of these potential solutions. We talked to some of our devs who had some experience. I wanted to quote Jin Xia, who's one of our senior software development engineers who worked with both Remix and Next. When I asked him about this, I thought this was really worth sharing. He said, "Both Remix and Next are great SSR meta-frameworks. Remix leverages web standards and innovative use of Forms and actions to mutate data, enabling React apps without relying on React state, which is transformative for apps requiring frequent data changes. Meanwhile, Next offers straightforward SSR and enhances SSR speed for React Server Components integration, making it an excellent choice for building static web pages with its strong ecosystem and mature tooling". This was definitely very much in favor of Next.

In the end, we did make a decision, and the astute among you will probably have figured out what that decision was based on the title of this talk. We chose Remix in the end. Why did we choose Remix? We chose Remix because it offered us route-level code splitting. It offered us React 18 support, including web streaming. This was key, because we had sites that were using old and outdated versions of React that desperately needed to be updated. In the monolithic environment in which we were working, it wasn't easy or possible to update versions of React without breaking a lot of other things in the process.

Being able to bust these out and create standalone repos for Remix apps that our dev teams could use to have the most modern version of React was absolutely key. The native React Router integration that I mentioned, again our devs were already familiar with this technology, so they didn't have to learn anything too much from the ground up to start working with this. The custom server functionality through adapters that convert data to and from Remix format. This is our middleware. This is where we were able to build all of the middleware components that we needed to work with our existing Java ecosystem. We have a whole series of microservices that are Java based.

Many of them have well established REST APIs that we can work with. The middleware layer was key to be able to continue leveraging those existing tools. We also like Remix because it only loaded change data. There was a lot less flow back and forth, and this allowed for faster performance, faster optimization, and an overall better page experience. There's always going to be tradeoffs. Like I said, Jin on the prior slide, made a very good case for Next. One of our subsidiary companies used Next. We believe that Remix was the best option for the next generation of server-side rendering at CarGurus.

The Remix-Renaissance Ecosystem

What did we do with it, once we had come up with this idea, and we pitched this, and they said, "Remix is the solution. We're going to go with it". We knew that we needed to have some core templates that all of our developers could work off of, and that template needed to start with UI and branding. It needed to be consistent with our existing CarGurus content network and component libraries. We have a number of branded interface elements. Any Remix site had to look absolutely indistinguishable from one of our Spring Boot and FreeMarker-based sites, or one of our client-side rendered React based sites. We also needed to integrate our internationalization libraries.

We have a number of modules working in the backend that handle translations and internationalization, because CarGurus is active across multiple countries and multiple languages. We needed to be able to have a seamless integration with all of that. Likewise, we needed access to the native core CarGurus application functionality. That's our search APIs, our recommendation engine, even things like our user support. We needed to be able to make sure that all of this would play nicely with Remix as close to out of the box as possible. Authentication, remembering and recognizing users and their sessions, and information as required.

Demo

I wanted to show you just a quick demo first, just to compare and contrast what a couple of sites would look like. This is just a quick demo site, and this is a Spring Boot based site in the background that's running a React client-side rendered app that was loaded in, again, CSR. It has this nice application shell with a nav bar. It's got a user menu, footer, all of this. This is what you would see if you go to CarGurus right now. This is our Remix based version of it, absolutely identical in every significant way, because it uses all the same services behind the scenes. We were able to tap into our existing app shell service. We were able to tap into our internationalization service. None of the copy here is hardcoded.

This all comes from our string and translation service. We were able to create something in Remix that was visually absolutely identical to our Spring Boot and React CSR based applications. This was a major step for us. This was a template that we were able to give to all of our frontend product development teams to start building their own apps. They could now start worrying about what was going on inside the app, the inner content, without having to worry about all the framework and scaffolding that they took for granted when working in our legacy ecosystem.

Just a little code. This is what the core of a Remix page actually looks like. It has a body tag. This is basically JSX. It's not too different from if you're writing JSX in React or any other high-level language. You see, we have some places where we can set a page header. We have the main body, which is where we have this internationalization Remix adapter, which is something we wrote in-house that serves as a middleware layer for handling all of our internationalization and translation. Our outlet is where the actual core application is being rendered. Then we have our CarGurus specific page footer, and then our scripts and application shell related material. This is it. This is basically the core of a Remix app. There isn't much going on, and everything is modular. We've been able to break out all of these different pieces into things that teams can build and maintain on their own.

One key issue that we had to worry about was the use of external scripts. I don't know if anyone has worked with SSR rendering extensively, but I mentioned earlier in the talk there was this idea of code hydration, where you had to load in JavaScript code after the static page was loaded. If your static page changes in any way when that JavaScript is loaded, you'd get what's called a hydration error. Your console will blow up and your page will not render properly. We had some scripts that would actually modify the DOM as they were loaded. We would do this server-side prerender of our page, and while it was hydrating on the client-side, things were changing in the DOM. DOM nodes could be moved around. Metadata could be added.

Then, after the hydration was complete, the two pages wouldn't mesh anymore, so we were getting these hydration errors that we needed to solve. The way we handled this was to create this addExternalScript method. This is me going into the weeds a little bit about some of the code we wrote, but I think it's worth sharing this portion of it. Someone hit on this idea that we could look at the script and determine, is this a script that modifies the DOM, or is it not? If it's a static script, then we can just preload it on the page, and we can have it go through the standard hydration process as any other script would, or it can be tagged as a post hydration script, in which case that script will be flagged, and after the hydration process finishes, we can use a React useEffect method, and call that script post hydration.

Any of the DOM changes that we need to happen could then happen without causing any of the hydration errors or breakage that we saw. This was able to get us over quite a few of the hurdles to working with some of our existing server-side services and some parts of the ecosystem, and made everything start to play very nicely together. This is when we started to really get Remix and push it to limits that we didn't know were possible before.

Results

What was the results of this experiment? It worked out better than we ever could have dreamed. The lead time for changes, prior to this, it would take up to a day from merge to release for one of our client-side rendered React apps. After we built our Remix solution, we moved it up to about 30 minutes from merge to release. Our Remix apps are updated so quickly because they are now free of that monolithic ecosystem that we had before, that we can get new versions out very quickly, sometimes multiple versions in a single day. Search results, we had a legacy client-side rendered widget which would return search results, and this is what it looked like before. If you did a search, it's our sell car consumer to dealer, and you would see that there was basically just one result, this, cargurus.com/sell-car/c2d/testimonials.

It was a single result that would come back because this page was client-side rendered after the blank page was delivered, the one that's actually being trolled by the search engine here. Then all of the testimonial data would be loaded post-load. Google would only get the client page metadata. This wasn't great if we had a site that had potentially tens or hundreds of testimonials that we wanted to share. This was one of the first applications that actually went out to production, a new version of our testimonials that was built entirely in Remix. The Remix rendered page you see now has 303 results, because all of these testimonials were rendered server-side and could be delivered and could be scraped by the web crawler. Right away, this boosted our SEO for this particular page through the roof. Company-wide impact of this can't be overstated.

Our goal was to release two Remix based apps to production by the end of 2023, and we achieved that in September, so four months earlier than we actually wanted to. How often does something happen ahead of schedule in our industry? It's almost unprecedented. This was a major cause for celebration for us. We were hoping to have three or four more apps in development in early 2024, we had 12. Remix just took off. I don't even know how many we actually have right now, but it's probably on the order of something more like 20 to 24 Remix based apps that are in development. I'm always seeing a reference on Slack to another Remix app I didn't know existed.

While I'm sitting here talking about Remix and everything we did with it, I'm far from a Remix expert. We have devs on our frontend dev team who have just gone so deep into the weeds on Remix and how it works that their happiness and productivity has just gone off the charts. Again, to quote a couple of our devs here, Bill Blanchette, my colleague, who was one of the original skunkworks members who put this together, wrote that Remix itself is inspiring to engineers. It motivates them to make these migrations come to life so they can be using it. It gave us an edge in the culture shift, which I don't think we fully appreciated. Massive props on that one.

If you walk out of this talk thinking that I'm saying that Remix is the solution to all of your frontend needs, then I have failed in my job as a speaker, because one thing I really want to emphasize is that this was the solution that worked for us. It might not work for you. It might not work for your particular business needs. Remix was in the right place at the right time to solve what CarGurus needed it to do. If we had slightly different requirements, or if another toolkit had come out, or if we had done this a year earlier, the results could have been very different. Since this is the choice that we did make, the ongoing work that we're doing with Remix is on a couple of levels.

First, we need to continue migrating apps to Remix when it makes sense to do so. I said at the beginning that not every application should be server-side rendered. It's a very common question, why don't we just SSR everything? I'm personally of the opinion that if something is behind the authentication wall, it probably makes less of a difference to server-side render it, because then SEO isn't as much of an issue. You've already got a user through your funnel. They've already logged into the site. They've gone through all those steps, so what, if it takes an extra half second for some content to be delivered? They're already where they need to be. On the other hand, some sites behind the off wall do make sense to SSR, if you do need something to come back quickly or it's something that you don't necessarily want to process on your user's machine.

Ongoing is integration with our server-side ecosystem. We have a large volume of server-side apps that my colleague Frank Fodera will be talking about in his presentation. We needed all of our Remix apps to play very nicely with our existing ecosystem. This is 15-plus years of Java code and services. We're still using struts for application routing in some places, and there have definitely been issues that stem from jumping through hoops to make the frontend and backend play nicely with each other. Client-side to server-side JS headaches, again, not to be overstated, there's a learning curve here. A lot of our devs were not server-side devs coming into this.

Most of them had never worked with Node before. Very talented React developers, but the frontend devs just didn't know really anything about writing anything on the backend, and this is only because they had never needed to before. Fortunately, they still were working in something that was more or less React under the hood, so a lot of it was very familiar to them, but there was still things that they needed to learn to get up to speed. We couldn't rely on things like local storage and session storage and hash URLs that we had taken for granted in some of our client-side applications, so we needed to come up with new ways to handle data loading.

There was actually a blog post on our site recently about an approach that some of our frontend devs came up with for that. This is an ongoing area of research for them. To quote another dev, Ian Schwartz, who's one of our principal devs who has a lot of experience with SSR, he said, with regards to Remix, he's overall really enjoying it. There is a runway to using it efficiently, particularly around data loading, error handling, and API interaction, or at least there was for us. We're really striving to do things the Remix way, and not just have Remix serve up a React app. It makes a lot of sense once you internalize it, but nobody on the team has been using it long, so it's clunky. I'm sure in a year I'll be cursing the code I wrote this spring. I think that's generous, if I'm not cursing the code I wrote three months ago, then I'm not growing as a dev. I hope that he's embarrassed of anything he wrote by the end of the summer.

Closing Thoughts

Don't pick the tool first. Again, if you walk away from this thinking that Remix is the be all and end all solutions to SSR, then I have failed in my job as a speaker. You want to clearly articulate your business and development requirements, and then pick a framework that's best suited to achieve those requirements. Name recognition alone isn't enough. I did have a client once who absolutely insisted that we use MongoDB as the database backing a project we were working on. This was long before I was at CarGurus. We were working with index data, like Mongo just was not the right choice for this project, but it was in the news. It was a fairly new offering. He just insisted that he wanted us to use Mongo for the project. Just because something sounds good on paper or is popular doesn't mean it's what you should be using to accomplish your goal.

Have some sense of the tradeoffs that might need to be made when choosing one framework over another. We spent a lot of time locked up in a room with whiteboards going over what turned into the slide I showed at the beginning, about the pros and cons of the different frameworks, trying to figure out, if we go with this route, what do we get? What do we lose? What's absolutely essential? What are the deal breakers on this? What can we roll ourselves? What do we want to find another solution for? Really have some idea of what it is that you want to do before you set out to do it. Maybe most importantly, your developer experience is just as important as any technical consideration. The devs are the stakeholders in this one.

This is a developer experience tool that we're creating, so include them in the process, because they're the ones who are going to be using this. That's why it was so important to include some of those testimonials from the devs, because we could not have done this without them. It was five of us working on this, but we were constantly consulting with devs across the organization to figure out, does this work for you? How does this look? Do you feel comfortable with this particular workflow? Get a sense for what they could do, and what they wanted to do and how they wanted to use the new tools that we're creating. I'll finish up by paraphrasing a quote from one of my favorite TV shows, "The framework isn't the thing, it's the thing that gets you to the thing".

Questions and Answers

Participant 1: If I have to start again a similar project like this one, which recommendations or what are we going to do differently from this time?

Shopov: What would we do differently if we were going to do it again? Not much, because we did do a lot of prep work on this one. We spent a couple of weeks before writing a single line of code just trying to figure out what it is that we wanted to accomplish. One of my favorite books is The Lean Startup, and it talks about this approach of just throwing ideas at the wall. If you want to build a car, you have to start with a skateboard, because that'll get you from point A to point B. You don't start by creating a door with polished chrome on it.

Rather than do something differently, maybe expand out a bit, knowing what we know about some of the hiccups that we've had along the way, particularly integrating with our backend services. Get a better sense, maybe by bringing in some of the Java engineers and think about, what do the APIs need to look like? Do we have well established contracts for these things? Do we need to create a middleware layer for something? Get a little bit more into the weeds about considerations that would be the same, regardless of which framework we choose.

Participant 2: How are you guys hosting your Remix application alongside with your Java Spring applications?

Shopov: Everything is just up on AWS. We do have a subsidiary that does use, I think Vercel is the ubiquitous Remix hoster. We do have some teams that have played with that a little bit. Generally speaking, the Remix apps just go up in the cloud with everything else. No special infrastructure. That was definitely something we did talk about, was, do we have any particular infrastructure considerations that will make this significantly different.

Participant 3: On the team of five freedom fighters, did you all have consensus on Remix, or did you have to build consensus between the five?

Shopov: We definitely had to build consensus, and it was contentious at times.

Participant 3: How did you do that?

Shopov: How did we do that? I'd be lying if I said that there were not a lot of heated conversations, including at least one borderline shouting match. Because when you have five principal engineers, all of whom have many years of experience and have been doing things certain ways, you have a lot of very strong opinions in the room. One of the hardest things to do is remain humble in the face that maybe your ideas are not the best one. I'm speaking not just for myself, but for other devs as well. I was a big proponent, I said at the beginning, of rolling our own solution. It hurt to have to give up that idea, but they made the case very strongly, that in the end, that was what we were going to need to do.

Particularly when it was coming down to Next versus Remix, it was heated. We had devs from outside of the skunkworks who were really weighing in on this one, saying that they would like us to go with one direction or another. Ultimately, we let the dev experience speak for itself. We looked at both solutions, and we realized that what we did in Remix was much easier to work with than what we did in Next, which, again, is not an endorsement of one over the other. It was the right tool for this particular job. One of our subsidiary companies uses Next to great effect, and they love it. Definitely pick the right tool for the right job.

Participant 4: At what point would you let a team experiment with something else?

Shopov: At any point, with the caveat that there's only so much support that they will get. If they're using an untested framework, there's only so much we can do to help them out if something goes wrong. We do have this culture at CarGurus of what we call Pit Stop Days, where people can just experiment with whatever they want to work on. A concept that we, I think, borrowed from Google originally. We have had people that have done little side projects playing with different build systems, different deployment systems, if a new framework came along.

Vue was popular for a while. I know that's in regular use. I haven't played with it myself, but other devs have. If a dev wanted to do an experiment with another framework, it's absolutely a Pit Stop project or a science fair project they could do. If they can make the case that this is a good solution for something, a standalone app could totally be developed in a different framework. That's one of the nice things about having this distributed ecosystem, not being as monolithic as we once were.

Participant 5: I come from a Java background, and with JSP and now this stuff, and one of the issues that we had in the past, historically, is that people that are in the backend write JS, they forgot they're run in the browser. The frontend developers have to talk to Java code and they forgot they're on the server. How with these needs which server-side I agree, one of the directions that I should take for the frontend? How do we avoid having the same issue again?

Shopov: I don't know if you can avoid that across the board. We have a joke, and it's definitely not exclusive to CarGurus, is that we have many full stack developers who are full stack in name only. They're very comfortable on either the frontend or the backend. I like to think I am actually full stack. I have worked very low level with Java, and even going back before that, I was working with C and C++ and even assembly. I've gone to the lowest levels. I've also built a lot of SaaS applications entirely on the frontend. We have few other devs at the company who have done very similar things. We have one perspective on it, but we definitely have devs that want to learn the other side of things. We have some that want to have nothing to do with it. We have some Java devs that if you show them a piece of JavaScript, they will run screaming into the night while making various religious symbols in the air in front of them.

We have JavaScript devs that are terrified by the word server, and they don't want to think about anything that doesn't happen in the browser. We have plenty of devs who really do want to understand both sides of it. We have what we call guilds at the company, where it's small special interest organizations. We have a server-side guild, and we have a frontend guild. They talk a lot about these different concepts from frontend and backend development, and figuring out, where is there common consensus? What can they learn from the other way of doing things? Particularly doing things the Remix way, versus just taking the concept that they understood from working in the frontend and doing it the same way in Node, when there is probably a better way to do it.

Participant 6: We went through something similar now with server-side, and one of the biggest difficulties was not the technical side, but the disagree and commit, especially the commit. How did you convince a team, or how did the executives convince the team that they would better be spending time transforming to Remix rather than creating a new feature?

Shopov: We spent a couple of months trying to convince our directors and C levels that this was the right approach, and multiple meetings and PowerPoint presentations. I was in a room with 20 high level leadership, basically outlining why this was the right approach from a developer experience, from a company experience, from a financial standpoint, because trying to continue to fit things into our existing monolith was just going to create more tech debt. We were going to have to make compromises. There were some things we just flat out could not do. We could not upgrade to newer versions of React because it would break things in legacy systems.

We could not upgrade to newer versions of webpack or use different linting tools, all these things, because, while some teams were very much on the cutting edge with their React applications, we had others that were still unfortunately stuck in earlier days where they had built something that worked, and they couldn't really update it. I can't downplay how much time and effort it took. There were times we did not think they would let us do this. That's why I joked that we all put our resumes back together at the start, just in case this didn't work out. Navigating leadership was the first major hurdle that we had to overcome. Fortunately, we have a leadership that was forward thinking and open minded. We had to really explain the benefits and what they would get out of this, but they saw it for the value that it would offer, and it's definitely paid for itself.