{"id":31392860,"url":"https://github.com/formidablelabs/nextjs-sanity-fe","last_synced_at":"2025-09-29T04:49:22.849Z","repository":{"id":65380729,"uuid":"506362488","full_name":"FormidableLabs/nextjs-sanity-fe","owner":"FormidableLabs","description":"NextJS Demo site with Sanity CMS","archived":false,"fork":false,"pushed_at":"2025-01-10T18:18:20.000Z","size":39148,"stargazers_count":31,"open_issues_count":18,"forks_count":6,"subscribers_count":29,"default_branch":"main","last_synced_at":"2025-06-07T05:35:27.368Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://nextjs-sanity.formidable.dev/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FormidableLabs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-22T18:27:56.000Z","updated_at":"2025-03-20T13:21:22.000Z","dependencies_parsed_at":"2023-10-01T15:19:33.068Z","dependency_job_id":"ee5d7081-ef04-4f52-91ac-1c376e21761f","html_url":"https://github.com/FormidableLabs/nextjs-sanity-fe","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/FormidableLabs/nextjs-sanity-fe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fnextjs-sanity-fe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fnextjs-sanity-fe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fnextjs-sanity-fe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fnextjs-sanity-fe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FormidableLabs","download_url":"https://codeload.github.com/FormidableLabs/nextjs-sanity-fe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fnextjs-sanity-fe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":277467481,"owners_count":25822917,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-29T02:00:09.175Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-29T04:49:21.758Z","updated_at":"2025-09-29T04:49:22.839Z","avatar_url":"https://github.com/FormidableLabs.png","language":"TypeScript","readme":"[![E-Commerce – powered by Next.js + Sanity CMS + Fastly — Formidable, We build the modern web](https://raw.githubusercontent.com/FormidableLabs/nextjs-sanity-fe/main/nextjs-sanity-fe-Hero.png)](https://formidable.com/open-source/)\n\n# E-Commerce – powered by Next.js + Sanity CMS + Fastly\n\nThis repo contains a demo of a (partial) e-commerce site powered by [Next.js](https://nextjs.org/), [Sanity CMS](https://www.sanity.io/), and [Fastly](https://www.fastly.com/). 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.\n\nThe general architecture of the site is shown below:\n\n![Diagram of big-picture architecture](./docs/img/big-picture.png)\n\nThe 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.\n\nTherefore, the project can be broken down into the following three constituent parts.\n\n## The Headless CMS (powered by Sanity)\n\nSanity is used for storing information about our e-commerce products. The data from Sanity is fetched using Groq [Groq](https://www.sanity.io/docs/groq) – a query language, used for fetching data. Formidable built [Groqd](https://formidable.com/open-source/groqd/) – a schema-unaware, runtime and type-safe query builder for GROQ.\n\n### Sanity Studio\n\nSanity 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`](./packages/sanity/schemas). Sanity Studio is integrated into the NextJS application and deployed alongside as a route at `/studio`.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./docs/img/sanity-studio-sample.png\" alt=\"Sample of sanity studio\" /\u003e\n\u003c/p\u003e\n\nIf 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](./setup.md).\n\n## The Next.js app\n\nTo 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).\n\nThe CMS data is fetched on the server via GROQ using the standard `fetch` API. With a sprinkle of [TailwindCSS](https://tailwindcss.com/) styling we have something that looks like the following.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./docs/img/website-sample.png\" alt=\"Sample of the deployed website\" /\u003e\n\u003c/p\u003e\n\nThe Next.js app is deployed to [Vercel](https://vercel.com) 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.\n\n## Fastly CDN and Caching\n\nIn 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.\n\n![Caching Flow](./docs/img/caching-diagram.png)\n\nSee [caching details](#fastly-caching-details) for some technical details on how the caching is implemented.\n\nThe 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](#cache-invalidation-and-purging) for more details on cache invalidation.\n\n### Fastly Caching Details\n\nTo 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:\n\n1. `Surrogate-Control` response header needs to be added to pages where caching is desired ([reference](https://docs.fastly.com/en/guides/working-with-surrogate-keys)),\n2. `Surrogate-Key` response header needs to be added to enable appropriate cache invalidation ([reference](https://developer.fastly.com/reference/api/purging/)).\n\nOn 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).\n\n- `surrogate-control` – Fastly-specific header used to set the cache policies. (`max-age`, `stale-while-revalidate`, `stale-while-error`).\n- `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`](https://developer.fastly.com/reference/http/http-headers/Fastly-Debug/) header in your request.\n- `cache-control` – used to indicate to browsers and Vercel to not cache so that we can handle caching solely at the Fastly layer.\n\nWith these response headers implemented, Fastly will start caching our responses and give us a path to invalidate our cache when necessary.\n\nIn our case, we use data items' `slug`s 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.\n\n### Cache Invalidation and Purging\n\nWe need to invalidate our Fastly cache whenever data in our Sanity CMS instance changes. To do this, we use [Sanity webhooks](https://www.sanity.io/docs/webhooks) to trigger purging whenever our CMS data changes. The general flow for this is shown below.\n\n![Process Diagram](./docs/img/purging-diagram.png)\n\nWhen 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`](https://www.sanity.io/docs/slug-type)) 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).\n\n\u003c!-- TODO: Diagram for this flow, too... --\u003e\n\n#### Purging on code deploy\n\nWe 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`](./.github/workflows/purgeFastly.yml).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformidablelabs%2Fnextjs-sanity-fe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fformidablelabs%2Fnextjs-sanity-fe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fformidablelabs%2Fnextjs-sanity-fe/lists"}