eCommerce

Headless Shopify: Lessons Learned Building with Gatsby, Part 2

In Part 2, take the deep dive into the “how” of headless Shopify. We’ll go through our project plan, tech stack, build process, and top 10 learning lessons.

Headless Shopify: Lessons Learned Building with Gatsby, Part 2

Thomas Slade

Thomas, VP of Engineering at Elevar, has been a web developer for 16 years. The last 10 years have been focused on eCommerce and pushing the boundaries of the web.

Watch our webinar replay with Gatsby where we go through the entire stack and process

—-

If you haven’t read part 1 of our Headless Shopify experience, then you can do so here.

This is part 2 written by our fearless technical leader, Thomas Slade.

How it all began

Brad and I worked together for years previously at an agency. We launched approximately 50 Magento and Shopify sites between us, resulting in many late nights. Through these efforts,  we’ve always discussed the future of e-commerce, which has lead to us speaking about the latest browser vendor features.

Last year, I took on the daunting task of prototyping a headless Magento. I used ObjectionJS, which acted as a graphql wrapper around Magento’s EAV Database. After evaluating a few different frameworks, I chose Gatsby. Soon after, I had a working static e-commerce site. Of course, it wasn’t pretty, but it was blazing-fast. I sent it to Brad and he was immediately blown away by the speed and near-native feel of the site. You might be thinking, how can a static site power a dynamic shopping experience? What about tiered pricing, discounts, the cart, or a wishlist? Don’t worry, I’ll get to that later in this article.

Fast forward a few months: we started to pitch the idea of a statically generated site as part of a Shopify Plus replatform to one of our clients, StriVectin. Their site had performance issues and needed to be modernized.

I’m going to outline the following in this article:

  • Technical Goals of the Project
  • Decision-making Process for the Stack
  • Top 10 Lessons Learned

Technical goals of the project

The goals of the StriVectin project included many stakeholders, including the client, our engineering team, and on-site experts. We decided on the following three key technical goals for the project:

  1. Great UX (User Experience)
  2. Great DX (Developer Experience)
  3. Static Payloads
  4. Migrating away from Magento

These goals helped us choose our technology stack, development process, and deployment strategy.

Great UX

Speed and performance

Your site’s speed is correlated to its conversion rate. It’s important to have intent around every single byte delivered to the client. This includes, but is not limited to:

  • Onsite assets like images, HTML, CSS, JavaScript, and fonts
  • Offsite assets, which are usually marketing pixels and scripts

Perceived performance vs actual performance

Lighthouse provides a good baseline for performance, but it doesn’t browse the site. It instead measures the performance of a single request. There is an opportunity to capitalize on browsing behavior so we can predict what your users will do next.

Preloading links in the viewport is a modern technique for helping with perceived performance. Requests are preloaded in the background as the user scrolls and links come into the viewport. When a user decides to click a preloaded link, the content is immediately displayed. There is also another benefit to this approach. Why keep sending your users the same content over and over as they browse the website? For example, the header and footer should only ever load a single time when visiting a website. The user should only ever load new assets.

Clean design and UX

Is your design process segmented from you? Is there friction after the design process? The designers should align with your technical goals without sacrificing the user experience.

Great DX

Are you happy with the tools that you use daily?

Our favorite tools

  • React: Isolated, declarative components
  • TypeScript: Typesafe code, helpful editor autocomplete
  • GraphQL: Expressive data query language, intuitive relations, GraphiQL
  • Hot Reloading: Immediate feedback when developing

Static Payloads

Reduced server costs

Deploying static payloads allows us to put our code on a CDN (Content Delivery Network). This allows the code to be replicated across zones and get to your users faster. I’ve worked with clients paying $100k/month in server costs for “managed hosting”. A static site can be hosted on a CDN for free or for a few dollars a day.

Security

How do you eliminate a hacker from getting into your servers? Remove the server! Just like the good ol’ days of editing HTML with Dreamweaver and FTPing to GoDaddy. We wanted to deploy the entire website as a single package that included all static assets needed to run the site.

Energy

Why should a backend process a request and give the same response over and over again? Why should a client have to download the same response over and over? We wanted a solution that would make things much more energy-efficient for our user’s devices and for the CDN.

Migrating from Magento

End of life

Magento announced the end of support in 2020 for EE v1.X. The Strivectin codebase has been around for 7+ years. During that time, it passed through many hands, making it unstable and unmaintainable. Who wants to work on a codebase with 20,000 files?

Security

Magento-land has exposed us to many of the vulnerabilities that exist in the real world. Usually, site hacks come from one of the following: 

  • Not keeping security patches up-to-date
  • Not using web application firewalls
  • Not upgrading software
  • General lack of secure practices (like shared passwords)

Intuitive content modeling

The content management system (CMS) in Magento is very limited. After a few years, the CMS blocks and pages in Magento turn into a confusing mess of references. We didn’t want Strivectin’s content team to ever edit HTML again. We wanted the process of editing and publishing content to feel natural.

The Stack

 

Strivectin Tech Stack

Gatsby

Gatsby is a web framework that bundles so many great things. It is fast, provides a pleasant developer experience, and has a thriving community! The Gatsby team is obsessed with performance so that we don’t have to be. Out of the box, it provides:

  • Server-Side Rendering at build time with React Hydration
  • Link Preloading via Intersection Observer
  • Content Mesh with GraphQL
  • Image Optimization: Webp support, Sharp for resizing and optimizing
  • Lazy Loading
  • Hot Reloading

Gatsby also has a robust plugin ecosystem that contains source and transformer plugins. The source plugins integrate with third parties like Shopify or Prismic. The transformer plugins transform data of one type into another. These plugins allow for complex pipelines to create the “content mesh”.

Shopify for checkout and account management

We needed a managed backend to handle the transactional aspects of the site. This includes:

  • Offsite User Account Management
  • Offsite Checkouts

Shopify Plus’ Storefront API allowed us to handle these transactions.

Storybook

How many times have you had to browse a site and perform multitudes of actions to show a specific state? Storybook provides an interface for viewing components in isolation in many different states. A component is anything that can be extracted out of a design, like a Carousel or Product Card. 

The development team identifies these components, creates the code to power them, and adds stories. These stories are viewable by the team in the Storybook site. Once approved, the components in Storybook are used on the live site!

Storybook also has many add-ons that allow for even more user-friendly stories. 

We used Knobs so that we could toggle data that powers the component. We used Actions so that we could receive updates about state changes. For example, imagine a dropdown input. What if the dropdown is in an error state? What if the dropdown is in a disabled state? What would it look like if the label changed? How do we know when the select is changed?

Storybook knobs feature

 

We also used the Viewport add-on so that we could demo functionality in many different device viewports.

Storybook viewports feature

Breaking a page down into components

Implement components in storybook

Historically you would receive a report on a project’s progress via page-level delivery. The project plan will show something like:

  • Week 1: Homepage
  • Week 2: Product page
  • Week 3: Product listing page 
  • Etc.

This enables many things to fall through the cracks. For example, what data is powering those pages and how is it getting into the system? Are all the third parties included on the page? This results in a lengthy QA and UAT process since things only get 80% completed.

The progress is much more granular when using Storybook. Components that span pages can be reviewed in an isolated context (e.g.,  a Hero Banner used on the Homepage and Product Listing Pages). This allows for consistent component vocabulary and data definition up-front so that the development team can focus on the task at hand. We were halfway through the project before we started modeling any content!

Prismic

Prismic is an API first CMS. The admin interface allows for defining “types”. The “types” are a lot like a simplified version of a database table schema. These “types” are comprised of fields which may be text, rich text, slugs, color pickers, relationships, groups of fields, etc. A “document” is an instance of a type (e.g.,  my type is “Product” and my document is “Blue Dress”).

Gatsby has a source plugin for Prismic that makes all Prismic documents part of the “Content Mesh”.

Other useful features include:

Integration fields

Prismic offers an “Integration Field” that connects with Shopify. This allows for associating documents with Shopify products and basically eliminates the need for fetching data directly from Shopify.

At the time this article is being written, integration fields are currently in beta.

Slices

Slices are reusable content blocks. They allow for content groups to exist across different content types. Storybook and Prismic make a great match since your components define the data you need. After that, all you need to do is to define the schema in Prismic and hook the component up to the document.

Webhooks

Webhooks allow for triggering builds after content updates.

Query Prismic sources for prop data

Google Cloud

Our Elevar applications are hosted on Google Cloud, so we wanted to stay within the platform. We were able to defer this decision for quite a long time since we didn’t need a CDN immediately. We decided to use Firebase because of its simplicity and cloud function integration.

The more complex piece of the puzzle is the deployment strategy. We decided to use Google’s Cloud Build for building Gatsby. Our build process has many checks that prevents builds from shipping if they fail. Additionally, we introduced cloud functions for auditing the site with lighthouse and triggering builds.

Through the above efforts, we confidently ship code to production multiple times per day!

The Plan

Strivectin Project plan

Deployment Strategy

Strivectin deployment strategy

Top 10 Lessons Learned

Looking back on the project, there were a few areas where we made assumptions and encountered roadblocks. We would like to share those pain points with you so you can learn from what we encountered and make better decisions.

1. Shopify apps without a shopify theme

Most of the Shopify applications that interact with the frontend were not guaranteed to work with our project. This was primarily because many of the apps rely on Shopify’s theming engine or the App proxy. Since we weren’t using most of Shopify’s theme engine, this wouldn’t work.

Many App vendors have started to expose formal APIs, like Recharge, which we used for subscription products. Unfortunately, most Shopify apps rely on the theme.

If your client relies on many Shopify apps that use the theme, then it may not be worth implementing a headless Shopify build. It is still possible and encouraged but will require a rethink of the tooling to accomplish the business goal.

2. Third party + site speed

Third-party code is a critical aspect of any e-commerce site since it can make up the majority of the inbound conversions. Some of the third parties are not just pixels or event tracking but are also responsible for rendering content. The rendering is usually simple, but the libraries and boilerplate used are expensive and repeated for each third party. 

We decided to make a point to understand the cost of each and every marketing script that we added to the codebase. We removed all third party scripts to get a baseline in lighthouse with simulated fast 3G throttling and 4x CPU slowdown. Again, we were purely looking at performance. We then compared the baseline against the codebase with only the added marketing script. The results were stunning! They wouldn’t be as exaggerated on a slow site, but when you are using Gatsby, it shows when running lighthouse.

Adding many third-party scripts can result in a “death by a thousand paper-cuts” situation, where a site has jQuery loaded 5 different times. Many third parties pride themselves on the simplicity of the installation, but this comes at a cost to our end users. Instead of making a custom solution that is performant, they usually go with a solution that will work for most of their clients. Third parties will have to change in order to keep up with the performant, modern web. Shout out to Third Party Web for actively collecting this data!

Keep track of your client’s marketing scripts. Educate them on the actual costs associated with each third party. For example, what is the conversion boost associated with the third party? If it is +0.25%, but the slowness introduced by the third party brings the conversion rate down -0.5%, then the net conversion change is -0.25%. A/B test your third-party scripts to accurately get these numbers.

3. Prismic content validation

Prismic does not offer any validation out of the box. We chose not to have component-level data validation as part of the Gatsby build process. Because of this, our team was required to validate and prevent rendering of invalid content. This caused significant bloat in our code since every single slice and document needed to have pass/fail logic.

Discuss optional fields during the design phase of the project. Also discuss the validation of the components with your clients.

4. Prismic integration fields

Prismic’s integration field joins most of the Shopify data needed for rendering, but leaves out one critical piece of content for creating a checkout. The variant’s global UID is required to create a checkout via the Storefront API. We had to query Shopify in order to get this data, and have brought this up with the Prismic team to investigate.

5. Global product context vs. queries everywhere

Strivectin has a relatively small catalog. We didn’t want to have queries in every single place that a product was rendered (e.g., the wishlist or the cart). We also wanted autocomplete, search, and product filtering to be instantaneous and handled by the client entirely. The dream was to have a truly static site!

React’s context API allows data to be shared to child components without prop drilling. We utilized a global product context that stored minimal product details. Again, our client’s catalog size was small, which enabled us to use this approach.

If your client has a small catalog, this may make things significantly easier. If your client has a large catalog, there is a potential for performance issues due to the number of products correlating with the context size.

6. Shopify theme for non-Gatsby pages

We had to theme certain aspects of Shopify where transactional processes occur like Checkout and Account Management. 

It is possible to handle account creation, login, and checkout purely using the API, but this approach has two major downsides:

  1. If you are using some Shopify apps that rely on the Shopify storefront to show data (like Swell does), then it likely makes more sense to keep the account management side of things on the Shopify storefront.
  2. Adding account management (and maybe checkout) to the Gatsby side of things introduces personal information, such as names and addresses (and even card numbers if you decide to implement checkout completely custom), which we would rather leave to Shopify to handle and secure.

This forced us to recreate a simplified footer and header in Shopify.

If you rely on parts of the Shopify theme, you’ll need to adjust the theme to match the Gatsby site. Keep in mind header components like cart and wishlist count because you’ll need a cookie to keep these in sync.

7. Bleeding edge tech

Using new software is risky. Thankfully, our dependencies were very active. Part of our development process is keeping our dependencies up-to-date. Renovate bot is a tool that can create Github PRs with updated dependencies on a schedule. This kept our dependencies in-line, though we did run into a few issues. 

We enjoyed using the React hooks API exclusively, but toward the end of the project we came across a few hydration issues…thanks for fixing these, Joe!

Evaluate each and every dependency of your project. Older, inactive dependencies should not be used. Keep things up-to-date so that you have the latest bug fixes, features, and potential performance improvements.

8. Link resolution

Our SEO goals involved custom URLs. Prismic offers a UID field that fit our needs perfectly, but some of our content types were in subpaths (e.g., our blog articles). To accommodate, we had to create a link resolver for Prismic rendering and our component rendering.

We also used gatsby-plugin-catch-links to utilize Gatsby links when anchor elements were injected via Rich text fields.

Understand the SEO requirements of a project up-front. Some clients rely on long-tail urls. Prismic doesn’t allow for subpaths in the urls, so this will need to be programmatic.

9. Storybook and Gatsby Image

For components that had images in Storybook, we needed a way to load Gatsby images. Gatsby images are added via GraphQL fragments when used for real data, but we couldn’t use Gatby’s GraphQL content mesh in our Storybook files. We decided to create a custom Webpack loader for Storybook that allowed us to specify static image files with parameters for customizing, much like GraphQL.

If you are interested in using Gatsby image in Storybook, comment below!

10. No templates, more work (or less)

We started this project without any templates. We had to write search, autocomplete, and layered navigation from scratch, and couldn’t rely on out-of-the-box e-commerce behavior. This makes for a lot of work! For search and autocomplete, we utilized fuzzaldrin. Layered navigation is simple with a global product context.

But there is a benefit to starting from scratch. How many sites have we seen squished into a mold that doesn’t fit, only to end up using 10-15% of the features of the platform? This makes for a bad developer experience and ultimately slows down the site. Below are the normalized code stats from the Magento project vs the Gatsby site:

Magento files and lines of code comparison to Gatsby

Not all teams or clients are prepared for the amount of work that goes into starting from scratch.  It can be rewarding, but painful if a “missed requirement” or “wrong assumption” comes out toward the end of the project.

Conclusion

Our site base doesn’t suffer from code rot. Our code and features have intent and purpose!

This makes for easier maintenance and painless deployments. And as you can see below, we deploy to production many times per day:
Deployment log

As browser vendors continue to evolve, we’ll be able to keep up. Our front end is agile and decoupled from our content and transactions, as they should be.

The web is rapidly evolving. The historical way we’ve been accustomed to approach eCommerce builds and platforms continues to change. We’ve all seen this first hand with Shopify’s dominating growth for not only small businesses but enterprise direct to consumer brands that are migrating from monolith platforms. 

Will we use Gatsby, Prismic, and Shopify again?

Yes! Strivectin and Elevar teams are both delighted with the results. The launch was effortless.

Felicity Sissener, the CDO at Strivectin commented:

This has also been the smoothest cut-over of my entire career- your team and work are top notch.

I hope this guide and learning lessons was helpful for your next endeavor.

Interested in learning more about Headless? Let us know!



  1. Firstly, great article. I’m launching a similar solution with sanity for the cms. I have a question. How did you tackle analytics? In particular, the built in shopify ones? Do you trigger events when the product pages load etc to populate shopifys analytics? Or do you just use a third party to to analyise the ecommerce analytics?

    1. Thanks Ben! We use Google Analytics to track on-site behavior. Gatsby provides preloading out-of-the-box which requires a different approach when compared to the traditional page-view tracking.

      We took an “event-based” approach using GTM as our “source of truth”. GTM then pushes data to external services, like Google Analytics.

  2. This is great, we’re looking at moving from Magneto to Shopify at the moment.

    I do have one question. How do you deal with promotions e.g. discounts or special pricing?

    The issue for us is finding a reliable way to schedule price, discount and copy/banner changes when we start or end campaigns.

    1. Hi Dave,

      All discounts via coupon codes are handled in Shopify checkout. Special pricing is just handled via Shopify product price updates.

      All scheduled updates are handled via releases in Prismic. For scheduling pricing updates, you’d have to use a third-party Shopify app.

      Again, nothing is instantaneous. Our builds take ~5 minutes to complete.

  3. Great article, great use case. We’re assessing moving from Magento so it was good to hear from your perspective.

    You mention “it’s possible to handle checkout purely by API.” I was under the impression the checkouts must take place on Shopify’s server? We made an enquiry to them about this and that’s what they told us at least.

    Could you please clarify, is it just Shopify Plus you can access the checkout API??

    Thanks!

    Steve

    1. Steve,

      Thanks! “Purely” wasn’t a great choice of words, but you can basically handle most things as Shopify has added a lot more capabilities to the Storefront API. Things like:

      • Applying discount codes
      • Customer creation and activation
      • Customer checkout association
      • Adding shipping addresses
      • Applying Shipping rates
      • Storing CC in the vault and completing the checkout

      Checkout the guide:
      https://help.shopify.com/en/api/storefront-api/guides/checkout-guide

  4. Thank you very much for the great two articles! Very inspiring. You create an store your products in Prismic? Do you have create the products in Shopify also or just send the shopping cart via API on checkout?
    I assume that you create static pages for each products? How long does in build in Gatsby? I got problems creating > 1000 pages using Gatsby.
    Thank you very much. Regards.

    1. Walter,

      Thanks! We do store the products in Prismic and in Shopify. The products in Shopify are very basic without any meta-fields.
      Our builds take around 10min. We did update our build servers to increase the number of cores and speed up the builds.
      Gatsby does have issues with tons of pages(> 1000), but it is something they are actively working to improve.

  5. Great post.

    I’ve been experimenting using Shopify with Gatsby too.

    So far I have found the shopify DX outdated and slow and also the lack of custom fields that are easily editable by the client.

    Few questions

    > How did you handle dynamic content such as browsing a catalogue of products and such things as “Sort By” and “Filter By”?

    > Do you think this would scale with a larger inventory say 500-1k products?

    Thanks,
    Jack

    1. Jack,

      Thanks!

      So far I have found the shopify DX outdated and slow and also the lack of custom fields that are easily editable by the client.

      Yes, meta-fields are not an ideal solution for managing product data.

      How did you handle dynamic content such as browsing a catalogue of products and such things as “Sort By” and “Filter By”?

      Those actions are static and entirely handled by the client. The site only contains ~400 products so we are able to share a product context that stores very basic information that can be used to sort, filter, and search. If we had 1000s of products we would have a much more complicated build process where we would have to build pages for each of those permutations or host a separate graphql server so that we could query for those on-the-fly from the client.

      Do you think this would scale with a larger inventory say 500-1k products?

      I do, but each merchant has their own requirements. Large catalogs aren’t well suited for static builds. There isn’t a problem with storing 1000s JSON objects with a similar schema in the browser and using that context to power components.

      I think it becomes a problem with merchants that have 100k products. The build process gets much more complicated and lengthy.

  6. Hey,
    Thank you very much for this article, it’s nice to see people on the same boat! I’m currently building a new static ecomm using Shopify, ReCharge, Sanity and Nextjs.
    I just have a question, how do you handle the cart? I don’t see any request to Shopify when adding products to the cart, even for the subscriptions?
    Thank you, and congrats!

    1. We handle the cart via localStorage. We only create the cart/checkout/quote in Shopify when a user is ready to checkout. We didn’t want to deal with the rate limit and the potential for carts to get out of sync. If a user goes to checkout, comes back to the cart page and removes an item, we generate an entirely new checkout. There are no mutations.

  7. That’s well thought! And then I assume you create a specific ReCharge checkout for the subscriptions, as there’s no way to add subscriptions to the Shopify cart using the storefront API?
    But so what do you think about the Shopify ecosystem of third party services just to add basic ecomm features, coming from Magento where I think it’s either built-in or the plugins are completely integrated to the system, or at least you can make plugins by yourself? I feel like it’s rather unstable and might need a lot of maintenance

    1. Thomas,

      Thanks! Basically, we divide the checkout between users who have subscriptions (and potentially other products) vs regular products. We created a serverless function that creates the recharge checkout.

      The Shopify 3rd party ecosystem does not support this model very well primarily because vendors rely on the theme. I hope that they can introduce different ways to alter functionality without adding more ajax, assets, and unnecessary logic. For example, multiple versions of jQuery that all belong to different apps! This is a common problem across all ecomm platforms whether it be big commerce, Magento, Salesforce Commerce Cloud, etc. The theme is highly coupled to the backend + 3rd party ecosystem.

      This is also common with marketing solutions whether it be newsletters, reviews, a/b testing, personalization, etc. They all suffer from non-performant JS solutions that have to be generic enough to work on 95% of merchants’ sites via point-and-click installs. The world is slowly evolving towards proper APIs, but it will take some time. Google will likely push this initiative through privacy.

      Integration has been painful and there isn’t any way around it. We have chosen to implement some of these features ourselves and lean towards mature vendors that have proper APIs.

  8. I’m currently working on a very similar stack, and was wondering how you got gatsby-image working with Storybook. What did you add to your webpack config? Thanks in advance.

  9. Great review ! thanks for the post !
    I’ve been discovering the gatsby world thoses days…
    But how do we handle the user account/auth with shopify ? with a third lib like auth0 ?
    And how do you manage the specific user data, I mean for example : the bookmark product ? with a custom api ?

    Thanks

    1. Hi JL,

      We used the Shopify theme for account pages because of constraints with 3rd party applications that relied on embedding into the Shopify theme.

      We also build custom logic for favorites and cart that is stored in localStorage.

  10. Hey Thomas,

    I recently created my own ecommerce site with Gatsby and Shopify. obihats.com

    I read in the comments that your build time is as low as 5m. How did you manage to get it that low? Thats super impressive! I’m struggling with a 30m build for a site with 50 products. I’d like to get it as low as possible so I can use the netlify free plan for continuous deployment. Do you have any tips or tricks that could help me out. ^^’

    Thanks in advance!

  11. Hey Thomas,

    Thanks for the article. I’m looking at using Gatsby with Prismic and wondering if you are using previews? It seems that the gatsby-source-prismic-graphql plugin is incompatible with incremental builds and Prismic recommends using the gatsby-source-prismic plugin. Are you using either of these?

    Cheers

  12. Hi Thomas,

    Great article. I have developed many such websites for clients like AthleticGreens, Foursigmatic. And I learn that the process is not just complicated for the client to accept, but also time-consuming.

    And that’s how I started building a product imorph, which takes care of all these automatically. All you need to write is HTML and CSS.

  13. Hi, after few years from the post and with the new Shopify 2.0 would you still using prismic as a CMS or would you use Shopify directly?
    It seems to me that with the new json templating features and improved metatags it would be possible to achieve the same result by using Gatsby with Shopify directly.
    At the moment I’m not sure if it is possible to query a json template structure to render the page components

    1. Hi Frederico,

      Perhaps we could use Shopify directly now with the announcements from Unite! They’ve also announced a cart API which has been the primary pain point for integrating a headless setup. Now we’ll have to wait for an API rate limit that works for larger merchants!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may also like