Enable your team with Reactive Enhancement

Chris Puzzo
Lovepop Platforms
Published in
9 min readJan 29, 2018

Pt. 1: Intro — Why We Did What We Did

In today’s startup world, time-to-market matters more than ever. From Wordpress to Squarespace to Shopify, the market is saturated with content management platforms that enable a new consumer business to quickly and easily get off the ground while building very little of their own tech. Getting started is easy, however the customizability is usually minimal, with a heavy focus on static content. Increasing traffic through paid advertising can only take your business so far and whether it be A|B testing or new product development, eventually you will need to build something your CMS doesn’t support.

When a company reaches this point, they are usually left with a few choices:

  1. Migrate off the managed CMS and build / host it yourself
  2. Find or buy apps / plugins for the CMS that enable new features
  3. Lean into the CMS and extend it in some way to provide the needed functionality

When Lovepop reached this point, we decided to evaluate our options through the lens of desired core competencies of our team. We looked at what, from a business or technical perspective, is already a solved problem (which we should buy or integrate) vs. what is unique to our company and foundational to our growth (which we should invest in building ourselves).

Build v. Buy?

At Lovepop, we use Shopify as our storefront CMS for all of our e-commerce needs. As we started to scale, we took a hard look at who within the business was using it and how. It turns out, pretty much everyone is using it, often for completely different purposes.

  • Marketing uses the configurable content features for merchandising and creating advertising landing pages.
  • Operations and finance use the reporting and analytics features to do forecasting, tracking, and other business-y things.
  • The fulfillment and manufacturing groups use the order management system for inventory tracking and production scheduling.

With so many people around our office happily using the platform, we decided to build upon the foundation we already had in place.

We then analyzed our roadmap and scoured the plugin / app marketplace to see if there were any off the shelf solutions that met our needs. Ultimately, anything we found did not meet our internal “awesome bar” for product quality or was not something we trusted our customer’s data with.

For those problems we decided we did want to solve in-house, we agreed to find the right tools for any particular job and keep things as small and simple as possible. A service-based architecture fit this model and would allow teams to stay nimble and flexible as the business landscape changes. This has allowed us to innovate quickly and stay focused on new projects we’re really excited about (like increasing how often people give to one another with a new reminder service, coming soon!) without having to worry about disrupting core business or reinventing the shopping cart.

Designed With Love

Lovepop Consumer Technology Architecture

As we dug into architecting the next generation of our e-commerce storefront, we wanted to set some high-level goals as a team.

  • Keep it fast — our site already loads pretty fast, don’t bloat it and keep the experience great for all customers, especially those on mobile!
  • Keep it flexible — a lot of business units already use the CMS features, we can’t lose those!
  • Make it measurable — we are data driven and we want to measure the impact of any new functionality we add (or remove)!
  • Release it incrementally — we are still small and want to iterate fast; we want to solve problems that really matter to us around giving, not rewrite e-commerce staples!

With this set of constraints in mind, we set to work architecting a solution which could be downloaded independently of the server-generated content and layered on top of the static content to enable more advanced functionality. This technique, known as Progressive Enhancement, lets the page continue to work even if the asynchronously-loaded javascript app fails to load due to poor connectivity or other factors. This also let us keep all the SEO wins that come (basically) for free with our CMS.

Since we were keeping the static content, we wouldn’t be able to build a full single-page app or take advantage of advanced routing techniques. We needed a component-based architecture that could be created or destroyed incrementally depending on what A|B tests the user fell into.

Additionally, since there are no guarantees what tests any user will fall into, we needed a static data tier which could exist independently of any user-facing components. We also wanted that data tier to accurately represent the state of the user’s shopping session.

If you’ve been paying any attention to the front-end tech space over the past couple years, I’m sure you’ve guessed where this is going …

Pt. 2: Implementation — Show Me Some Code Already!

Isomorphic(ish) Redux

Starting with the data tier, we chose to use Redux since it can exist completely on its own and doesn’t necessarily tie us to a component library. While it of course integrates fantastically with React, having a standalone Redux tier also allows us to freely experiment with Vue, Glimmer, or whatever the framework du jour is (or even mix and match!) as we look to optimize our user experience down the road.

We were inspired by the modern isomorphic app trends to develop a way to rehydrate the server’s state for any page load. Shopify’s templating language (called Liquid, which looks a lot like Handlebars) allows us to access many of the server-side model objects directly, which are used when generating static content. It also has utilities to take those models and use them to generate JSON representations.

Putting all of the above together, we use Liquid to generate Javascript which uses the JSON representation of models as a variable assignment. We ended up with a bunch of Liquid snippets that could inject server state into global variables, which could then be used as the initial state in a Redux reducer. These snippets are included statically in page templates where that data is relevant.

Once these are included, we can then use the window-level data as initial state when creating our Redux store.

With this technique, we can seamlessly integrate our customer’s static Shopify cart and browsing state with that of our asynchronous application in a way that leaves the render tier completely agnostic.

Reactive Enhancement

For the view tier, React was a natural choice as it is by far the most mature of the component-based view-tier libraries on the market. As we all know, ReactDOM.render(Component, element) is used to mount a component instance at a specific DOM element, overwriting the contents of the element in the process. For most single-page apps, this is only done once per page load to mount the root component for the app, which then generates the DOM based on the current state.

For our uses, that won’t really work since the DOM is already being statically generated for us by the CMS and we really just want to enhance some specific pieces of it, not replace it completely. Using Redux allows us to create a single, static instance of the store and then mount multiple React roots, one for each micro-app we want to use to enhance the static content, which will all share a unified backing state model.

The key here is that the store is created once and is effectively a singleton that is used by both of the above component tree roots. This gives us a ton of flexibility to mount the minimal amount of dynamic content on top of what the CMS already provides, with the power to keep everything in sync and let the components communicate through updates to the store.

The Cake is NOT a Lie!

Being able to render multiple component roots onto a page is convenient for targeting specific sections of the DOM, but it can be tedious and inefficient to do if we have many small, purely visual updates to make. Fortunately React 16 added an awesome feature called Portals. According to the official doc:

“Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component.”

The important part is that portals allow you to affect a DOM node outside the current DOM hierarchy but from within the React hierarchy, allowing logical organization of components and data. The primary place portals are being used to date is for things like modals or rich hovercard or tooltip components — basically anything that needs to be absolute positioned over other content, but wants to know something about why it exists.

At Lovepop, we are using portals a little differently — as a mechanism for a React component to reach out and update statically-generated content which is nowhere near it in the DOM in a way that is stateful and much more controlled than raw jQuery events or direct DOM manipulation.

In the screenshot below, the cart quantity indicator is initially generated statically based on the user’s current cart size at the time the page is rendered, and the shopping cart is a dynamic React component. The cart has a child component for the quantity indicator which then gets rendered via a portal in place of the static one so that as the user adds or removes items from the cart, the count updates in real time.

Here’s what this looks like in practice.

Rigging to the Rescue

All of this is awesome and it has really enabled our team to build some cool stuff quickly and iteratively, but we felt we needed an orchestration layer to own the store and decide what components should show up on which page. In a nod to our co-founders’ nautical background, we named this library Rigging.

Rigging is a plain-old Javascript class whose initialization is deferred until after the rest of the page loads, allowing us to keep the page responsive while it boots up. When Rigging initializes, it creates the store and keeps a reference to dispatch (which will be important in a bit).

We then wrapped the individual React apps in a stateful abstraction called a RiggingApp which lets us understand the lifecycle of the micro apps from the outside world. It also allows us to drive the apps from a configuration file defining what apps should be mounted where. We can even mount multiple copies of the same app to different locations with different props, which is useful if say, the page layout is dramatically different on desktop vs. mobile, or you have multiple “Add to cart” buttons on a list page.

Since we’ve scoped a copy of the Rigging instance to the window, and Rigging has retained a reference to the store’s dispatch function, we can actually use this to let events from CMS-generated content dispatch actions into the store directly without having to build React components for them as well. This keeps our incremental feature development time super fast instead of having to rewrite everything before we can add any new functionality to it.

By letting React work as a pure functional view rendering library, Redux work its magic, and then layering this all on top of CMS generated content, we’ve been able to dramatically increase our revenue and customer engagement. Moreover, we’ve been able to do so in a fraction of the time it would have taken to rewrite the whole site in a modern framework or attempt to solve the same problems with just Liquid and jQuery alone. We now have sane state and data management, an abstraction that can grow with us, and even a path to unit testing!

blogPostWillUnmount()

Over the last decade, the web development community has gotten a lot of shiny new toys, which have come with a whole lot of complexity. Single page apps are super cool, but you occasionally have to jump through some pretty big hoops to get some of the basic essentials, like SEO. All of that takes time, which is the single most precious asset when trying to establish or grow your presence in the market. When we began this project, we evaluated a lot of options on how to bring our CMS into the modern era, and ultimately the path outlined above was able to get us where we wanted to be faster than we thought possible.

By thinking critically about where and how to invest our time and get the biggest bang-for-our-buck we’ve been able to move the needle meaningfully for our business while not over-committing to building technology that already exists in the world. We’ve been able to focus on what makes us us, and and ultimately that’s the most important thing a startup can do. We wanted to share our process as well as our results in the hope that we inspire other entrepreneurs to think creatively about bringing their ideas to market.

While we’re not ready to open source our solutions just yet, we are super excited to engage with the community on effective ways to deploy technology to solve critical-path business problems. Have questions or your own scaling war-stories to share? Reach out directly or we’ll see you in the comments!

(And of course, we’re hiring! Check out our jobs site or reach out at engjobs[at]lovepopcards.com)

--

--

Chris Puzzo
Lovepop Platforms

Consumer Tech Lead @Lovepop. Product developer. Team builder. JavaScript nerd.