I’ve been hacking on GitHub Pages as somewhat of a side project for the past three years, but this past January, I took on managing GitHub Pages full-time. I’d apparently been doing what others would call product management for a while (even though I didn’t call it that at the time), and as I entered my first formal product management role, I realized that there were a handful of lessons that I’ve learned over the past decade or so of creating software that fundamentally informed what I thought was the best way to build products, be they proprietary, government, or open source:
1. Absorb complexity on behalf of users
The internet’s a complicated place and most websites don’t make it any better. First and foremost, products should strive to be user-centric. Your job as a creator is to engineer simplicity in an otherwise complex system, to leverage technology to reduce the cognitive burden of accomplishing whatever task your user is trying to accomplish (for which using the thing you built is just a necessary step along the way). Every time you add a toggle, dropdown, or text box, you’re pushing complexity on your users, and you’re adding another decision that they wouldn’t have to make if they didn’t use your product. Software should be hard to build and easy to use, not the other way around.
Example: When a GitHub Pages build fails, we have access to all sorts of diagnostic information: what permissions did the requesting user have, what version of each dependency were we using, when did the build process stop, what’s the backtrace of the exception, it goes on. Instead of throwing the raw build output at the user and telling them to figure it out, we detect the underlying cause, if we can, and provide them with a short, but descriptive failure message, with links to the relevant documentation.
2. Build features 80% of users will use
It’s easy (and fun) to optimize for power users and to solve for edge cases. The real challenge, for any product, lies in genuinely nailing the out-of-box experience for 80% of users. As a geek, it’s always going to be more fun to develop the new wiz-bang feature that will allow the app to integrate with whatever’s currently on the top of Hacker News and as a project maintainer, it’s always going to feel better to ship code and mark an issue as resolved, than to close yet another issue as
wontfix. At the same time, power users typically are the ones most invested in your product, and thus the most likely to be vocal. Adding a setting is easy. Burying that setting in the endless clutter of an “advanced” tab is even easier. Forego the temptation to inch your project one step closer to the inside of an airplane cockpit.
Example: GitHub Pages originally had five markup interpreters, each with their own idiosyncrasies. Data showed that 97% of users used the default engine. Removing the other four engines allowed us to simplify the user experience and concentrate on building more impactful features, rather than maintaining legacy compatibility.
3. Drink your own champagne
There are few examples of great products that aren’t used on a daily basis by the teams that develop them. On the one hand, why would you subject your users to an experience that you don’t deem worthy enough to satisfy your own needs (or that you don’t understand well enough because you don’t experience the problem itself). Would you eat at a restaurant that the chef refused to eat at? That didn’t eat that type of food? When you use a product day in and day out, every bug, every missing feature, every rough corner becomes a form of passive self-trolling, that is, until you fix it. Not to mention, it provides an opportunity to test a feature in the wild, before you subject users to them. Dog fooding, as it’s often called, or “drinking your own champagne” as I prefer, gives you an unparalleled sense of user empathy, a level of understanding no focus group or market survey will ever provide.
Example: At GitHub, not only do many GitHubbers use GitHub Pages to host their own site or blog, but we actively encourage other teams to use GitHub Pages any time they can, and users undoubtedly benefit from that test best of diverse use cases. We added the Jekyll SEO Tag plugin to GitHub Pages a while back. The plugin automatically provides metadata for search engines to better index your site. When it was first whitelisted for GitHub Pages, we didn’t announce it. Instead, we used it internally, for GitHub properties like
desktop.github.com, to see how it performed, and then added it to the GitHub Pages Gem, for early adopted to experiment with, before eventually announcing it.
4. Optimize for the ideal use case, not the most common
If a user can do something with the product, they will. That’s a lot of unexpected uses, and as result, a lot of unanticipated feature requests. Regardless of how the product is actually being used, push users in the right direction. Encourage them to do the thing you want them to do. This means building features that support your ideal use case, not the most common, the one used by the loudest part of the user base, or the squeakiest wheel.
Example: GitHub Pages is used for all sorts of things, from automated status sites to local bakeries. It’s intended to be used for user, organization, and project pages. Instead of investing in features to support use cases completely unrelated to activity on GitHub, we made it easier to publish metadata about your GitHub activity, such as a list of your organization’s most popular open source projects, even though that use case wasn’t popular prior to introducing the functionality.
5. Let the user make the change
Once you’ve added a feature, you’ve entered an implicit but perpetual contract with the user, a contract that requires you to support that exact functionality in perpetuity. Sometimes things need to change. You want to refresh the design. It’s not the direction you want to go any more. It’s too hard to support. Only a small subset of users are actually using it. Whatever the reason, avoid the temptation to pull the rug out from under the user’s feet. Explain why you’re making the change, given them plenty advanced notice, provide an upgrade path, and let them take the corrective step, rather than taking it for them (and thus risk breach of product contract).
Example: In the case of GitHub Pages, we often see this around major dependency upgrades, especially ones that involve unavoidable breaking changes. When a user makes the change that breaks their site, it’s frustrating, but they can do it on their own schedule (and exactly what caused it). When global changes silently break that same site, it’s just frustrating.
6. Always provide an out
I don’t remember the last time I read a user manual, physical, software, or otherwise. It’d be unfair to assume your user has read your documentation, especially not if it’s changed since they first started using your product six months, a year, or five years ago. Instead, every time you communicate with your user, being it copy around a settings toggle, an error message, or blog post announcing a new feature, always provide a link to the relevant documentation in the form of a call to action. Any time you don’t, you’re pushing your user into a dead end that will likely result in both frustration and ultimately opening an issue or a support request. With every interaction, always provide the user with the next step in the form of a URL.
Example: Every time you receive a build failure or warning from GitHub, we have continuous integration tests to ensure not only that that email has a link to the relevant documentation (and thus the remediation steps), but that the link is still valid. Additionally, most Pages interfaces have either explicit or implicit links to their relevant documentation for further explanation whenever we surface an error.
7. Be as lean as possible
If there’s a less heavyweight solution and you’re not using it, you’ve over-engineered things. Look to existing tools (think open source), services (think RESTful APIs), and practices (think shared standards) before rolling your own. Simpler, less complex applications with fewer moving parts are easier to scale, easier to maintain, and have fewer potential points of failure. Not to mention, starting with a lightweight solution allows you to rely on the experience of others, and individually validate any new assumptions early on, rather than baking them in via technical investment where late course correction is difficult or cost prohibitive. Put another way, don’t reinvent any components of the wheel unless you absolutely have to. Focus on your value add, which likely is your core competency.
Example: For the first six years of its existence, GitHub Pages was a single, 100-line shell script, and the resulting sites would be served from two vanilla Nginx servers. It worked for nearly a million sites, but as things grew, we eventually needed to add additional complexity, but that complexity wasn’t necessary at the onset, and was minimal once it was.
8. Iterate, iterate, iterate
If you are not embarrassed by the first version of your product, you’ve launched too late. It doesn’t need to be perfect or complete. In fact, it shouldn’t be. Release early, release often. To borrow a popular tech. idiom, don’t let perfection be the enemy of good (or validated learning). Ship what you can, start small and ramp up to where you want things to be. If you can ship a smaller iteration, do it. If you can ship to a smaller team of internal stakeholders before customers see it, even better. Watch how customers receive things and adapt accordingly. Be transparent, manage expectations. Let your vision evolve. Whenever possible, you should publicly ship 0.1, not 1.0.
Example: HTTPS support for GitHub Pages is one of our most requested features. Supporting HTTPS for more than a million sites is not an insignificant engineering task that requires changes up and down the technology stack. While we’re working to eventually support HTTPS for custom domains, we sequentially shipped changes at the network, load balancer, application, and router layers to support HTTPS for
*.github.io sites (with custom domain support in mind), validated that those changes worked as expected, and hope to build upon those ships going forward.
9. Push logic to the edges
When it comes to technology, centralized, heavyweight, or human-driven process are rarely the right answer. As you design systems, avoid engineering single points of failure, both online and off, which more often than not simply serve to create blockers. Instead, automate (or eliminate) everything possible. In other words, don’t force a human to do what a computer can, or as I like to say, “eliminate all humans”. When it can’t be automated, decentralize authority and empower communities to be self-sufficient by pushing decisions to the edge. Put your faith in the crowd. Rather than creating an entire team to manage a workflow or task, foster ecosystems technically, legally, and culturally. Most importantly, both in computers and in people, don’t bake in locking operations.
Example: If you want to configure a custom domain with GitHub Pages, you must configure your custom domain’s DNS settings, a potentially complex and confusing task. For the first seven years of GitHub Pages existence, if your domain was misconfigured, you had to email GitHub Support, which in turn, often had to reach out to the Pages Team for help. Instead, we automated the diagnostics we normally performed, creating the GitHub Pages Health Check Gem, exposing those checks directly to users (without needing to contact Support), and to the support team in the form of ChatOps and what we call stafftools (without needing to contact the Pages team). The result is faster (self-)resolution of DNS misconfigurations, and today, that tool is open source, meaning users can improve the detection of various edge cases.
10. Share to the widest extent possible
Barriers to the free-flow of information add friction, limit the marketplace of ideas, and more often than not, by inevitably restricting your own ability to communicate, you just end up shooting yourself in the foot. This is true both internally and externally. Whenever possible, share information to the widest extent possible. Make open the default. That means you should prefer open standards, open formats, and open systems over bespoke, custom built, or one-off solutions. To facilitate this, prefer social and cultural norms over technical constraints. Capture and expose process in everything you do. Don’t lock it down unless you absolutely have to. Even better, encourage others to contribute.
Example: We had an internal implementation for gathering metadata about a GitHub Pages site. As a result, users couldn’t replicate that experience locally when previewing their site, despite the fact that there was nothing proprietary about our approach. We created an open source plugin to replicate that experience, and eventually moved to using that implementation exclusively. As a result, users’ local environments more closely represent their live site, and bugs are more rapidly surfaced and addressed as they’re discovered.
GitHub Pages is far from perfect, but I’ve learned a lot spending the last three years hacking on a single product (an eternity in tech. years), and even more, hacking on open source projects, and government projects before that. These are the lessons I’ve learned, from what’s worked, and more importantly, from what hasn’t, that has informed what I think was the best way to build products, but I’m sure there’s much, much more. What rules guide how you think about products (or as I thought of them before, open source projects)?