Making mobile web apps is hard. Browser performance, tooling, and APIs are still a long way from catching up with native mobile apps. Imgur has long had a mobile website, but earlier this year we resolved to prioritize our mobile experience for both web and native mobile users. Knowing the aforementioned difficulties that commonly befall mobile websites, we set out to create a mobile web experience that would buck these trends and provide a fast, native-feeling experience with which to enjoy Imgur.
This was not a light undertaking. Our primary goals were:
- improve time to initial render, a.k.a. “time to initial cat picture”
- engaging navigation between posts to aid content discovery
- overhaul the UI to better mirror our native apps and feel more like a native offering
- take a more modular approach to engineering that will be more future-proof for iterating on
To accomplish this, we had to start on a completely new codebase that diverged greatly from our existing Backbone app. Our existing trusty mobile web experience utilized most of the desktop features of Imgur but fell short in a couple ways. First and foremost, it was entirely-dependent on client-side JS for everything — including the initial render to the device. This means that if the application logic takes five seconds to load on a device, the user won’t be seeing anything for five seconds.
To combat a slow initial load, we decided on an entirely new stack. React was decided on for the core of the application logic (both view logic and client-side manipulation of data) while the backing server would be Node.
- The server initializes the app, importing the same code the browser uses, based on the request URL.
- This app is then stringified into HTML markup.
- The data fetched by the server to populate the app is also stringified.
- The stringified application markup and stringified data is inserted into an HTML template which is sent to the client as the server response.
- When client-side JS loads, the client initializes its own app, using the stringified data from the server to hydrate its own stores, preventing any redundant data fetches.
- The client compares the DOM it would have rendered to the DOM the server sent over using checksums and, as long as the checksums are the same, no re-render is triggered for the initial client render cycle.
- Event handlers go live and the app is ready for interaction in the browser.
While it seems like a lot of steps, what ends up happening is that we can serve the initial view to the client much faster while their browser works on finishing up loading our scripts and making everything go live. Because viewing the content of Imgur is the main draw, this fast initial render is extremely valuable for us!
In addition, this approach lets us have a 99% shared codebase. The only server or client-specific files deal with loading the app for the first time — other than that, everything is shared between the two. Components themselves are engineered to be polymorphic and work in either a server or client-side environment. This basically entails making sure that any browser-specific logic gets executed in lifecycle methods that only occur on the browser. A typical component is below:
As you could probably tell by the above code example, we’ve also fully embraced ES2015. Destructuring, class syntactic sugar, revamped variable declaration, fat arrow anonymous functions, and much more were all taken advantage of with great aplomb. It’s not a new language, but it’s definitely got a fresh coat of paint that makes a lot of previously-verbose expressions fairly terse.
While there’s lots of details to cover with our new mobile web experience, we’re going to save deep dives for follow-on posts.
Users can expect to see our new Mobile Web hitting their browsers later this year.