Beyond the Browser: How NerdWallet Went Native

March 2nd 2021

At NerdWallet, our guiding vision has always been a world where everybody can make financial decisions with confidence. Over the years, this vision has steered NerdWallet from its humble origins as a credit card comparison tool toward a far broader and more ambitious set of undertakings. Perhaps the most profound microcosm of this evolution is the NerdWallet app: a holistic personal finance management solution that provides users with a high-level view of their financial life, surfaces bespoke insights based on their unique situation, and recommends concrete actions for them to make meaningful improvements.

Introducing a mobile app to NerdWallet's web-first landscape required concurrent evolutions of our tech stack, business model, and development culture — some of which continue to this day.

The NerdWallet app is built on a modern stack anchored by React Native, TypeScript, and GraphQL. Each of these represents a transformative technology decision for our mobile engineering discipline, though none more so than React Native itself. While certainly not without its challenges, the framework has been instrumental in unlocking key strategic wins, including blazing time-to-market, impressive development efficiency, and powerful leverage of technology and talent across NerdWallet Engineering. Over the years, these have been instrumental in the gradual teardown of the NerdWallet "web hegemony" -- that is, a deeply-ingrained web-first mentality that had historically played an outsized influence in most engineering, product, and business decisions within the company.

While the landscapes of personal finance and mobile app development have each shifted considerably over the years, NerdWallet remains all-in on React Native, and we are excited to be a part of its continued success.

The dawn of the NerdWallet app

Circa 2016, the web hegemony reigned supreme. The website was NerdWallet: a two-sided marketplace for credit cards and other financial instruments, surrounded by an SEO moat borne of an extensive library of topical and consumer-friendly articles about all things personal finance. Introducing a mobile app to this web-first landscape required concurrent evolutions of our tech stack, business model, and development culture -- some of which continue to this day.

NerdWallet’s frontend engineering community had just concluded a sweeping modernization effort, moving from a dreaded PHP monolith (fun fact: originally developed by our CEO himself back in 2009!) to a microapp architecture featuring over a dozen separate apps built on React, NodeJS, ES6, and Redux. Accordingly, most of the company’s business and product development efforts at the time were focused on optimizing the website’s core revenue engine and replicating that business model in other financial verticals.

By contrast, NerdWallet’s native mobile presence was nascent. There was one small team tasked with a venture-bet, iOS-only chat app that helped users consult with in-house financial advisors. Faced with middling product-market fit (not to mention obvious obstacles to scale), the company pivoted to a brand-new concept: a general-purpose personal finance management solution intended to transform NerdWallet’s visitor-heavy traffic into highly-retained members across web, iOS, and Android.

Several of our web engineers had experimented with React Native during recent company hackathons and were thrilled with the results. Faced with an aggressive delivery timeline and buoyed by a wealth of positive sentiment around React and React Native, we decided to take the plunge -- and the rest, as they say, is history.

Reflection (and inflection) points

The NerdWallet app first hit the app stores in January of 2017 with a modest feature set: credit score monitoring, coarsely-targeted credit card recommendations, and a curated selection of personal finance content. Over the past four years, the NerdWallet app has grown dramatically along every possible dimension: functionality, active users, codebase contributors, and architectural complexity. It is now much closer to our vision of a fully-fledged personal finance management product, including powerful capabilities like financial account aggregation, dashboards for tracking net worth and cash flow, automated money transfer, and ML-driven insights and recommendations.

When performing a retrospective assessment of a technology decision, there is an almost instinctive reflex to frame the conversation in terms of “what did and didn’t work well”. In evaluating NerdWallet's journey with React Native, however, this frame yields a somewhat reductive perspective that doesn’t adequately capture the nuance of our experience. So instead, we’d like to meander through a few broad areas of reflection, most of which offer ample opportunity for celebration, constructive self-criticism, and recognition of pivotal adjustments made along the way.

Development efficiency

Time-to-market and speed of iteration are of paramount importance for early-stage products. These were (and remain) two of the top-billed value propositions of React Native, and after years of rapid product evolution, we’ve learned quite a bit about the extent to which they are borne out in practice.

Accelerators

In terms of time-to-market, we could not have been more pleased with the result. The founding mobile team shipped the first version of the NerdWallet app on both iOS and Android after a development period of just five weeks. Several factors proved crucial to our success, including a deep bench of React talent at the company and the ability to directly reuse several JS libraries for high-complexity areas like authentication and integration with TransUnion (our credit score provider).

95% code reuse? Yes, please.

As we settled into a post-MVP iteration cadence anchored around standard two-week release cycles, several other efficiency wins came into sharp focus. An obvious benefit of using a cross-platform framework like React Native is having a shared codebase, which effectively halves the volume of code that needs to be written, tested, and maintained. Less obvious (but no less impactful) was the equivalent reduction of the communication and coordination overhead involved in executing a fast-paced product roadmap. We were able to achieve desirable outcomes like feature and analytics parity across iOS and Android with essentially zero effort, and QA and release logistics were greatly reduced compared to the traditional, platform-siloed approach to app development.

Over the years, one of our advantages that became increasingly apparent was having started with a greenfield React Native app. Much ado has been made (most notably by Airbnb) about the challenges of successfully integrating React Native into existing native-first codebases and development cultures, with which we wholeheartedly sympathize. In the early days, we too entertained utopian visions of seamless feature development across the React Native layer and the underlying native platform, but eventually realized that doing so would require significant infrastructure investments that simply didn’t make sense to incur from a strategic standpoint. Over time, we instead gravitated towards an approach of building all UX in pure React Native and layering in key pieces of native infrastructure as needed, thus fully capitalizing on the framework's inherent efficiency advantages while minimizing distraction and unnecessary complexity.

Friction

...the React Native community was vibrant and growing, but often lacked a firm stance or hardened solutions for many foundational building blocks

React Native’s promise of development efficiency has largely held true for NerdWallet, but was undeniably tempered by some serious (and often unexpected) sources of friction along the way. In the early days, the React Native community was vibrant and growing, but often lacked a firm stance or hardened solutions for many foundational building blocks. We quickly ended up in a state of over-reliance on a bevy of questionable third-party solutions ranging from cookie management to carousel container views, which introduced both risk and maintenance burden as the core React Native library evolved rapidly beneath their (not to mention our own) feet.

Settling on a navigation solution was particularly costly. We started out with react-native-router-flux and soon encountered severe performance and UX issues. After evaluating several third-party alternatives, we underwent a costly migration to Wix's react-native-navigation, which was a marked improvement but ultimately still proved unsatisfactory from both a UX and DX perspective. At that point, we bit the bullet and developed our own React Native navigation library (accompanied by another costly migration), which has served us quite well but came at a regrettably high expense of codebase churn and opportunity cost along the way.

In the early days, we also met some friction due to the limitations of the React Native dev tooling solutions of the era. Chrome DevTools, Reactotron, and React Native Debugger were each serviceable in their own right, but couldn't provide adequate observability into crucial parts of the app like the FSM (finite state machine) architecture underpinning our navigation flows. This drove us to create our own powerful and modular dev tool framework, replete with out-of-the-box features such as:

  • Searchable/filterable log viewing

  • Network traffic inspection

  • Session timeline visualization

  • FSM traversal visualization

  • Redux and Apollo Client state auditing

  • Analytics event monitoring and debugging

Ever the personal finance Nerds, we've affectionately named this tool piggy -- a play on the eponymous childhood banking solution, as well as its ability to root out even the most elusive of bugs. We intend to open-source piggy in the near future, so stay tuned!

State management

React Native + Redux: a sordid love affair

At their core, apps are just conduits that allow users to view and modify information, or state. State management refers to the set of technologies and techniques that an app uses to access state and reflect changes to it in the app's UI. Accordingly, selecting a state management strategy is perhaps the single most-important architectural decision for an app developer, carrying massive implications for DX, UX, and propensity for defects.

Before we proceed, a brief primer on terminology:

  • Application state (or just "state", for brevity): all state that the app knows about

  • Local state: state whose source of truth is client-side (e.g., whether the device's network connection is online)

  • Remote state: state whose source of truth is server-side (e.g., the user's credit score)

Redux

...we now consider utilizing Redux as a one-stop shop for application state management to be our greatest architectural misstep

Circa 2016, the technical zeitgeist made the decision to use Redux in our new React Native app all but reflexive. Redux pairs naturally with React and enjoyed strong cachet within NerdWallet's frontend engineering community at the time. As mentioned previously, Redux also allowed the founding mobile team to directly repurpose a lot of state management code from the web app, which ultimately proved instrumental in meeting our aggressive timeline.

Despite this early success, we now consider utilizing Redux as a one-stop shop for application state management to be our greatest architectural misstep. Our difficulties fell into the following themes:

  • Managing remote state: Redux shines at interacting with local state, but has zero intrinsic facility for interacting with remote state. We attempted to roll our own infrastructure around Redux to provide standard capabilities like making async API requests, tracking API request meta state, retrying failed requests, cache policies, and cache eviction, which were serviceable and fun to develop, but invariably fell short of the level of maturity and sophistication that the app needed.

  • Code verbosity: Although conceptually simple, Redux is notoriously verbose, requiring developers to write actions, reducers, and selectors for even the simplest of state manipulations.

  • Code "oversharing": the ability to reuse Redux code across clients turned out to be a double-edged sword, manifesting in a phenomenon we call "code oversharing": blithely reusing code from one platform that might not be appropriate for the other. This temptation was exacerbated even further by Redux's aforementioned verbosity.

Graphql and Apollo client

By 2018, NerdWallet's frontend engineering community was abuzz with excitement around GraphQL and its ability to vastly simplify how clients fetch data from disparate backend services (a pain point for mobile and web apps alike). Even better, the popular Apollo Client library seemed an ideal tool not only for fetching data via GraphQL, but also for interacting with it in the UI layer -- thus alleviating two of our primary architectural pain points at once. The mobile team seized the opportunity to serve at the vanguard of the movement, and the mobile app become the very first NerdWallet client to utilize GraphQL in production in mid-2019.

Apollo diagram

The NerdWallet app uses GraphQL and Apollo to simplify data-fetching and state management

Since then, GraphQL adoption has slowly but steadily ticked upward. During the 2020 calendar year, the percentage of the app's API calls made via GraphQL rose from 14% to 60%. Migrating a core layer of the app’s architecture (while continuing to develop features at a fast clip) posed no shortage of interesting technical and logistical challenges, some of our solutions to which can be perused here:

As we invested in our understanding of both advanced Apollo Client state management patterns and modern React techniques (e.g., useState, useReducer, and the new React Context API), we gained more and more confidence that they could, in fact, collectively replace Redux entirely. We look forward to the day when we can finally remove redux from our project’s dependency list.

Talent and technology leverage

On paper, using React for all frontend development unlocks seemingly limitless opportunities for leveraging both technology and talent. Indeed, this was one of our primary motivations in choosing React Native, and our results have proved more multifaceted than anticipated. While we’ve enjoyed almost unequivocal success in leveraging talent across our React and React Native ecosystems, attempts to leverage technology have produced a more nuanced set of outcomes.

Our observations about cross-platform leverage fall into a few discrete themes:

    • Developer portability: the ease with which product engineers can contribute to both React and React Native apps

    • Solution portability: the degree to which the same technical solution (e.g. UI code, business logic code, frameworks) can be used in both a React and React Native application (with minimal additional complexity or abstraction)

    • Community cohesion: the extent to which React and React Native developers can collaborate within a shared set of principles, ideologies, and paradigms

Developer portability: one of the loftiest promises of React Native

To little surprise, we found developer portability to be quite high. It was remarkably easy for React developers to become effective in a React Native codebase, and vice-versa. This has helped immensely with resourcing flexibility, personal enrichment for developers, and cross-pollination of domain context and technical learnings. That said, developer portability tends to extend only as far as the infamous React Native bridge, which separates the JS realm from the native iOS and Android realms in a very tangible, yet highly-symbolic way. Native ecosystems require significantly more investment for developers to become proficient in, from languages to IDEs to toolchains -- and there are two of them. Rather than attempt to ramp everybody up on native-side development, we have found it far more efficient to staff up a small infrastructure team with deep native experience, thus allowing product engineers to focus on moving quickly within their primary areas of expertise.

While sharing feature code across web and native is often more trouble than its worth, we've had great success leveraging shared infrastructure.

Solution portability has also been a net win for NerdWallet, despite producing a more mixed bag of results. As discussed above, our early inclination was to reuse as much code as possible with our web counterpart -- largely via a single JS library, whose initial efficiency boon soon ground to a halt as the library rapidly grew into a monolith. We also observed that the intrinsic commonalities between the web and native tech stacks resulted in a tendency to overgeneralize the problem space out of sheer convenience, as evidenced by the "code oversharing" phenomenon discussed above. While we still actively seek and exploit opportunities for sharing code between our React and React Native apps, we now do so much more judiciously -- and with a keener eye towards the intrinsic differences between web and native product experiences.

While sharing feature code across web and native is often more trouble than its worth, we've had great success leveraging shared infrastructure. For example, the mobile app is able to use the same React Context-based analytics infrastructure as NerdWallet's web apps. We're also able to easily take advantage of Structured Content, an in-house server-driven UI framework that allows us to render NerdWallet's vast library of personal finance content natively (i.e., not in a webview) in a React Native app.

Perhaps the most surprising and pleasing outcome of our React Native journey has been the emergence of strong community cohesion across the web and native engineering disciplines at NerdWallet. This was obviously induced in part by the sheer number of developers who had feet in both camps, but also by the fundamental commonalities between React and React Native -- as well as the density of cross-platform interactions that organically occur via mediums such as technology-specific forums, development of shared infrastructure, code reviews, and hallway conversations. Community cohesion among frontend engineering has been instrumental in fomenting and stewarding several company-wide technological paradigm shifts over the years, from GraphQL to TypeScript, and remains an invaluable cultural pillar of NerdWallet Engineering.

Onward

NerdWallet has always been (and will likely always be) a web-first company, but over the years has taken great strides in dismantling its web hegemony and embracing a more-holistic perspective towards the types of products that our users need to make financial decisions with confidence -- and meeting them on the mobile platforms where those decisions are increasingly performed.

Selecting a native development strategy is a critical strategic decision for any company who seeks a mobile presence, and the landscape has undergone cataclysmic change since the early days of iOS and Android. It’s important to underscore that React Native is just another tool, not a panacea -- but in cases like ours, has proven an immensely valuable one.