Separate your product catalog from commerce.

Tutorials

How to build an international ecommerce website with Sanity and Commerce Layer.

April 6, 2022 Bolaji Ayodeji

One of the greatest benefits of composable, headless commerce is the flexibility it introduces to the developer experience of building shopping experiences. Decoupling website content and commerce data makes it easier for content and commerce teams of experts to work independently and more efficiently. With Commerce Layer, content managers can work with a best-of-breed headless CMS like Sanity, merchants can build their inventory in Commerce Layer, and developers can build with any stack in their most preferred programming language while utilizing Commerce Layer’s APIs.

In this tutorial, you will learn how we built the Commerce Layer Template with Next.js, Sanity studio, and deployed it to Netlify. At the end of the tutorial, you should be able to set up and build your own 1-click Sanity starter with ease or integrate with Commerce Layer.


Prerequisites

  • Git installed (learn how to install Git here if you haven't already).
  • Node and NPM installed (Learn how to install Node.js here if you haven't already).
  • Basic knowledge of how to use the terminal.
  • Basic knowledge of NPM.
  • A grin on your face ;)

Introducing Sanity Studio

The Sanity studio is an open-source content management system built with React.js. It offers rapid configuration, free form customization, reusable structured content, a comfortable editor, real-time collaboration, toolkits, plugins, and more features to enable you to create the best content workflow.

Sanity provides the possibility to create starter templates that can be re-used by developers easily. The starter is primarily a repository hosted on GitHub that contains some meta-information, demo content, schema, and frontend(s) that will end up in a new repository when a developer installs the starter through sanity.io/starters. When a developer installs the starter, Sanity creates a new project on Sanity and a new repository on GitHub with the starter code, attaches a new Sanity dataset ID to the starter, and deploys the project to Netlify simultaneously.

Generally, a sanity starter can include a Sanity studio, a frontend application, both or multiple frontends and studios. For the purpose of this tutorial, we will create a starter that will include a studio and frontend. Our starter will include:

Sanity starter project structure

Sanity has a defined specification for starters which includes some required files and directories. These specifications provide information about the starter to developers using the starter and make the project function as a reusable starter. Below is the folder structure of a Sanity project with required files and directories (without any frontend added):

├── .sanity-template
├── .sanity-template/assets
├── .sanity-template/data
├── .sanity-template/manifest.json
├── README.md
  • .sanity-template is the root directory where all meta-information for using this repository as a template on sanity.io/starters is stored.
  • .sanity-template/assets is the directory for storing assets related to displaying information about the starter (in this case, preview images for the overall project and for each site the starter contains).
  • .sanity-template/data is the directory to store a Sanity dataset export if you want the starter to launch with some demo content.
  • .sanity-template/manifest.json is the JSON file containing details about the Starter as well as deployment information.
  • README.md is the markdown file for this project that will be displayed on the Create page.

For a finished starter project, the root of the project should contain all deployable code, including the frontend and studio. Generally, a project spun from a starter is split into three parts:

  1. The root for all frontend code
  2. The /studio directory for all studio code.
  3. The .sanity-template for all starter meta-information.

Here is a sample from the Commerce Layer sanity template as seen in the image below:

How we built the Commerce Layer Sanity starter

In this section, you will learn how we built a starter with an ecommerce application with transactional functionalities powered by Commerce Layer APIs, structured content on Sanity, imported seed data, and deployment configuration to Netlify. If you want to follow along with the guide, you can take a look at the finished project on GitHub.

Please find below the sequential breakdown of all the steps taken to develop our starter.

1. Setup a new Sanity project using the Sanity CLI

Sanity has a command-line interface that we can use to interact with Sanity, create new projects, manage datasets, import data, and much more from the CLI. We'll use this CLI to set up a new sanity project following the steps below:

1. Install the CLI

Run the command below to install the Sanity CLI:

npm install -g @sanity/cli
2. Create a new project

Run the command below to bootstrap a new project which will log you into Sanity, create a new project, set up a dataset, and generate the files needed to run the studio environment locally:

sanity init
3. Run the studio

Run the command below to build the initial JavaScript code required to run the studio, and start a local web server:

sanity start

The studio should now run on localhost:3333. You can always run sanity help to get an overview of other available and useful commands in the Sanity CLI.

2. Content modelling for the created Sanity studio

Now that we understand how Sanity works and have set up a new sanity project, let's structure our sanity studio schema. Sanity schema defines how your content should be modelled, and this structure reflects in the studio UI. The schema describes the different field types a document consists of. Sanity uses the schema.js file in the /schemas directory to determine the content model of the project.

With Sanity, you define a block of content as a document or split your documents into modules and import them into the parent schema.js file. Generally, there are three categories of Sanity schema types:

You can find all of the Sanity’s types in this reference documentation or learn how to structure your content model based on your needs by reading this comprehensive guide.

For the Commerce Layer starter, our schema.js looks like so in the snippet below with imports of several other module documents. You can view the schema code for each module here in the GitHub repository.

import createSchema from 'part:@sanity/base/schema-creator'
import schemaTypes from 'all:part:@sanity/base/schema-type'

// We import object and document schemas
import product from './product'
import country from './country'
import variant from './variant'
import size from './size'
import taxon from './taxon'
import taxonomy from './taxonomy'
import catalog from './catalog'
import blockContent from './blockContent'

import productImage from './productImage'
import localeString from './locale/String'
import localeText from './locale/Text'
import localeSlug from './locale/Slug'
import localeBlockContent from './locale/BlockContent'

// Then we give our schema to the builder and provide the result to Sanity
export default createSchema({
  // We name our schema
  name: 'default',
  // Then proceed to concatenate our document type
  // to the ones provided by any plugins that are installed
  types: schemaTypes.concat([
    // The following are document types which will appear
    // in the studio.
    product,
    country,
    variant,
    size,
    taxon,
    taxonomy,
    catalog,
    // When added to this list, object types can be used as
    // { type: "typename" } in other document schemas
    productImage,
    blockContent,
    localeString,
    localeText,
    localeSlug,
    localeBlockContent,
  ]),
})

3. Add content to Sanity studio

If you are working on a new project like we did when we began developing the starter, you will have to manually add content to your project using the Sanity studio running on localhost:3333. The studio should now have the content fields populated with the configured content schemas in the “Desk” view. You can use that to add content to your project, as seen in the screenshot below.

If you are starting a new project using a starter or a previously saved project, then you can easily import a dataset with saved data. To do that:

  • Extract the production.tar.gz file in /.sanity-template/data directory using the command below:
tar -xf production.tar.gz

The extracted folder name should look like production-export-2021-02-26t14-15-56-557z.

  • Run the command below in /studio to import the data.ndjson file in the extracted folder:
sanity dataset import ../.sanity-template/data/<name of extracted folder>/data.ndjson <your_dataset>

You should check the running Sanity studio now to preview the imported content.

4. Add frontend and integrate with Sanity

Before you add all frontend code to the root directory, you should move the Sanity studio code into a directory named /studio.

At this stage, you will add the frontend code of your project, which can either be a blog, marketing website, CRM, or storefront. The major thing to do here is to use any of the Sanity client libraries to integrate Sanity into your frontend and fetch data. In our case, we used the official Javascript client that works in Node.js and modern browsers.

import sanityClient from '@sanity/client'

const client = sanityClient({
  projectId: process.env.SANITY_PROJECT_ID as string,
  dataset: process.env.SANITY_DATASET as string,
  useCdn: process.env.NODE_ENV === 'production', // `false` if you want to ensure fresh data
})

Here’s an example of how we query Sanity to fetch the country and product data:

import _ from 'lodash'
import {
  SanityCountry,
  SanityProduct
} from './typings'

//Countries
const sanityAllCountries = async (locale = 'en-US') => {
  const lang = parseLocale(locale, '_', '-', 'lowercase')
  const query = `*[_type == "country"]{
    name,
    code,
    marketId,
    defaultLocale,
    "image": {
      "url": image.asset->url
    },
    'catalog': {
      'id': catalog->_id
    }
  } | order(name["${lang}"] asc)`
  const countries = await client.fetch<SanityCountry[]>(query)
  return countries.map((country) => {
    const localization = {
      name: country?.name[lang],
    }
    return { ...country, ...localization }
  })
}

//Products
const sanityGetProduct = async (slug: string, locale = 'en-US') => {
  const lang = parseLocale(locale, '_', '-', 'lowercase')
  const query = `*[_type == "product" && slug["${lang}"].current == "${slug}"]{
    name,
    description,
    reference,
    slug,
    'images': images[]->{
      'url': images.asset->url
    },
    'variants': variants[]->{
      label,
      code,
      name,
      size->,
      'images': images[]->{
        'url': images.asset->url
      }
    }    
  }`
  const item: any[] = await client.fetch(query)
  return parsingProduct(_.first(item), lang)
}

You can explore all our queries for the Commerce Layer starter project here in the GitHub repository. Also, here’s the major code powering our frontend alongside some hooks, utils, components, and dependencies.

Now that you have a Sanity Starter set up, we’ll show you the foundation principles needed to integrate commerce data from Commerce Layer. This is where you will begin to see the powerful combination of Commerce Layer data with Sanity content. And by the end of the tutorial, you will see not only the benefits of this integration, but you will also be able to experiment with your commerce data next to Sanity to see the power of both tools together.

5. Get Commerce Layer API credentials

In the starter we built, transactional functionalities of your ecommerce platform are managed by Commerce Layer, while the content is managed by Sanity studio. This will result in better order management and content management. To get started with using Commerce Layer, you will need to create an organization, perform some configurations and settings for your business, seed some demo data, and get your API credentials. The API credentials will allow you to interact with Commerce Layer in your presentation layer (frontend) and the CLI. To get the required credentials, kindly follow the steps below:

  1. Create a free developer account here. Iif you already have an account, skip to step 3.
  2. Upon successful sign-up, skip the onboarding tutorial for the purposes of this article (we'll set up and seed the organization manually through the CLI shortly).
  3. Create a new organization for your business.
  4. In the Commerce Layer dashboard, click on the Sales channels tab and create an application, with the name: Website. Upon successful creation, you'll get a client ID , a base endpoint, and one or more allowed scopes. Kindly remember to save that as we'll use it to interact with our application later.
  5. In the Commerce Layer dashboard, click on the Integrations tab and create an application, with the name: CLI and role: Admin. Upon successful creation, you'll get a client ID, a client secret, and a base endpoint. Kindly remember to save that as we'll use it to interact with the CLI later.

6. Seed organization with test data

The Commerce Layer CLI helps you to manage your Commerce Layer applications right from the terminal. Installing the CLI provides access to the commercelayer command. To set it up:

  • Install the CLI using your favorite package manager:
//npm
npm install -g @commercelayer/cli

//yarn
yarn global add @commercelayer/cli
  • Log into your application via the CLI using the previously created integration application credentials like so:
commercelayer applications:login -o <organizationSlug> -i <clientId> -s <clientSecret> -a <applicationAlias>

Now, with the steps below, you can use the CLI to import three demo markets (UK, USA, and Europe), a set of product SKUs, related price lists, related pricesstock locations, and inventory  into your organization using the multi-market business model.

commercelayer plugins:install seeder
  • Seed your organization using the command below:
commercelayer seed -b multi_market

7. Final checklists and Netlify deployment configuration

  • In order for a starter to be validated and used through sanity.io/starters, it needs to follow the project name must start with sanity-template-.
  • Configure your Sanity metadata in sanity-template.json and add deployment configuration for the frontend web application and Sanity studio like so:
{
  "version": 2.0,
  "title": "Commerce Layer Starter",
  "description": "A multi-country ecommerce starter built with Sanity Studio, Commerce Layer, Next.js, and deployed to Netlify.",
  "previewMedia": {
    "type": "image",
    "src": ".sanity-template/assets/preview.jpg",
    "alt": "Preview image with Commerce Layer, Next.js, and Netlify's logo"
  },
  "technologies": [
    {
      "id": "nextjs",
      "name": "Next.js",
      "url": "https://nextjs.org"
    },
    {
      "id": "commercelayer",
      "name": "Commerce Layer",
      "url": "https://commercelayer.io"
    },
    {
      "id": "netlify",
      "name": "Netlify",
      "url": "https://netlify.com"
    }
  ],
  "deployment": {
    "provider": "netlify",
    "sites": [
      {
        "id": "studio",
        "type": "studio",
        "title": "Commerce Layer Starter Studio",
        "description": "A multi-country ecommerce starter built with Sanity Studio, Commerce Layer, Next.js, and deployed to Netlify.",
        "dir": "./studio",
        "previewMedia": {
          "type": "image",
          "src": ".sanity-template/assets/studio.png",
          "alt": "A preview image of the Sanity studio."
        },
        "buildSettings": {
          "base": "studio",
          "dir": "/dist",
          "cmd": "npm run build"
        }
      },
      {
        "id": "web",
        "type": "web",
        "title": "Commerce Layer Starter Web",
        "description": "A multi-country ecommerce starter built with Sanity Studio, Commerce Layer, Next.js, and deployed to Netlify.",
        "dir": "./web",
        "previewMedia": {
          "type": "image",
          "src": ".sanity-template/assets/preview.jpg",
          "alt": "A preview image of the web demo."
        },
        "buildSettings": {
          "base": "/",
          "dir": "/out",
          "cmd": "npm run build"
        }
      }
    ]
  }
}

The metadata information is primarily displayed on sanity.io/create as described below by the visual explainer from Sanity docs.

  • Test your sanity-template.json file for errors using the sanity-template command:
sanity-template check
  • Build your project with the configuration in sanity-template.json using the command:
sanity-template build
  • You need to refactor your project's name, projectId and dataset in studio/sanity.json to a dynamic variable so when a user installs your starter via sanity.io/starters, Sanity can populate it with new values. To this, you pass the string value in <#< ... >#> as seen in the snippet below:
{
  "root": true,
  "project": {
    "name": "<#< sanity.projectTitle >#>",
    "basePath": "/"
  },
  "api": {
    "projectId": "<#< sanity.projectId >#>",
    "dataset": "<#< sanity.dataset >#>"
  }
}
  • You can also set up Renovatebot to automatically make and merge pull requests that bump the Sanity dependencies upgrades in studio/package.json. All you need to do is add a renovate.json to the root directory, with the following configuration:
{
  "extends": [
    "github>sanity-io/renovate-presets:sanity-template"
  ]
}
  • Run the command below to build the studio to a static bundle and deploy it to Sanity cloud on a <your-project>.sanity.studio URL (you can also deploy anytime you make any change to your studio):
sanity deploy

You can still host a studio on any cloud platform you choose too (here’s how to deploy to Netlify) so you don't have to manually deploy after every change.

  • Lastly, push your finished code to GitHub and test it live by deploying the starter on Sanity following the starter specification:
https://www.sanity.io/create?template=[githubhandle]/sanity-template-[starter-name]

Conclusion

Now that you have built a Sanity starter and integrated Commerce Layer, you can start to add more items and product data stored in Commerce Layer so you can see how your products and prices show up within your content. The power of Commerce Layer is that you can really localize your commerce data to make it work for multiple markets, all of which likely have different prices, SKUs, promotions, and even simple things like item weights and measurements. Commerce Layer gives you tremendous flexibility to sell your products locally and paired with a powerful tool like Sanity, you will be on your way to building the best, most optimized shopping experience for your customers.

You can get started with the Commerce Layer starter by visiting this link and following the instructions. Feel free to join the Commerce Layer Slack community to share what you are able to build after reading this tutorial.