In early 2013, less than 14% of all web traffic came from mobile devices; today, that number has grown to 53%. In other parts of the world the difference is even more staggering: in African countries, more than 64% of web traffic is from mobile devices; in India, nearly 78% of traffic is mobile. This is a big deal, because all 248 million new internet users in 2017 lived outside the United States.
And while internet connections are getting faster, there are still dozens of countries that access the web at speeds of less than 2 Mbps. Even in developed nations, people on mobile devices see spotty coverage, flaky wifi connections, and coverage interruptions (like train tunnels or country roads).
This means we can no longer talk about user experience (UX) without including performance as a first-class requirement. A Google study found that 53% of mobile users abandon a page if it takes longer than three seconds to load—and none of us are willing to lose half our traffic, right?
User experience and performance are already aligned—in theory
User experience designers and researchers lay a solid foundation for building modern web apps. By thinking about who the user is, what they’re trying to accomplish, and what environments they might be in when using our app, we already spot several performance necessities: a commuter, for example, will be accessing the app from their phone or a low-speed public wifi connection with spotty coverage.
For that type of user, we know to focus on a fast load time—remember, three seconds or more and we’ll lose half our visitors—in addition to an experience that works well, even on unstable connections. And since downloading huge files will also take a long time for this user, reducing the amount of code we ship becomes necessary as well.
UX and performance have issues in practice
My sister loves dogs. Once, as a kid, she attack-hugged our dog and loved it so hard that it panicked and bit her.
The web community’s relationship with UX is not unlike my sister’s with dogs: we’re trying so hard to love our users that we’re making them miserable.
Our efforts to measure and improve UX are packed with tragically ironic attempts to love our users: we try to find ways to improve our app experiences by bloating them with analytics, split testing, behavioral analysis, and Net Promoter Score popovers. We stack plugins on top of third-party libraries on top of frameworks in the name of making websites “better”—whether it’s something misguided, like adding a carousel to appease some executive’s burning desire to get everything “above the fold,” or something truly intended to help people, like a support chat overlay. Often the net result is a slower page load, a frustrating experience, and/or (usually “and”) a ton of extra code and assets transferred to the browser.
The message we appear to be sending is, “We care so much about your experience as a user that we’re willing to grind it to a halt so we can ask you about it, and track how you use the things we build!”
Making it worse by trying to make it better
We’re not adding this bloat because we’re intentionally trying to ruin the experience for our users; we’re adding it because it’s comprised of tools that solve hard development problems, and so we don’t have to reinvent the wheel.
When we add these tools, we’re still trying to improve the experience, but we’ve now shifted our focus to a different user: developers. There’s a large ecosystem of products and tools aimed toward making developers’ lives easier, and it’s common to roll up these developer-facing tools under the term developer experience, or DX.
Stacking tools upon tools may solve our problems, but it’s creating a Jenga tower of problems for our users. This paradox—that the steps we take to make it easier to help our users are inadvertently making the experience worse for them—leads to what Nicole Sullivan calls a “deadlock between developer experience [and] user experience.”
Developer experience goes beyond the tech stack
Let’s talk about cooking experience (CX). When I’m at home, I enjoy cooking. (Stick with me; I have a point.) I have a cast-iron skillet, a gas range, and a prep area that I have set up just the way I like it. And if you’re fortunate enough to find yourself at my table for a weekend brunch, you’re in for one of the most delicious breakfast sandwiches of your life.
However, when I’m traveling, I hate cooking. The cookware in Airbnbs is always cheap IKEA pans and dull knives and cooktops with uneven heat, and I don’t know where anything is. Whenever I try to cook in these environments, the food comes out edible, but it’s certainly not great.
It might be tempting to say that if I need my own kitchen to produce an edible meal, I’m just not a great cook. But really, the high-quality tools and well-designed environment in my kitchen at home creates a better CX, which in turn leads to my spending more time focused on the food and less time struggling with my tools.
In the low-quality kitchens, the bad CX means I’m unable to focus on cooking, because I’m spending too much time trying to manage the hot spots in the pan or searching the drawers and cabinets for a utensil.
Good developer experience is having the freedom to forget
Like in cooking, if our development tools are well-suited to the task at hand, we can do excellent work without worrying about the underlying details.
When I wrote my first lines of HTML and CSS, I used plain old Notepad. No syntax highlighting, autocorrect, or any other assistance available. Just me, a reference book, and a game of Where’s Waldo? to find the tag I’d forgotten to close. The experience was slow, frustrating, and painful.
Today, I use an editor that not only offers syntax highlighting but also auto-completes my variable names, formats my code, identifies potential problems, helps me debug my code as I type, and even lets me share my current editing session with a coworker to get help debugging a problem. An enormous number of incremental improvements now exist that let us forget about the tiny details, instead letting us focus on the task at hand. These tools aim to make the right thing the easy thing, leading developers to follow best practices by default because our tools are designed to do the right thing on our behalf.
It’s hard to overstate the impact on my productivity that modern development environments have made.
And that’s just my editor.
UX and DX are at odds with each other
There is no one-size-fits-all way to build an app, but most developer tools are built with a one-size-fits-all approach. To make this work, most tools are built to solve one thing in a general purpose way, such as date management or cryptography. This, of course, necessitates stacking multiple tools together to achieve our goals. From a DX standpoint, this is amazing: we can almost always find an open source solution to problems that aren’t ultra-specific to the project we’re working on.
Patching the holes in our UX comes at a price
Of course, we can solve some of these problems. We can manually optimize our apps by loading only the pieces we actually use. We can find lightweight copies of libraries to reduce our overall bundle size. We can add performance budgets, tests, and other checks to alert us if the codebase starts getting too large.
But now we’re adding audits, writing bespoke code to manage the foundation of our apps, and moving into the uncharted, unsupported territory of wiring unrelated tooling together—which means we can’t easily find help online. And once we step outside the known use cases for a given abstraction, we’re on our own.
Once we find ourselves building and managing bespoke solutions to our problems, many of the DX benefits we were previously enjoying are lost.
Good UX often necessitates bad DX
There are a number of frameworks that exist to help developers get up and running with almost no overhead. We’re able to start building an app without first needing to learn all the boilerplate and configuration work that goes into setting up the development environment. This is a popular approach to front-end development—often referred to as “zero-config” to signify how easy it is to get up and running—because it removes the need to start from scratch. Instead of spending our time setting up the foundational code that doesn’t really vary between projects, we can start working on features immediately.
This is true, in the beginning, until our app steps outside the defined use cases, which it likely will. And then we’re plunged into a world of configuration tuning, code transpilers, browser polyfills, and development servers—even for seasoned developers, this can be extremely overwhelming.
- install Node and npm;
- use npm to install Yarn;
- use Yarn to install React, Redux, Babel (and 1–5 Babel plugins and presets), Jest, ESLint, webpack, and PostCSS (plus plugins);
- write configuration files for Babel, Jest, ESLint, webpack, and PostCSS;
- write several dozen lines of boilerplate code to set up Redux;
- and finally start doing things that are actually related to the project’s requirements.
This can add up to entire days spent setting up boilerplate code that is nearly identical between projects. Starting with a zero-config option gets us up and running much faster, but it also immediately throws us into the deep end if we ever need to do something that isn’t a standard use case.
And while the open source developers who maintain these abstractions do their best to meet the needs of everyone, if we start looking at improving the UX of our individual apps, there’s a high likelihood that we’ll find ourselves off the beaten path, buried up to our elbows in Byzantine configuration files, cursing the day we chose web development as a career.
Someone always pays the cost
On the surface, it might look like this is just the job: web developers are paid to deliver a good UX, so we should just suck it up and suffer through the hard parts of development. Unfortunately, this doesn’t pan out in practice.
Developers are stretched thin, and most companies can’t afford to hire a specialist in accessibility, performance, and every other area that might affect UX. Even a seasoned developer with a deep understanding of her stack would likely struggle to run a full UX audit on every piece of an average web app. There are too many things to do and never enough time to do it all. That’s a recipe for trouble, and it results in things falling through the cracks.
Under time pressure, this gets worse. Developers cut corners by shipping code that’s buggy with
// FIXME oh god I'm so sorry attached. They de-prioritize UX concerns—for example, making sure screen reader users can, you know, read things—as something “to revisit later.” They make decisions in the name of hitting deadlines and budgets that, ultimately, force our users to pay the cost of our DX.
Developers do the best they can with the available time and tools, but due more to a lack of time and resources than to negligence, when there’s a trade-off to be made between UX and DX, all too often the cost rolls downhill to the users.
How do we break the deadlock between DX and UX?
While it’s true that someone always pays the cost, there are ways to approach both UX and DX that keep the costs low or—in best-case scenarios—allow developers to pay the cost once, and reap the DX benefits indefinitely without any trade-offs in the resulting UX.
Understand the cost of an outstanding user experience
In any given project, we should use the ideal UX as our starting point. This ideal UX should be built from user research, lo-fi testing, and an iterative design process so we can be sure it’s actually what our users want.
Once we know what the ideal UX is, we should start mapping UX considerations to technical tasks. This is the process of breaking down abstract concepts like “feels fast” into concrete metrics: how can we measure that a UX goal has been met?
By converting our UX goals into measurable outcomes, we can start to get an idea of the impact, both from a UX and a DX perspective.
From a planning perspective, we can get an idea of which tasks will have the largest impact on UX, and which will require the highest level of effort from the developers. This helps us understand the costs and the relative trade-offs: if something is high-effort and low-impact, maybe it’s OK to let the users pay that cost. But if something will have a high impact on UX, it’s probably not a good idea to skip it in favor of good DX.
Consider the cost when choosing solutions
Once we’re able to understand the relative cost and trade-offs of a given task, we can start to analyze it in detail. We already know how hard the problem is to solve, so we can start looking at the how of solving it. In general terms, there are three major categories of solving problems:
- Invent your own solution from scratch.
- Research what the smartest people in the community are doing, and apply their findings to a custom solution.
- Leverage the collective efforts of the open source community by using a ready-made solution.
Each category comes with trade-offs, and knowing whether the costs outweigh the benefits for any given problem requires working through the requirements of the project. Without a clear map of what’s being built—and what it will cost to build it—any decisions made about tools are educated guesses at best.
When to invent your own solution
Early in my stint as a front-end architect at IBM, I led a project to roll out a GraphQL layer for front-end teams to rapidly build apps in our microservice-based architecture. We started with open source tools, but at the time nothing existed to solve the particular challenges we were facing. We ended up building GrAMPS, which we open sourced in late 2017, to scratch our particular itch.
In this situation, building something custom was our lowest-cost option: we knew that GraphQL would solve a critical problem for us, but no tools existed for running GraphQL in a microservice architecture. The cost of moving away from microservices was prohibitively high, and the cost of keeping things the way they were wasn’t manageable in the long term. Spending the time to create the tooling we needed paid dividends through increased productivity and improved DX for our teams.
The caveat in this story, though, is that IBM is a rare type of company that has deep pockets and a huge team. Letting a team of developers work full-time to create low-level tooling—tools required just to start working on the actual goal—is rarely feasible.
And while the DX improved for teams that worked with the improved data layer we implemented, the DX for our team as we built the tools was pretty rough.
Sometimes the extra effort and risk is worth it long-term, but as Eric Lee says, every line of code you write is a liability, not an asset. Before choosing to roll a custom solution, give serious thought to whether you have the resources to manage that liability.
When to apply research and lessons from experts in the field
A step further up the tooling ladder, we’re able to leverage and implement the research of industry experts. We’re not inventing solutions anymore; we’re implementing the solutions designed by the foremost experts in a given field.
With a little research, we have access to industry best practices for accessibility thanks to experts like Léonie Watson and Marcy Sutton; for web standards via Jeffrey Zeldman and Estelle Weyl; for performance via Tim Kadlec and Addy Osmani.
By leveraging the collective knowledge of the web’s leading experts, we get to not only learn what the current best practices are but also become experts ourselves by implementing those best practices.
But the web moves fast, and for every solution we have time to research and implement, a dozen more will see major improvements. Keeping up with best practices becomes a thankless game of whack-a-mole, and even the very best developers can’t keep up with the entire industry’s advancements. This means that while we implement the latest techniques in one area of our app, other areas will go stale, and technical debt will start to pile up.
While learning all of these new best practices feels really great, the DX of implementing those solutions can be pretty rough—in many cases making the cost higher than a given team can afford.
Continued learning is an absolutely necessary part of being a web developer—we should always be working to learn and improve—but it doesn’t scale if it’s our only approach to providing a great UX. To paraphrase Jem Young, we have to look at the trade-offs, and we should make the decision that improves the team’s DX. Our goal is to make the team more productive, and we need to know where to draw the line between understanding the down-and-dirty details of each piece of our app and shipping a high-quality experience to our users in a reasonable amount of time.
To put it another way: keeping up with industry best practices is an excellent tool for weighing the trade-offs between building in-house solutions or using an existing tool, but we need to make peace with the fact that there’s simply no way we can keep up with everything happening in the industry.
When to use off-the-shelf solutions
While it’s overwhelming to try and keep up with the rapidly changing front-end landscape, the ever-evolving ecosystem of open source tools is also an incredible source of prepaid DX.
There are dozens of incredibly smart, incredibly passionate people working to solve problems on the web, and many of those solutions are open source. This gives developers like you and me unprecedented access to prepaid solutions: the community has already paid the cost, so you and I can deliver an amazing UX without giving up our DX.
This class of tooling was designed with both UX and DX in mind. As best practices evolve, each project has hundreds of contributors working together to ensure that these tools are always using the best possible approach. And each has generated an ecosystem of tutorials, examples, articles, and discussion to make the DX even better.
By taking advantage of the collective expertise of the web community, we’re able to sidestep all the heartache and frustration of figuring these things out; the open source community has prepaid the cost on our behalf. We can enjoy a dramatically improved DX, confident that many of the hardest parts of creating good UX are taken care of already.
The trade-off—because there is always at least one—is that we need to accept and work within the assumptions and constraints of these frameworks to get the great DX. Because again, as soon as we step outside the happy path, we’re on our own again. Before adopting any solution—whether it’s open source, SaaS, or bespoke—it’s important to have a thorough understanding of what we’re trying to accomplish and to compare and contrast that understanding to the goals and limitations of a proposed tool. Otherwise we’re running a significant risk: that today’s DX improvements will become tomorrow’s technical debt.
If we’re willing to accept that trade-off, we find ourselves in a great position: we get to confidently ship apps, knowing that UX is a first-class consideration at every level of our stack, and we get to work in an environment that’s optimized to give our teams an incredible DX.
Deadlock is a (solvable) design problem
It’s tempting to frame UX and DX as opposing forces in a zero-sum game: for one to get better, the other needs to get worse. And in many apps, that certainly appears to be the case.
DX at the expense of UX is a design problem. If software is designed to make developers’ lives easier without considering the user, it’s no wonder that problems arise later on. If the user’s needs aren’t considered at the core of every decision, we see problems creep in: instead of recognizing that users will abandon our sites on mobile if they take longer than three seconds to load, our projects end up bloated and take twice that long to load on 4G—and even longer on 3G. We send hundreds of kilobytes of bloat, because optimizing images or removing unused code is tedious. Simply put: we get lazy, and our users suffer for it.
Similarly, if a team ignores its tools and focuses only on delivering great UX, the developers will suffer. Arduous quality assurance checklists full of manual processes can ensure that the UX of our projects is top-notch, but it’s a slog that creates a terrible, mind-numbing DX for the teams writing the code. In an industry full of developers who love to innovate and create, cumbersome checklists tend to kill employee engagement, which is ultimately bad for the users, the developers, and the whole company.
But if we take a moment at the outset of our projects to consider both sides, we’re able to spot trade-offs, and make intelligent design decisions before problems emerge. We can treat both UX and DX as first-class concerns, and prevent putting them at odds with each other—or, at least, we can minimize the trade-offs when conflicts happen. We can provide an excellent experience for our users while also creating a robust suite of tools and frameworks that make development enjoyable and maintainable for the entire lifespan of the project.
Whether we do that by choosing existing tools to take work off our plates, by spending an appropriate amount of time properly planning custom solutions, or some combination thereof, we can make a conscious effort to make smart design decisions, so we can keep users and developers happy.