Code Monkey home page Code Monkey logo

nextjs-sanity-fe's Introduction

E-Commerce – powered by Next.js + Sanity CMS + Fastly — Formidable, We build the modern web

E-Commerce – powered by Next.js + Sanity CMS + Fastly

This repo contains a demo of a (partial) e-commerce site powered by Next.js, Sanity CMS, and Fastly. The goal of the project is to provide a realistic demonstration of running a highly performant and available e-commerce site with data sourced from Sanity's headless CMS.

The general architecture of the site is shown below:

Diagram of big-picture architecture

The e-commerce data is stored in a headless CMS (powered by Sanity). The project uses Next.js (deployed on Vercel) to render the site, and Fastly is placed in front of Vercel to cache server-rendered webpages for speed and availability.

Therefore, the project can be broken down into the following three constituent parts.

The Headless CMS (powered by Sanity)

Sanity is used for storing information about our e-commerce products. The data from Sanity is fetched using Groq Groq – a query language, used for fetching data. Formidable built Groqd – a schema-unaware, runtime and type-safe query builder for GROQ.

Sanity Studio

Sanity Studio is a web interface for Sanity's headless CMS. It is used for creating and editing the data on the site. The models for Sanity are created in code and tracked in source control. The models can be found at packages/nextjs/sanity-studio/schemas. Sanity Studio is integrated into the NextJS application and deployed alongside as a route at /studio.

Sample of sanity studio

If you want to poke around the Studio site, you will need to go through the steps of creating your own Sanity account and project. Instructions for that can be found in the setup guide.

The Next.js app

To show the CMS data to end-users we created a Next.js web app that server-renders some common e-commerce pages, including a landing page, a Product Listing Page (PLP) with sorting and filtering, and a Product Details Page (PDP).

The CMS data is fetched on the server via GROQ using the standard fetch API. With a sprinkle of TailwindCSS styling we have something that looks like the following.

Sample of the deployed website

The Next.js app is deployed to Vercel via their git pipeline. In a real-world e-commerce app, we expect to experience some heavy loads on pages whose data doesn't change much between visits, and therefore we can deploy caching strategies to reduce the load on our source server.

Fastly CDN and Caching

In order to enhance the speed of the app, we are utilizing Fastly's CDN with a high cache-lifetime for server-rendered pages. We are using Fastly to both cache and host the subdomain used for this showcase app. The data flow involved in caching is illustrated below.

Caching Flow

See caching details for some technical details on how the caching is implemented.

The caveat to this approach of aggressive caching is that it is important to invalidate the cache when our source data changes. Otherwise, we will be showing stale data to end-users, even if that data has been updated in the CMS. See cache invalidation and purging for more details on cache invalidation.

Fastly Caching Details

To cache our server-rendered pages at the Fastly layer, we use response headers to indicate what/how we want Fastly to cache our responses from the source server. We need to a couple key ingredients:

  1. Surrogate-Control response header needs to be added to pages where caching is desired (reference),
  2. Surrogate-Key response header needs to be added to enable appropriate cache invalidation (reference).

On the Next.js side we'll need to include a few primary response headers to then control caching (in our case, we're setting these headers from middleware on server-rendered pages that we'd like to cache).

  • surrogate-control – Fastly-specific header used to set the cache policies. (max-age, stale-while-revalidate, stale-while-error).
  • surrogate-key – Fastly-specific header that allows purging by key. Note: this header is removed by Fastly before sending the response to the client. To see the value of this header, you must include the Fastly-Debug header in your request.
  • cache-control – used to indicate to browsers and Vercel to not cache so that we can handle caching solely at the Fastly layer.

With these response headers implemented, Fastly will start caching our responses and give us a path to invalidate our cache when necessary.

In our case, we use data items' slugs as part of our surrogate-key header to indicate what items' data are used to render a page so that we can invalidate accordingly when any of those items' data changes.

Cache Invalidation and Purging

We need to invalidate our Fastly cache whenever data in our Sanity CMS instance changes. To do this, we use Sanity webhooks to trigger purging whenever our CMS data changes. The general flow for this is shown below.

Process Diagram

When CMS data changes, a Sanity webhook is triggered and makes a request to an API endpoint in our Next.js app. The endpoint does some validation on the request (to make sure it's coming from a trusted Sanity webhook), and then makes a request to Fastly's API to invalidate/purge our cache accordingly. The Sanity webhook payload contains information (in our case, an item's slug) about what data changes, and our API endpoint uses that slug to tell Fastly which cache data to invalidate (based on the surrogate-key set in the original response header).

Purging on code deploy

We discussed invalidating our cache when our source data changes. However, our source code can also alter our HTML response, and our Fastly cache is not aware of when we deploy code changes. Therefore, we also need to purge our entire cache when we deploy code. We do this via a GitHub Action that runs in CI; you can find the action details in .github/workflows/purgeFastly.

nextjs-sanity-fe's People

Contributors

alesvoy avatar burnett2k avatar carloskelly13 avatar cpresler avatar gksander avatar luisabarca avatar nlkluth avatar sarmeyer avatar scottqmn avatar scottrippey avatar umxr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nextjs-sanity-fe's Issues

Sanity + NextJs Blog Page

What

Add a blog page on Sanity and display it on NextJs Site. The content can be just lorem ipsum

Other Requirements

  • Add a button to upload word doc and using Sanity Client create a new content for the blog (Needed for Demo)
  • Show publisher info if necessary
  • Create an API on nextjs to upload content to Sanity

Add Search for Product functionality

What

Add ability to search for product title, or any keyword match in the description in a typeahead search. This search will be performed via Sanity API

Demonstrate multiple facets on product pages

In order to make the showcase more presentable as an eCommerce app, we need to show that the CMS has the capability to track more than one facet/attribute for each product. Exact requirements to be determined once we settle on a coherent product set.

Enforce filter-specific sorting rules for filter options

When a user views the list of options for filtering by size, the sort appears to be random:

Image

For some options like shoe size, we can get away with filtering the options numerically. However, t-shirt size has a bespoke sort that all users expect: XS > SM > MD > LG > XL . The CMS should have this capability, and all filters for our final product set should be sorted in a way that users expect. This could possibly be an admin capability if it isn't already?

New font loading causing slower performance

Ran a lighthouse audit on the site and saw this:
image

It appears the introduction of the new font was done in a way that introduces a bottleneck before DOM rendering can be completed. It's the second thing to load when looking at the network tab, and it then causes subsequent loads later on of the actual woff2 files

image

image

https://web.dev/render-blocking-resources/?utm_source=lighthouse&utm_medium=devtools

Ideally, we would mark this script as async or defer, so that it doesn't block rendering of the page. Or, self-host the font in the _next/static folder so it's faster to retrieve.

I don't fully understand the usage of this script / api, so I could be wrong, but it should probably be looked into at some point if Lighthouse is flagging it.

Use Fastly as CDN/cache layer

What?

Use Fastly as a CDN/cache layer in front of our Vercel deployment. This will likely require some investigation. For more details, reach out to @gksander and he can (try) to get your more information on the ins and outs!

add Fastly VCL to disable vercel caching

At this point, we only want CDN caching at the Fastly level, and not the Fastly level. To ensure we aren't caching anything on the Vercel level, we should add some VCL to instruct Vercel to not cache when Fastly routes requests to the Vercel origin.

The VCL should do one of two things:

  • Add a querstring parameter of _vercel_no_cache=1 OR
  • Add a cookie with key _vercel_no_cache and a value of 1

When either of those methods are used, you should be able to see in the response headers that the Vercel cache response should say "PASS" instead of "MISS".

documentation updates

docs could use more detail in some areas, and be updated where they are now incorrect.

Also, want to add as much fastly and infra debugging as possible to the readme so other people can pick it up and take it from there.

Cache invalidation via Sanity webhooks

What?

Once Fastly is in place (#10) as a cache layer, we should use Sanity's webhooks to invalidate particular cache keys when data changes. This will likely require coordination with the Fastly caching setup, as we will need to coordinate cache keys between serving pages and invalidating the cache.

Only show published documents on the FE

Why?

Currently if there is a draft version of existing product than it shows as duplicate and if a product is WIP in draft it still shows up on the site. We only want to show products that are not in draft state.

PLP filters/sorting

Filter by:

  • size
  • on sale (price !== msrp)

Sort by:

  • name (alphabetical)
  • price

Deep link with query params

Handle blog caching and cache purging

Currently, we have limited support for caching products and categories, but nothing in place for blogs which were recently added.

Add cache response headers for blogs and any associated cache invalidation code required.

We typically use the _type and slug.current for surrogate keys.

Hide Blog Navigation

What?

For now we would hide the navigation to the blogs as we do not have bandwidth to make design updates to the blog. We will revisit this in the future.

[Spike] Page transitions

When users navigate between pages in the app, they do not encounter any loading indicators from the app or from the browser. This can lead to an experience where they try to hit a page they do not have cached, and nothing appears to happen for an awkward amount of time. This ticket is a research spike to identify our technical options for page transitions within the app.

Warning with sort dropdown

What?

Getting a warning

Warning: Use the `defaultValue` or `value` props on <select> instead of setting `selected` on <option>.

The code is located here

Fix

Use defaultValue prop set to query param value or the default sort value

Create more specific surrogate keys

Currently, we're using slug.current as our surrogate key which is working great. However, slugs are only unique for each _type, so there's potential for cache conflicts here.

Here's a sample scenario to better explain:
In Sanity, you could create a product with a slug of blank-tshirt-1. If you tried to create another product _type with that slug, it would fail. However, if you then tried to create a category with a slug of blank-tshirt-1, that would be considered valid since it is unique for that type.
Since we use slug for caching and purging, there's an unlikely potential for conflict here where we might incorrectly purge or mis-associate a page with the wrong slug.

To fix this, we should be able to simply append or prepend the _type to the surrogate key. So, blank-tshirt-1, becomes product_blank-tshirt-1 or something like that.

The webhook logic will also need to be modified to handle the new key structure.

Keep in mind there are limits on the length of the Surrogate-Key header, so we should try and be parsimonious where possible. It's unlikely we will run into any limits, though, since we are dealing with sample data.

Migrate Sanity studio and NextJS into shared, pnpm-workspaces repository

Move Sanity and NextJS frontend into the same repository, using pnpm workspaces to manage relationship/dependencies between each.

Ideally, we'd have a package script like dev or start that spins up both Sanity and the NextJS frontend to make this project as easy as possible to develop with.

RuntimeError: SSR / Client rendering problem

The following error occurs when loading and then refreshing the page at localhost:3000

image

Error: Hydration failed because the initial UI does not match what was rendered on the server.

It's also happening on the vercel app, but since it's a production build, the error overlay is not present. However, the error can be found in the logs.

This appears to be due to some kind of HTML mismatch between the SSR and Client side pages.

PLP Dynamic filter groups

  1. The groups and the values are dynamic, maybe a Sanity Schema called filters? (I don't think this makes a lot of sense)
  2. We keep the filter group in the code the content dynamic, instead of having options hard coded, can we fetch it from Sanity?

create diagrams

it would be nice to have a diagram which illustrates the following:

  1. overall architecture

  2. flow diagram of caching / purging

This will likely be necessary if we want to do a blog post or do a presentation on this repo. For now, they can simply go into the readme, but can be repurposed later on for other things.

Home Page Design Improvements

Ideas

Instead of home page just being a collection of categories it might be better suited for something better

  • Show Top 3 categories (Can just be last updated Categories)
  • Show Top 3 products (Can just be last updated products)
  • Add an image slideshow of products (5 maybe)
  • Add Categories to the Top Header
  • Make the Cards for categories and products bigger

Feel free to add more ideas to make it different and catchy

store webhook configuration as code

Currently, our webhook for purging is created manually within the sanity project. This is risky because if it gets deleted, we will lose all of that configuration.

This issue is to see if there's a way in the CLI or using some other mechanism like IaC to allow us to:

  1. store the webhook configuration in git
  2. create the webhook in CI

I've already ran into a few issues where I forgot to update the webhook. It would be more top of mind if it could be found in the code base.

Cart UI: List Cart

  • a "cart" page that lists out cart items, and allows user to remove/update items.

cart cookie saved as undefined when env var isn't set

currently, if you don't have the NEXT_PUBLIC_CART_COOKIE_NAME value set up in your .env, or set up in vercel, the cart will be stored in a cookie with the value of undefined. This is not great. I think having a sane fallback is a great way to go here to avoid confusion. Alternatively, we could just hard code it to something specific and remove the environment variable. I cannot think of any good reasons why the cart name would need to be stored in an environment variable.

Cart UI: PDP Add to Cart

What?

Create a (rough) cart UI by adding:

  • an item-count indicator to the header;
  • an "Add to cart" button in the PDP UI;

SEO updates

SEO updates to support the showcase app. Initial ideas include:

  • Titles added to pages
  • SEO keywords
  • Descriptions mention Formidable
  • URL structure

increase cache lifetime

Currently, all the cache lifetimes are set to 10 minutes. This is nice, but that means:

  • most times people open the page they are getting a copy served from origin rather than cached
  • slower response times occur because of the origin / cold start hit
  • most all performance metrics will be slower

I suggest once we further test caching and purging, we increase the cache lifetime to 24 hours or so. That way, when the site is loaded it's much more likely to be cached content. Heck, we could even increase the cache to 100 days or something like that if we're confident in how the purging works.

Usage analytics

We want to track usage analytics on the showcase eCommerce app so that we know how many users are using the app, as well as some basic information about their sessions. More detailed requirements TBD.

Implement mini design system

Figma link available upon request. Components include

  • Typography
  • Button
  • Link/Button Link
  • Pill
  • Input
  • Checkbox
  • Colors

cart flashes when there are items in it

The cart was recently added and is shown on most pages. Because the cart is retrieved from a client side cookie, that means the SSR cached page will also cache the value of 0. Since we are SSR caching, the cart with a value of 0 is being cached. This is ok until a user has added some items to their cart. When this happens, you will see a flash on the screen b/w when the cached page is loaded, and a new value for the cart is retrieved. In my case, it jumps from 0 to 3 when I refresh the page.

This is pretty low priority, but it does bring up an interesting use case. What's the best way to handle this? A few ideas:

  • Instead of showing a value of 0. Maybe start with a loading icon which lasts for a couple of seconds. The icon would get cached and would give a more consistent experience since it wouldn't show a numeric value rapidly changing w/o user interaction.
  • Don't show numbers for the cart. This isn't a very good solution from a business perspective, but since this is a demo it probably doesn't matter

Purge Fastly cache when a vercel deploy occurs

Recently, some styling changes were made to the app that were not reflected in the Fastly cached SSR pages.

When a code deployment happens, major functionality and styling should be up-to-date, so we should purge all caches when a deploy happens. This does mean, we will lose out on some cache hits, but since we don't deploy that often it probably doesn't matter that much.

Steps to reproduce:

  1. Load the Fastly page, so that it's cached
  2. Modify styling and merge / create a PR
  3. Visit the vercel page and note that the styles are up-to-date
  4. Visit the Fastly page and note that the SSR pages are still showing the old stying, but if you click around the client navigation will then pick up the correct styling.

Requirement: initiate a purge all for the entire site when a vercel deploy happens. Consider adding a fastly snippet to "authenticate" the request with a simple header secret or something of that nature.

Bug: filters show invalid values

Now that shoes have been added, it looks like filtering is a bit wonky now. The below screenshot is a good example:
image
We should probably not show shoe and t-shirt sizes on a page where only t-shirt sizes are application. Same goes for shoe pages (they shouldn't show t-shirt sizes).

PDP Design updates

As part of overhauling the PDP with Patty's new designs, we will need to implement a panel for a "recommended" product set. This does not need to be a smart recommendation -- we could get away with pulling from products that have been manually tagged in Sanity, or the most recently edited products, or any other way of highlighting a discrete set of products.

Cart: Simulate Cart API

What?

Simulate a Cart experience by setting up NextJS API endpoints to CRUD a "cart". To keep things simple, just store cart information in an http-only cookie – so the backend is required to retrieve/edit the cart information, but an actual database is not required (to start).

Sanity CDN Images + NextJS Image component

Problem

We are currently not utilizing NextJs Image component with Sanity CDN as the current version of NextJs Image component requires a width + height or layout=fill this does not work well with tailwind styling.

Ideas

  1. Next 12.2 introduces an experimental image component which does not have the above constraints which might better for our use case.
  2. Using Cloudinary for images instead of Sanity

Convert from SSG to SSR

What?

We will use Fastly in front of Vercel as a CDN/caching layer (see #10). Therefore, we will convert from Static Site Generation (SSG) to Server Side Render (SSR) with caching. Eventually, we'll use cache invalidation when data changes – but for this ticket, we should focus on converting from SSG to SSR.

How?

In the NextJS page files, instead of using getStaticProps, we should use getServerSideProps so that our page data is fetched each time the page is fetched from Vercel.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.