How We Used Gatsby.js to Build a Blazing Fast E-Commerce Site
Flamingo has landed! Harry’s, Flamingo’s parent company, serves over one million female customers. We believe that women deserve high-quality, reasonably priced, and simple body care products too.
As an engineering team, we are committed to providing women with a body care experience tailored to their specific needs. It’s important to us that our customers enjoy their interactions with us not only while they’re in the shower, but also when they shop for their blades, wax kits, or razor handles online too.
To simplify shopping for razors we built a performant, visually-appealing, and secure site. Our architecture performs well across browsers and devices, emphasizes developer ergonomics and modern tools, and lets us continuously deploy with confidence. We think shopflamingo.com can be a model for how to architect other e-commerce sites.
As excited as we are about what we’ve built, we try to stay realistic. We figured that we might be the only ones this engaged. To our surprise, we found out that other people actually did want to know how we built our site!
So here we go! These are the tools we used to build shopflamingo.com, why it works as well as it does, and how you can build similar applications to provide your users with performant, accessible experiences.
Static Sites vs. Web Servers
The early internet consisted of static sites. Each page a visitor saw was represented by a different HTML page. Every user that visited a site saw the same content as everyone else.
A little later, web servers arrived that could produce HTML dynamically. The typical flow looked like this: a user makes a request that may or may not be shielded by a CDN. That request hits the web server which interacts with a database or APIs. Based on the data that the database or API returns, the web server builds HTML pages and serves them to the browser.
Historically, static sites came with less security risks and prevented the application from spending time per request generating each page, making them more performant.
On the flip side, static sites made it difficult to share code between files. Additionally, a developer usually needed to update content on a static site since it was written into the HTML. For both of these reasons, large static sites could become difficult and time-consuming to maintain compared to a web server.
Unlike static sites, web servers could make real-time decisions about what content to show or hide to a user, providing more sophisticated personalization with less effort. But, building a web page dynamically every time a user visits a page takes a lot of time.
In recent years static site generators like Gatsby.js, Next.js, and Nuxt.js have made it much easier to walk a middle-ground between the static websites of the early web and dynamic web servers. It’s now possible to reap the performance and security benefits of a static site, while also having the ability to share code easily and fetch content from a CMS.
How Did We Do It?
A Gatsby plugin called gatsby-source-contentful makes it straightforward to pull content and assets from Contentful into Gatsby. We query for that content using GraphQL.
We deploy and host our static files in S3, and serve a cached version of those files from Fastly, further reducing latency and thereby improving user experience.
We store all of our content in Contentful so that we don’t need to manage a database, server, or a custom CMS. We also have a webhook that triggers a build from our CMS to start a deployment.
We use Circle CI for continuous integration and to kick off our deployment flow. Circle CI runs Jest and Flow, runs the build and checks that it’s passing, deploys our code to a staging branch, and runs our Cypress tests. If we’re on the master branch, it rebuilds the site with our Production environment variables, and deploys to Amazon S3.
We host the static files built by Gatsby in Amazon S3.
Fastly is a content delivery network (CDN) that we use to cache the content that is in our S3 bucket so we can serve it to our users even more quickly. We configure Fastly to decide what content we want to cache and for how long, and use both gzip and Brotli to serve pre-compressed files.
Our Hosting Architecture
Let’s dive a bit deeper into how we’ve configured Fastly and S3 to deliver our content as quickly as possible to our users.
Imagine that we have a user in New York. That user makes a request to shopflamingo.com. Once the DNS record is returned, we make a request to Fastly. Because there’s a Fastly node in New York, the request will (in most cases) go there.
We ask the Fastly node if it has cached the most current version of the site. If the Fastly node has the current version cached, it will return it to the user. That process eliminates the need for the request chain to continue and reduces latency. If the Fastly node does not have a current version of the site cached, it will make a request and ask the Fastly shield node whether it has the current version cached.
If the Fastly shield node has the current version cached, it will return that to the user, updating the node in the user’s region on the way. If it doesn’t have a cached version, it has to ask the S3 bucket.
After a new deploy, the first user in a region experiences the most latency. Their request will go all of the way to the S3 bucket, since none of the Fastly nodes have a cached current version of the site. Once that initial request goes through, all of the other users in that area can access the site from the closest Fastly node, reducing latency for them. Also, the first user will access a cached version of the site from then on, until we deploy new version of the site.
We work hard to deliver existing content to our users as quickly as possible, but what about new content? The second major component to our architecture allows us to deploy quickly and easily multiple times a day.
We have automated every part of our deployment flow to prevent time-consuming manual steps and to provide steps that make us confident that we’re not deploying broken code to our users.
The process starts when someone triggers a build. To trigger a build, someone either pushes their code to Github or publishes a change to our CMS.
Circle CI Steps
Once we trigger a build, Circle CI runs through a number of steps:
- It runs Jest and checks to see if the Jest tests pass
- It runs Flow and makes sure all of our type-checking passes
- It runs the build and confirms the build passes
- It pushes changes to a branch
- It runs the Cypress tests
- (Optional Step) If we’re on master and all of the tests pass, Circle CI will rebuild the site with the production ENV variables.
If any of these steps fails, Circle CI will fail the build and the process will stop.
Once the build is green (on staging or master), Circle CI pushes the static files generated by Gatsby to S3, and the process described in the Hosting Architecture section begins.
The Whole System
Now that we’ve talked through the the two major components of the system, here’s a diagram of everything working together.
- A developer or product manager pushes to Github or publishes to our CMS
- Circle CI triggers a build
- Gatsby builds the site
- Gatsby fetches data from our CMS.
- Gatsby passes data to GraphQL
- GraphQL passes data back to Gatsby
- Gatsby builds the static pages
- Circle CI pushes the static pages to S3
- Fastly fetches and serves the static site
- A user accesses shopflamingo.com
We sought to build a highly-performant e-commerce site, and believe we achieved just that. We love working on this application, and hope that you will consider using it as a model when you build your next e-commerce site.
We love working with people who are curious about software and how things work, who work to understand technical problems deeply, and who stay current. If you’re interested in working with this type of architecture, check out our careers page!