Welcome to 2019, everyone! If we all close our eyes and wish really really really hard, this year might be less of a dumpster fire than the last one.
The astute reader will ask why I haven’t posted anything for more than two weeks. One part of the answer, dear reader, is that I just didn’t feel like it. I’m on vacation. I want to spend this time wrapped up in a blanket, sipping on something warm, reading or listening to music or playing a game.
The other part of the answer is that I wanted to break a dangerous pattern I’ve noticed in my own behavior. It goes something like this:
Step 1: I take up a new activity. It could be music lessons, a new workout routine, writing reviews for all the music I listen to, or writing these weeknotes.
Step 2: In the beginning, the activity is fun because of the novelty. Eventually, though, the magic of the honeymoon period wears off. This is when you really need to start putting your back into the work, and also when most people quit.
Step 3: Even if I’m not as totally in love with the activity as I was in the beginning, I still keep showing up every day. At a logical level I understand that you don’t get good at anything without putting in the work, and it’s no fun doing anything unless you’re at least slightly good at it. So I show up each day, no matter what happens. Gotta keep that Seinfeld calendar going.
Step 4: Eventually I miss a day. It could be because I’m tired from work, or sick, or traveling, or because someone is visiting me. It could be a number of totally legitimate reasons, or it could be simply because I don’t feel like being productive that day.
Step 5: There was a time when, if I missed a day, my internal voice would start berating me. “You’re such a loser Ankur,” it would say. “You can’t even show up to the gym for three weeks without missing a day. You’re useless, you’re trash.” Over the years I’ve managed to reign in this voice, but missing a day still feels like I’ve committed a grave and unforgivable sin. As if I’ve done something morally reprehensible that I need to be punished for.
Step 6: The activity — whether it’s exercising, music practice, writing, or whatever else — starts to feel like a chore. I do it not because I’m interested and committed, but because I’m afraid. I don’t want to feel the guilt and shame that comes from skipping one day.
Step 7: I start associating the activity with negative feelings. Sometimes I stop doing it altogether. It’s easier to not do something than to do it semi-regularly and feel guilty all the time.
Step 8: Pick a new activity and go back to to step 1.
This year I’m trying to break the pattern. In order to enjoy my hobbies and side projects, I need to prevent them from becoming obsessions. I need to know when to take breaks and go easy on myself – and also when to push myself to my limits. It’s important for me to keep reminding myself that hobbies are supposed to bring you joy, not to suck our your life force.
And so, when I didn’t feel like I was up to the task of writing a weeknote, I didn’t push myself. More than once I felt a twinge of guilt. I even started to write something one Wednesday, but then I reigned myself in and played a few more hours of Breath of the Wild.
I’m doing a few other things this year to improve my relationship with my work, and to make sure I don’t burn out like I’ve done in the years past. I’ll talk about them in my next weeknote.
For now, I wish you a happy 2019! Eat your vegetables, keep your sneakers clean, and be kind to yourselves.
Reading: Creative Quest by Questlove, and Lihaaf by Ismat Chugtai
Listening to: a bunch of old Parliament/Funkadelic records, a random assortment of Kanye’s old hits, Kids See Ghosts by Kids See Ghosts, and Remind Me Tomorrow by Sharon Van Etten.
Playing: The Legend of Zelda: Breath of the Wild. Trying really hard to keep myself from dropping $85 on Super Smash Bros.
The great thing about the weeknote format is that you can make your note as long or short as you want. I’m keeping it short today, with just a few important life updates.
2018 has been a tough year for me. My mental health is at an all-time low, so I’ve decided to take a long break from work and go live with my family in Delhi for a while. I’m planning to spend a lot of time cooking, reading, and playing games on my Switch.
By this time every year, I have a long list of new year’s resolutions. Last year’s list had no less than 20 items. I ended up accomplishing a lot of those goals, but they came at the cost of my health and well-being. I’m going to keep it simple this year and limit my list of resolutions to three or four tiny goals. Top of the list: take care of my health.
My throat is slowly getting better. I expect that I’ll be able to talk normally again in a month or two. Reddit tells me I might have something called Muscle Tension Dysphonia, but I’ll keep the self-diagnosis out of this and just do what my doctor tells me to.
A happy new year to whoever is reading this tonight! Hope you find whatever it is that you’re looking for.
Reading:Never Eat Alone by Keith Ferrazzi
Listening to:Bayaan by Seedhe Maut, i am > i was by 21 Savage, and a bunch of critically acclaimed albums from 2018.
I’m in one of my Moods™. All my brain wants to do is scroll through Instagram looking at pictures of beautiful skinny perfect people wearing beautiful expensive perfect sneakers. On weekends, I can’t even summon up the motivation leave my blanket, let alone do normal functional adult things.
I’ve had to put in a Herculean effort to just sit down and type out this note. That’s why I’m publishing this on a Tuesday instead of a Sunday as usual.
This sense of listlessness and melancholy will pass in a few weeks, as it always does. In the meantime, getting any work done will be an uphill battle. There is nothing to do but take it easy for a while and wait for the feeling to pass.
Status of throat: please make it stop. I’m going to wait one more week to see if things improve, and then spend the first half of January visiting a few different doctors until I find someone who can figure out what’s wrong with me.
I’ve realized that the two keys to consistently doing creative work are to know when to take a break, and to know how to get back to your creative schedule after that break.
I find it easy to show up and put in the effort towards my music every day, but only for a few months at a time. Eventually there comes a time when I start to feel burnt out and empty—like right now—and then I fall off the wagon.
So the problem is not that I can’t show up and consistently write, but that I don’t understand when I’ve reached my limit and need to take some time off.
I spent a few hours this morning thinking about this problem and came up with some general guidelines for myself. I’m not going to share all of them here, but here are two significant ones:
I’ll take a break when the sense of anticipation and joy that comes from writing is replaced by a sense of dread and guilt. Or, I’ll take a break when I can’t make significant progress on a song for two consecutive weeks.
When I’m on a break, I’ll do something that takes my mind off music and writing. For example, binging a TV series, finishing a game, or just watching cat videos on YouTube. Anything to do with music—except casually listening to it—is strictly off the table.
Pretty obvious guidelines, but the most obvious things are often the ones that are the hardest to bear in mind as we go about our lives.
Reading:Never Eat Alone by Keith Ferrazzi
Listening to:critically acclaimed albums from this year, including Wide Awake! by Parquet Courts, El Mar Querer by Rosalía, and A Brief Enquiry Into Online Relationships by The 1975. 2018 has been a disappointing year for music. I haven’t really discovered anything new this year that I still expect to be listening to five years from now.
Playing: nothing. I finished Diablo III a few days ago, and I now want a break from games for a few weeks.
I’m writing this from Chennai. I’m here for the second time this year for a Visa appointment. It’s all very scary and stressful and dehumanizing and I just want it to end already. I have many opinions about traveling internationally as an Indian citizen, but if I start writing them down they’ll take up this entire weeknote. Let’s just move on, okay?
I skipped writing last week’s note because my brother was visiting me in Bangalore. I cooked mac and cheese for him and a bunch of friends using a recipe Pooja shared with me a long time ago, and it turned out great as always. I’m starting to be known as the guy who cooks mac and cheese all the time, and I ain’t even mad. Thanks Pooja!
Exciting throat update for those who have been following along: last week, my doctor stuck a camera down my throat to figure out if there was something seriously wrong in there. Turns out there is a very slight injury on my vocal cords, but nothing too serious. I don’t have much pain anymore, and I’m able to have short conversations without any problems. If I speak for too long, though, my throat starts to get sore. Saying nothing for two months might have something to do with it, and I’m hoping this problem will sort itself out in a few days. If not, then it’ll be another trip to the hospital.
Hospitals and airplanes. That’s how I’ve spent most of this year. Next year, I’m planning to stay put in Bangalore and prioritize my health over everything else.
I’ve been thinking a lot about this staying put business. Sitting still and just enjoying what I have is something I’ve never been able to do. In life, work, and art, my tendency has been to always move forward, to always keep looking for the next thing.
Learned how to use a new piece of technology? Great, now move on to the next trendy thing. Figured out how to produce a new genre of music? Cool, go check what else is hot on SoundCloud right now. Went on a great vacation? Start planning next year’s vacation before the bags have been unpacked. Reading a book on a lazy Sunday afternoon? Start thinking about how great the next one is going to be before this one is half finished.
Novelty is fun. It’s addictive. Always doing something new means I’m constantly learning new things, keeping myself challenged, meeting new people, discovering and rediscovering the world and, with it, discovering and rediscovering myself.
But I’m starting to get tired of all of this thrashing around. Listening to new music is great, but what about all my favorite music from years ago that I’ve lost touch with? Trying out new food is great, but what about the food from my childhood that comforts me and reminds me of simpler times? Taking up new hobbies is great, but what about the hobbies I’ve already invested thousands of hours—and rupees—into? Making new friends is great, but what about all the friends I’ve neglected to keep in touch with?
I’m not saying I need to stagnate, stop being curious about the world, and stop doing new things. I wouldn’t want to live a life that lacks the thrill of discovering something (or someone) new. No, stasis is worse than this constant turmoil.
However, as I’m growing older and taking stock of my life, I starting to crave deep roots. I crave friendships and relationships built on shared experiences over a long period of time (I’m grateful to have at least a few of these). I crave being part of a larger community. I crave a career that’s built on practical experience gained from years in the trenches, not bookish knowledge picked up from blogs and READMEs. I crave skills that have been sharpened by thousands of hours of deliberate practice.
I crave something that keeps me rooted when things go south, something that’s a constant reminder of who I am and where I came from. Even if it’s just daal makhani or trashy nu-metal music everyone pretends never happened.
I crave a kind of spiritual wealth accumulated by respecting what I have, being grateful for it, taking care of it, and slowing down.
That’s it, I think. I want to slow down. At least for a little while.
Reading:The Power of Habit by Charles Duhigg
Listening to: a random selection of favorites from Mitski’s Puberty 2, Run the Jewels’ RTJ2, St. Vincent’s Masseduction, and The Hotelier’s Goodness, among many others.
I survived Siberia! After my talk last Sunday, I had two days in Novosibirsk to spend as I pleased before my flight back to Delhi. I spent those days walking around the city center, reading, and writing. The temperature fell to -13ºC while I was there, so I had to keep ducking into random cafes and restaurants every half an hour to keep myself warm. It goes without saying that I had a lot of caffeine in my system by the time I got back to my hotel each night.
Coming from a hot country like India, and having seen snow only on Christmas specials on TV, the city center looked like something out of a fairytale.
At one point I was walking through a park and spotted a flock of pigeons sitting in the snow (how!!?) When I went close to them to take a picture, the entire flock waddled towards me and surrounded me on all sides. They made themselves comfortable, ruffled their feathers until they were all big round grey balls of fuzz, and stared at me in anticipation as I struggled to operate my phone with my freezing fingers. I think they were expecting me to feed them, but the only feed I had on me at the time was my Instagram feed (badum-tish). Sorry to disappoint, pigeons. Maybe next time.
I made sure to end each day in Novosibirsk with a bowl of solyanka. My first order of business when I get back to Bangalore will be to figure out how to make this delicious soup in my own kitchen!
This year I’ve read several books about creativity and the creative habit, the most recent one being The Practicing Mind by Thomas M. Sterner. It puts into words many truths about creativity that I’ve discovered for myself over the last few years but haven’t had the vocabulary to express.
While reading the book I had a realization: even though 2018 has been a terrible year for me in all respects, I’ve found a sense of peace and calm thanks to my own creative practice.
This year I’ve had to deal with failing health, close friends moving away, a wonderful relationship ending, a pet dying, and a frustrating career slump. Through all of this, I’ve found a sanctuary in writing and making music. The simple habit of sitting down at the same time each day, shutting out the world for a short time, and writing down one word after another has kept me grounded, sane, and oddly contented through these rough times.
The knowledge that I’ll wake up tomorrow morning and spend some time writing gives me great comfort. I can only hope that I’m able to keep up with this habit long-term. It’s something I would be devastated to lose in the chaos of everyday life.
While I’ve had about 30k impressions on the post so far, it hasn’t really generated any leads for new business. I can’t say I was expecting it to get us new business immediately, but I do find it a little disappointing that it resulted in absolutely zero enquiries.
Ah well, on to the next one. I know that it takes a large body of work over a long period of time before writing and speaking starts to bring in prospective customers. Keeping that in mind, I’ve already started working on my next post.
In my head, an ideal situation would be to have enough content on the Uncommon blog to attract 250-300k monthly readers even when we’re not actively writing. We can then begin to figure out how to convert these readers into clients. I’m confident we’ll get there within 12-18 months of regular writing.
It’s a long road ahead. Good thing writing is so damn fun.
Reading:The Practicing Mind by Thomas M. Sterner
Listening to:DiCaprio 2 by JID, CARE FOR ME by Saba, and Be the Cowboy by Mitski
Hello from Novosibirsk, Siberia! The temperature outside is -9ºC, the streets are covered in fresh white snow, and I’m sipping hot chocolate at a cafe near the Novosibirsk Opera and Ballet Theatre. I came here to speak about Rust and WebAssembly at DevFest Siberia 2018, and I’m staying here for a few extra days so I can explore the city.
This is the first time in the 28 years I’ve been alive that I’m seeing snow. When I boarded my flight from Delhi I was afraid that I wouldn’t be able to deal with the sub-zero temperatures in Siberia, but when I got here on Thursday I discovered that central heating exists. I’m glad to report that I’m cozy af, and the only time I have to deal with the cold is when I’m running from a building into my Yandex Taxi.
I think today I’ll venture out for a long walk around the city center. Wish me luck.
Yesterday afternoon I spoke to an audience of about 150 people about drawing the Mandelbrot set on a <canvas> using Rust compiled to WebAssembly. While I knew my material well, I hadn’t practiced verbally delivering my talk before I went on stage. The injury to my vocal cords still prevents me from speaking at length without pain, so all the run-throughs I’d done involved me mouthing words in front of a mirror.
Problem is, things sound way cooler when you say them in your head. When I actually vocalized my words in front of my audience, my jokes fell flat and the explanations that had sounded lucid and coherent in my head came out sounding ambiguous and confusing. Even the SpongeBob GIFs didn’t do much to excite my audience.
I’m going to do this same talk at another conference in January next year, and I plan to start preparing for it as soon as I get back to Bangalore. The current state of my vocal cords makes it impossible for me to do multiple run-throughs of the talk in a single day, but I can certainly practice it once or twice a week so that I’m better prepared to go on stage by the time January rolls around.
A few specific things I noticed about my talk:
I have some math in my slides. While I explain it in human language during the talk, understanding it is not too important for the audience. It’s not even complicated math; it’s possible for me to translate it into plain English instead of presenting it using mathematical symbols. Doing that would make my talk far more engaging.
Before starting to explain the Rust code I’ve written for this talk, it might be a good idea for me to explain what I’m attempting to do in simple, high-level terms. This would allow people who miss out on some of the small details of my code to still understand what’s happening in the talk as a whole.
Forty-five minutes is a long time. I can be less conservative about what can be covered in forty-five minutes, and take my time to explain some of the trickier parts of my code.
In last week’s note, I mentioned that I wanted to write about a few reasons I’ve failed at doing my job effectively this year. I’m going to list them out now.
(For context, I recently stopped being a lone freelance developer and started working at Uncommon so I can build a Web Engineering team here.)
I don’t ask for help when I need it most. It’s not because I feel I don’t need it, or even that I’m ashamed of reaching out. It’s genuinely something that does not occur to me at all. This is one of the reasons I’ve been a bad collaborator throughout my life. Too often, I’ve spent days researching solutions to a problem when I could have just walked across the office and asked somebody.
This is a habit that I’m slowly starting to change. I’ve been a lone-wolf (well, more a lone-puppy than a lone-wolf) developer for the last five years so it’ll be a while before I learn how to work well with people. But I recognize that programming is a team sport, and I’m confident that I’ll get there.
I avoid tough conversations. Just the thought of conflict makes me so anxious that it becomes hard to function. If I find myself in a difficult conversation with a client or co-worker, I’m so exhausted by the end of it that I can barely get work done for the rest of the day. This has cost me dearly in both my personal and professional life, and I honestly don’t know how to fix this.
If you have any ideas, I’d love to hear from you over email or twitter.
I overestimate my own abilities. I feel everyone has been guilty of this at some point in their lives. It becomes a real problem when you end up burning yourself out or hurting yourself physically, both of which have happened to me a couple of times now.
As I grow older, I’m learning to anticipate my body’s needs more and prioritizing my health over getting work done.
I work reactively. I often react to situations as they arise instead of planning ahead. This is frustrating because, if I’m spending all my time putting out fires, I’m not working on my long-term goals. This is another problem I don’t know how to fix yet.
I don’t make full use of the resources available to me. This is a strange one, and I still don’t understand why I do it. Here’s an example: I recently needed a spare Android phone to test something I’d been working on. The model I wanted cost about $150. Instead of just asking my company to buy me one—which they would have happily done—I spent a week looking for somebody who could lend me one for a few hours.
Recognizing these issues has taken me a long time, and dealing with them is going to take an even longer time. However, I’m grateful that I got this far. These are all normal issues that can be tackled, and I plan to do just that in the coming few months.
Reading: Americanah by Chimamanda Ngozi Adichie.
Listening to: FM! by Vince Staples, CARE FOR ME by Saba, and Be the Cowboy by Mitski.
Playing: Diablo 3, and Mario + Rabbids: Kingdom Battle.
I’m writing this from Delhi, where I’m visiting my family for a few days before I leave for Russia to attend DevFest Siberia 2018 as a speaker. My talk is about using Rust and WebAssembly to draw fractals in the browser. I’m really excited, not just because Rust is amazing and WebAssembly is amazing and being able to use both of them together is amazing, but also because this will be my first talk outside of India!
My throat, however, has still not fully recovered. I’m scared that I won’t be able to speak for 45 straight minutes without hurting myself badly or lapsing into a coughing fit. I’m going to a new doctor tomorrow and hoping for a miracle. Fingers crossed.
After I published last week’s post, a friend asked me why I wanted to publish these weeknotes on the Internet for everyone to see. Taking time to introspect is helpful, putting your thoughts down in writing is also helpful, sharing them with close friends and family is perhaps also helpful, but why put them up for strangers to see?
That question doesn’t have a single answer.
First, I enjoy the conversations that happen as a result of me publishing something on my blog. It’s powerful, to connect with another human being simply by the virtue of typing up whatever I’ve been thinking about lately. I don’t have a vast army of fans hungering to read my next piece, but the five or six people who click through to my posts from Twitter usually end up talking to me, which is reason enough for me to continue writing.
So far, almost everything I’ve published online has been technical. I suppose these weeknotes are also an attempt on my part to break away from that kind of writing, to flex writing muscles I haven’t flexed since high school.
Third, it’s fun to have this little space online where I can just type and not have to worry too much about tailoring my words to a specific audience. I enjoyed the old-school blogging culture of a decade ago, which was what passed as social media back then. People wrote meandering posts about what they were cooking, their favorite coffee places, or how their dog walked all over their favorite rug with muddy paws that day. That kind of stuff now happens on Instagram, Twitter, or Facebook. Social media is fun, but it doesn’t quite afford me the space to think out loud in the way I’m doing right now.
Fourth, my weeknotes give me a chance to practice saying “I don’t know”, or “I’m struggling with this”, or “I don’t feel so great” until I have an easier time saying these things.
Fifth, maybe someday some of this will help somebody else?
Sixth, it gives me a feeling of accomplishment, of having made something. I know it’s nothing that has much value, but hey. It gives me a certain satisfaction.
I can probably go on about this, but I’m going to stop now. It’s been a long day and I’m about to fall asleep at the keyboard.
I’ve recently made a lot of progress on some of my creative projects because I’ve started dedicating an hour and a half every morning exclusively to them. During this time, I disconnect completely from all means of communication and focus solely on my work. I sometimes feel like a jerk when I turn my phone back on and find frantic messages from people who have been trying to get in touch with me, but nothing world-ending has happened yet. I’ll continue being a jerk for the foreseeable future.
I’ve started doing a similar thing, to a lesser extent, for the work-related writing I’ve been doing at Uncommon. It seems to be working, because I’ve already finished writing one blog post that I’m going to publish next week! I’m probably going to start leaving the office to do this, and sit at a nearby cafe with a coffee in order to get some thinking space.
In the next few days I’m coming up with a concrete plan for my client outreach efforts at Uncommon, and I’ll have more to say about it in my notes next week. I also have a few thoughts about the different ways I’ve failed at doing my job properly this year, but that’s another thing I need to sit on before I can write about it.
I recently discovered Weeknotes and now I’m compelled to try writing them myself. The idea of reflecting and thinking out loud in public is fascinating.
* * *
I’ve been sick a lot this year. My current bout of sickness started when I came down with a bad cough that lasted three weeks. After I got better, I went right back to working long hours, going out, and staying up far too late. The infection never really went away completely and has now developed into some sort of an injury in my throat? Serves me right for not listening to my body.
On Monday I sent my client an email telling them that I have to quit working on their project because my brain can’t figure out how to write an if statement anymore. This is the first time in my career that I’ve walked away from a project for any reason. I understand that I genuinely needed to rest and heal, but I still feel pretty garbage about this whole situation.
I still can’t talk for too long without pain. Funny, because I’m speaking at ReactJS Bangalore next Saturday. Fun times.
Kids, take care of your body.
* * *
These last few months I’ve been thinking a lot about how to drum up new business for the Web Engineering team at Uncommon. So far, most of our new work has come to us serendipitously. Uncommoners been active in different technology and design communities in India for years, and the networks we’ve built keep sending new clients our way.
While I’m thankful for all the amazing people we get to work with, relying on the same networks all the time means the kind of work we get to do is not as varied as I’d like it to be. More than that, an over-reliance on existing networks leaves us helpless in the face of dry-spells, since we have no idea how to effectively reach people outside of our circles.
I say we, but really I mean just me.
I don’t have a repeatable strategy for finding new work, and this year has been all about figuring that out. I’ve tried a few things and learned a few things, mostly about what works for me personally. Here is a braindump:
Cold email has a very low conversion rate, even when you’re reaching out to people you’ve previously worked with.
Social media can help you find work. Do not Twitter uselessly, use it instead to become a Thought Leader™ and engage in some Growth Hacking™.
Creating content that helps someone accomplish something is one of the most effective ways of connecting with people. Think blog posts, books, YouTube tutorials, livestreams, podcasts, conference talks, and workshops.
I don’t listen to podcasts, watch livestreams, or look up programming tutorials on YouTube. I do enjoy reading, as well as watching conference talks. I want to create content for people who have similar preferences, instead of putting energy into content I would personally never consume.
Writing is the easiest, cheapest, most efficient way to reach people. It’s hard to stand out from the crowd with just writing, but it’s still worth doing.
I find writing opinion pieces incredibly hard. Much harder than writing something purely technical. In the short term, I’m planning to exclusively stick to technical content. I’ll try my hands at other kinds of writing when I’ve made a habit out of publishing regularly.
Speaking is fun! It’s much more time consuming than almost anything else, but the payoff makes it worth doing.
As all creative endeavours, technical writing and speaking will only pay off if you have consistency and a large body of work. Quality is usually a result of consistency and volume.
Whether you’re a freelancer or you’re running a consulting firm, you have to make time in your schedule for generating new leads. This is part of your job, and it’s not optional.
I don’t have anything particularly insightful to say about this subject yet, but I will keep coming back to it in the coming weeks and months. It occupies a large part of my attention.
* * *
I’ve grown up listening to hip-hop and, like any other hip-hop fan, I’ve tried my hand at writing my own verses. I’ve recorded a few of them and shared them with friends, but it has never been something I’ve taken seriously.
In the last few years I haven’t written much at all, focusing instead on music production with Ableton and the incredible Push 2. But lately I haven’t been able to stop thinking about writing again. Maybe it’s the political climate, maybe it’s the incredible new music coming out of the Indian hip-hop scene, maybe it’s just a phase. Point is, I want to write.
So I’ve started. And this time I’m writing in Hindi.
I’m glad to report that my output is not as corny as I’d expected. Progress is slow, but I’m seeing results and it’s making me very happy.
Part 1 of this case study was a general overview of how the Alaris Prime team rebuilt abof.com to load almost instantly even on flaky mobile connections, and part 2 was an account of how we got to grips with the often confusing React ecosystem. If you haven’t checked out the first two parts yet, you should do so now.
In this final part of our case study, I’ll discuss a few specific issues we ran up against through the course of the project, and how we tackled them.
Keeping Track of Scroll Position in an Infinite Scrolling Grid
A recent post on the Google Developers blog talks about the challenges inherent in implementing an efficient infinite scrolling list in the browser. The post is recommended reading, and I won’t repeat the information it already covers in this case study. Instead, I’ll talk about how we use a URL to keep track of the user’s position within our infinite scrolling grid without slowing the browser down.
Two common causes of jank on pages that use infinite scrolling are:
Event listeners on the document’s scroll event.
Repeatedly querying the DOM from those event listeners.
With a little bit of work, we can avoid listening on the scroll event altogether, as well as keep DOM queries to a minimum.
Listing URLs on abof look something like this:
That page=xxx bit at the end keeps track of the user’s position within the grid, and changes as she scrolls from page to page.
Every 12th product in the grid has a data-page-end property attached to its DOM representation that indicates that the product appears at the end of a certain page. For example, the product card at the end of the 4th page (i.e, the 48th product in the grid) is marked up as follows:
We call these elements page markers, and we keep track of them in an array called activePageMarkers inside our ProductGridContainer component. Whenever a new set of products is loaded, any page markers inside that set are appended to this array.
These page markers are references to actual DOM nodes within the document. Along with these references, we also keep track of their positions on the page, as well as their dimensions. This way, we don’t have to query the DOM for this information repeatedly as the user interacts with the page. We only recalculate it when the user triggers an event that is likely to invalidate our existing data (e.g, resizing the page or rotating the device).
Finally, we use requestIdleCallback() to fire a function called syncPageLocation() whenever the browser is idle, throttle it so it fires at most once every 500ms, and make sure it doesn’t fire if the user hasn’t scrolled the page for a while.
syncPageLocation() uses the browser’s scroll offset and the position data stored in activePageMarkers to find the page marker closest to the bottom of the page. It then extracts the value of data-page-end from that element, and uses history.replaceState() to change the page=xxx bit in the URL to reflect the value stored in data-page-end.
This machinery allows the user to share the URL of the listing page over IM, email, or social media with the confidence that anybody who follows it will see the same set of products that were on her screen a moment ago. Moreover, it allows users to move back and forward between product detail pages and listing pages without losing her position in the grid.
Analytics with Google Tag Manager and Redux Middleware
Analytics on abof.com are powered by Google Tag Manager hooked up to a number of third-party analytics providers.
On each page, the analytics team at abof wanted to capture a number of custom events tied to specific user interactions. We wanted to do this in a way that none of our components had to be made aware of analytics or GTM.
We started out by making a list of all the custom events that the analytics team wanted to capture. For example, they wanted to capture a bunch of data about the current page whenever the user changed its sort option from the default value of “Popularity” to one of the other available options (“Just In”, “Discount — High to Low”, “Price — Low to High”, “Price — High to Low”).
Then, we mapped each interaction to one or more of our React components. The components mapped to each user interaction would emit a Redux action containing all the data we needed to capture about that interaction. For example, the SortDropdown component would emit an action called SORT_OPTION_CHANGED every time the user changed the sort option on a page. This action looked something like this:
In the payload object, the from field kept track of the sort option before the user changed it, and the to field kept track of the new sort option.
Of course, our components were not aware of all the data required by a analytics event. For example, the SortDropdown didn’t know whether the user was logged in, what her IP address was, or even the current page URL. We didn’t want our components to be analytics-aware, so we only had them capture the data that they actually had access to. We filled in the missing bits using a Redux middleware called gtm.
The gtm middleware looked at each Redux action that we were interested in, created one (or, in some cases, more than one) analytics events for each action, filled in any missing information that the events required, and pushed them into GTM’s dataLayer array.
This architecture allowed our components to be oblivious of GTM while still allowing analytics data to be collected at a very granular level.
Caching Pre-Rendered Pages for Logged-in Users
Once our universal React app renders a product listing page on the server, abof’s CDN caches it for 10 minutes. This not only shaves a few hundred milliseconds off our load time, but also helps keep abof’s server bills down.
This optimization is straightforward to apply to requests that come from customers who are not logged into their abof accounts. Any given listing page will look identical to all of these anonymous users, which means we can serve them whatever the CDN has cached.
However, we can’t blindly serve a cached page from the CDN to a user who is logged into abof. A customer who is logged in sees a few extra bits of information on each listing page:
Her username, with a link to her profile, on the top right corner of the page (on mobile, this appears in the hamburger menu).
A dropdown listing all the items she’s added to her cart.
If she’s added an item to her favorites, the tiny heart icon on the top right of each product image is filled-in.
Since this information varies from user to user, caching the page is not an option for logged-in users. On the other hand, letting our universal application deal with every request that comes from a logged-in user means it now has to handle a load it was never designed for.
This is what a typical page load looks like:
User makes a request to a listing page.
The CDN serves up a static HTML page that doesn’t contain any user-specific information (i.e, no cart, no favorites, no username). At this point the user can start interacting with the page.
Our root component makes a request to a REST endpoint that returns user information.
If the endpoint returns valid information, our app knows the user is logged in. At this point, it makes requests for cart items, favorites, and whichever other bits of information are required to customize the page for this specific user.
If the endpoint doesn’t return valid information, our app knows the user is not logged in. It doesn’t need to do anything special to handle this case.
This architecture is not perfect. On slower connections, the user sees page elements move around and change as we fetch the extra information needed to assemble the page. However, it lets us eke out that last bit of performance from an already fast webpage.
In this final part of our case study, I talked about three specific problems we faced while rebuilding the listing pages on abof.com:
Keeping track of page URLs as users scroll through abof’s infinite scrolling grid of products.
Using Google Tag Manager and Redux middleware to collect granular analytics without impacting page performance.
How caching works in an application that uses universal rendering.
In case you missed the first two parts, you can read them here: part 1, and part 2.
If you haven’t read the first part of this case study, I suggest you go check it out before diving into the second part. It’s a quick read that explains in detail our motivations for the technology choices we made while building the new abof.com.
Done? Great! In this second part, I’ll talk about my and my team’s impressions of the React ecosystem, our opinions on build tooling, and our approach to performance testing.
When we started working with abof, all of us were primarily Angular 1.x developers. We had used the framework to build several complex applications, which meant we had the ability to ship quality Angular code rapidly.
However, with a stable release of Angular 2 right around the corner, starting a new Angular 1.x project would have been irresponsible on our part.
My experience with building a small application using Angular 2 a few weeks prior to the start of the abof project had left me with mixed feelings. I personally enjoyed working with the framework and found it a welcome improvement over Angular 1.x, but I had to admit that the number of concepts a newcomer to the framework must wrap her head around just to build a functioning TODO list application with Angular 2 was unjustified.
Besides, as I mentioned in part 1, there were other issues with Angular 2 that made it a no-go for us (mainly the large payload size, and the lack of support for universal rendering).
With some apprehensiveness, we began the process of learning React — and what looked to us like a glut of supporting libraries that were apparently absolutely required to produce a working application. There were a number of tools and libraries that we either didn’t understand the purpose of, or didn’t know if we needed. Redux, Radium, Immutable.js, MobX, Relay, Falcor, Flow, Babel, Webpack, just to name a few.
Despite this fractured and confusing landscape, learning React turned out to be easier (and way more fun!) than we anticipated.
We found that there is only one thing that is absolutely required to build React applications: React. Learning it takes a few hours, and — besides the official docs — there are plenty of tutorials on the web that can accelerate and supplement the learning process. I’m partial to the tutorials on egghead.io.
Having built this throwaway prototype, we were in a position to take a deeper look at the React ecosystem and understand what problems each of the popular libraries was designed to solve. For example, after having spent a day scratching our heads over how to elegantly share state between components, we had a better appreciation of the problems Redux solves.
This exercise let us choose a subset of the libraries that were relevant to us from the plethora of libraries available to us.
In the end, the structure of our application was very similar to what a tool like create-react-app would produce, and our list of dependencies was no different from what any standard React application written in 2016 would use. However, by taking a YAGNI approach to building abof, we were able to understand what purpose each of our libraries served at a deeper level. Most importantly, it kept us from getting overwhelmed with new tools and concepts right at the beginning of the project.
We wrote most of our build system from scratch, adding tools and features as we went. This often caused us pain — for example, adding support for isomorphic rendering after most of the application was already written and functional cost us a few days of development time. We had to rewrite parts of our codebase to make sure they ran correctly on Node.
Our build system did nothing out of the ordinary, but knowing it inside-out gave us the confidence to jump into our Webpack and Babel configurations and tweak things to our heart’s desire. It also helped us automate our release process to a point where building and deploying a new version of the website was a single command.
Would I recommend that every team assemble their build tooling in this piecemeal manner? No. As much as we learned from this exercise, starting with one of the hundreds of available React boilerplates on GitHub and carefully studying its source code would have been a more productive exercise and given us an equal amount of confidence in our tooling.
If you’re starting a new React project now, don’t even think twice about using create-react-app.
Measuring and Optimizing Page Load Performance
Our primary source of insight while measuring page load performance was using the website on real devices connected to real mobile networks. A number of tools exist to simulate different network conditions and spit out numbers, but we found that seeing what our users see on flaky connections and devices was valuable while optimizing our page load times.
WebPageTest and PageSpeed Insights are great for giving you hard numbers to target while building or optimizing your application, and for pinpointing exact areas of your application that need work. However, only by testing on real devices will you know which optimizations directly enhance your users’ experience and which ones shave a few seconds off your loading time without affecting perceived performance in any way.
Our second source of performance metrics was the Chrome developer tools. Even while developing locally, we tested the website with a throttled connection. This pushed us to keep the number of API requests and payload sizes small. We set ourselves a page size quota, which was 150kb of data minified and gzipped. That sounds generous, but we got away with it because we were serving a pre-rendered page to the user.
Our final source of performance metrics was WebPageTest. We ran both WebPageTest and PageSpeed Insights after deploying to our staging server to surface issues we might have overlooked. There are far too many things that can go wrong while building a web application, and automated testing tools serve as — for lack of a better term — interactive checklists that will help you ensure you comply with all the best practices. If it hadn’t been for WebPageTest, we would have never realized that the cache headers on our product images were all wrong, or that we could compress them more aggressively.
Measuring Application Performance
Just like page load performance, our primary source of insight while measuring application performance was using the application on a real device. We had access to a number of low-end mobile phones running Android and Windows Phone, and we would periodically (usually after a staging deploy) test the website on these to make sure abof performed acceptably.
This part of the case study was a mostly subjective look at the React landscape. In the last and final part, I’ll talk about some specific issues we ran into while building abof and how we tackled them.