This content originally appeared on DEV Community and was authored by Enodi Audu
Building an e-commerce store can be daunting, especially when dealing with loads of data that don’t change often but still need to be readily available and up-to-date.
That's where tools like Sanity and Next.js come in handy. Sanity is a powerful headless CMS that allows you to manage your content effortlessly, while Next.js is a React framework that makes it easy to create fast, dynamic web applications. Together, they offer a robust solution for building and maintaining an e-commerce store efficiently.
In this article, we’ll walk through the step-by-step process of setting up an e-commerce store using Sanity and Next.js. We’ll cover everything from configuring your backend in Sanity to creating a responsive frontend with Next.js. By the end, you'll have a solid foundation for building scalable and maintainable online stores with ease.
Here is a screenshot of what the e-commerce store will look like. You can also check out the live app here. You can also review the code here
Table of Contents
- Step 1: Install Nextjs
- Step 2: Setup Chakra UI
- Step 3: Setup Sanity Studio
- Step 4: Update Content Schemas
- Step 5: Query Data using GROQ
- Step 6: Display Content in your Ecommerce App
- Step 7: Deploy Sanity Studio
- Step 8: Deploy to Vercel
- Step 9: Next Steps
1. Install Nextjs
Open a terminal and run this command:
npx create-next-app@latest
This will install the latest version of Next.js.
We'll name our project sanity-nextjs-ecommerce-store
but you can pick any name you like. We'll use the following options to set up our Next.js app.
✔ What is your project named? … nextjs-sanity-ecommerce-store
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … Yes
This will install the required dependencies including TailwindCSS which we will use to style our web app.
To see the application, run the commands below:
cd nextjs-sanity-ecommerce-store
npm run dev
This should start a server on port 3000
. Open your browser and go to localhost:3000 to see it in action.
2. Setup Chakra UI
Chakra UI is a React component library that provides customizable, accessible, and reusable UI components to build responsive web applications.
Chakra UI offers ready-to-use, reusable components, and we will be using some of them in our application, like the Drawer and Button components.
In your root directory, run this command or follow this documentation to set up Chakra in your Next.js project (App Router).
npm i @chakra-ui/react @chakra-ui/next-js @emotion/react @emotion/styled framer-motion
This will install all the necessary dependencies to run Chakra UI.
Setup Provider
In the src
directory, create a folder named lib
, and inside it, create another folder named chakra
. Then, create a file called ChakraProvider.tsx
within the chakra
folder.
Copy and paste this code into ChakraProvider.tsx
.
//src/lib/chakra/ChakraProvider.tsx
"use client";
import { ChakraProvider as Provider } from "@chakra-ui/react";
import { theme } from "@/lib/chakra/theme";
export function ChakraProvider({ children }: { children: React.ReactNode }) {
return <Provider theme={theme}>{children}</Provider>;
}
Create a file named theme.ts
inside the chakra
folder, then copy and paste this code into theme.ts
.
//src/lib/chakra/theme.ts
import { extendTheme } from "@chakra-ui/react";
export const theme = extendTheme({
fonts: {
heading: 'var(--font-lato)',
body: 'var(--font-lato)',
}
});
Create another file named fonts.ts
inside the chakra
folder, then copy and paste this code into fonts.ts
.
//src/lib/chakra/fonts.ts
import { Lato } from 'next/font/google'
const lato = Lato({
weight: "400",
subsets: ["latin"],
variable: "--font-lato"
})
export const fonts = {
lato
}
These files instruct Chakra to use the Lato
font as the default font for the application.
Use Chakra Provider
Navigate to the layout.tsx
component within your src/app
folder and use Chakra Provider within the layout component.
This is what it will look like:
//src/app/layout.tsx
import type { Metadata } from "next";
import "@/app/globals.css";
import { fonts } from "@/lib/chakra/fonts";
import { ChakraProvider } from "@/lib/chakra/ChakraProvider";
export const metadata: Metadata = {
title: "Create Next App", // You can update this
description: "Generated by create next app", // You can update this
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className={fonts.lato.variable}>
<body>
<ChakraProvider>{children}</ChakraProvider>
</body>
</html>
);
}
3. Setup Sanity Studio
Sanity Studio is Sanity's open-source UI tool for managing website content. It allows you to add, edit, and delete data like text, images etc within Sanity.
If you don't have a Sanity account, create one here and create a new project.
Log into Sanity from your terminal
If you already have an account and have logged in previously from your terminal, you can skip this step. Otherwise, run this command in your terminal:
npx sanity login
This will authenticate you using the sanity.io API.
Install Sanity Studio
Within your project directory, run this command:
npm create sanity@latest
If you created a project during your account setup, you can continue using that project or create a new one from your terminal. In this article, we'll use an existing project that was set up when creating a Sanity account. You can configure your project using the options provided below.
? Select project to use: **ecommerce-store**
? Select dataset to use: **production**
? Would you like to add configuration files for a Sanity project in this Next.js folder? **No**
? Project output path: /Users/USER/Desktop/nextjs-sanity-ecommerce-store/**studio**
? Select project template Clean project with no predefined schema types
? Do you want to use TypeScript? **Yes**
? Package manager to use for installing dependencies? **npm**
After completing the setup, Sanity Studio will be installed locally. To view the studio, run these commands:
cd studio
npm run dev
Navigate to localhost:3333
. Log in using the same method you used to create your account, and you should see the studio running locally.
4. Update Content Schemas
If you've successfully set up and logged in to Sanity Studio locally, you should see a display similar to the screenshot below:
Currently, our studio is empty because we haven't defined any schemas yet. Next, we'll define our schemas.
Schemas are like blueprints that outline how different types of content should be structured. They describe what fields each piece of content can have and what kind of information those fields can hold. This helps keep content organized and manageable.
For this e-commerce store, we create two schemas: one for Product
and another for Category
.
-
Product Schema
: This schema defines how a product is structured within our store. -
Category Schema
: This schema defines how product categories are structured within our store.
Product Schema
Create a file named product.ts
inside the schemaTypes
directory located within the studio
directory.
Copy and paste the code below into product.ts
file
//studio/schemaTypes/products.ts
import { defineType, defineField } from "sanity";
export const productType = defineType({
title: "Product",
name: "product",
type: "document",
fields: [
defineField({
title: "Product Name",
name: "name",
type: "string",
validation: (Rule) => Rule.required()
}),
defineField({
title: "Product Images",
name: "images",
type: "array",
of: [
{
type: "image",
fields: [
{
name: "alt",
title: "Alt Text",
type: "string",
},
],
},
],
}),
defineField({
title: "Product Description",
name: "description",
type: "text",
validation: (Rule) => Rule.required()
}),
defineField({
title: "Product Slug",
name: "slug",
type: "slug",
validation: (Rule) => Rule.required(),
options: {
source: "name"
}
}),
defineField({
title: "Product Price",
name: "price",
type: "number",
validation: (Rule) => Rule.required()
}),
defineField({
title: "Product Category",
name: "category",
type: "reference",
to: [{ type: "category" }]
})
]
})
Category Schema
Create a file named category.ts
inside the schemaTypes
directory located within the studio
directory.
Copy and paste the code below into category.ts
file
//studio/schemaTypes/category.ts
import { defineType, defineField } from "sanity";
export const categoryType = defineType({
title: "Category",
name: "category",
type: "document",
fields: [
defineField({
title: "Category Name",
name: "name",
type: "string",
validation: (Rule) => Rule.required()
})
]
})
Update index.ts
to look like this:
//studio/schemaTypes/index.ts
import { productType } from "./product"
import { categoryType } from "./category"
export const schemaTypes = [
productType,
categoryType
]
Each schema and field needs to include the name
, and type
properties. Here's a quick overview of each property's role:
- The name property serves as the identifier for referencing the schema in query language contexts. It must be unique to prevent schema conflicts.
-
Type indicates the specific schema type being defined. Setting it to
document
instructs the studio to enable the creation of new documents.
Visit your studio
Navigate to the studio
directory from your terminal and run npm run dev
.
Open your browser and go to localhost:3333.
This is what it should now look like:
Populate your Schemas
Begin by creating categories, followed by creating products within those categories. Create as many categories and products as you want.
This is what it should now look like:
Next, we will query the product and category data to use within the application.
5. Query Data using GROQ
GROQ (Graph-Relational Object Queries) is the query language developed by Sanity for querying structured content in their backend. It enables retrieval and manipulation of data stored in Sanity's content lake.
To query product and category data, we'll use a library named @sanity/client
. This library offers methods for querying, creating, updating, and deleting documents in Sanity. It is designed for use in both server-side and client-side JavaScript applications.
In the lib
directory, create a new directory named sanity
, and within it, create a file called client.ts
.
Then, copy and paste the code below into client.ts
.
//src/lib/sanity/client.ts
import { createClient, type ClientConfig } from "@sanity/client";
const config: ClientConfig = {
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
useCdn: false
};
const client = createClient(config);
export default client;
Replace process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
and process.env.NEXT_PUBLIC_SANITY_DATASET
with the correct projectId
and dataset
values.
-
projectId
: This is your project's ID. You can obtain it by running the commandnpx sanity projects list
in your terminal or by visiting the projects page in your Sanity account. See the screenshot below. -
dataset
: This is the name of your project's dataset. By default, it isproduction
, but if you create a new dataset, you should update it to reflect your dataset name.
Create 3 new files named product-query.ts
, category-query.ts
, and types.ts
. Copy and paste the following code into it:
//src/lib/sanity/product-query.ts
import { groq } from "next-sanity";
import client from "./client";
export async function getProducts() {
return client.fetch(
groq`*[_type == "product"] {
_id,
"categoryName": category->name,
description,
name,
price,
"productImage": {"alt": images[0].alt, "imageUrl": images[0].asset->url},
"slug": slug.current
}`
);
}
export async function getSelectedProducts(selectedCategory: string) {
return client.fetch(
groq`*[_type == "product" && category->name == $selectedCategory] {
_id,
"categoryName": category->name,
description,
name,
price,
"productImage": {"alt": images[0].alt, "imageUrl": images[0].asset->url},
"slug": slug.current
}`,
{selectedCategory}
);
}
The getProducts
function retrieves all products, whereas the getSelectedProducts
function fetches specific products based on the chosen category.
//src/lib/sanity/category-query.ts
import { groq } from "next-sanity";
import client from "./client";
export async function getCategories() {
return client.fetch(
groq`*[_type == "category"] {
_id,
name,
}`
);
}
The getCategories
function retrieves all categories.
You'll notice that the GROQ query begins with an asterisk (*), representing all documents in your dataset, followed by a filter in brackets. The filter used here returns documents with a _type
of "product" or "category".
//src/lib/sanity/types.ts
export type ProductType = {
_id: string,
name: string,
productImage: {
alt: string,
imageUrl: string
},
slu: string,
categoryName: string,
description: string,
price: number,
};
export type CategoryType = {
_id: string,
name: string,
};
The ProductType
and CategoryType
define the structure of the Product
and Category
objects.
6. Display Content in your E-commerce App
It's now time for us to display the content within the e-commerce application.
Begin by stripping all styles from the globals.css
file, leaving only essential Tailwind imports at the beginning. Next, erase the contents of your root page.tsx
file in your Next.js application and replace it with the following code:
//src/app/page.tsx
"use client";
import { useDisclosure } from "@chakra-ui/react";
import { Fragment, useEffect, useState } from "react";
import Hero from "@/app/components/hero/Hero";
import Cart from "@/app/components/cart/Cart";
import Navbar from "@/app/components/navbar/Navbar";
import Products from "@/app/components/products/Products";
import { getCategories } from "@/lib/sanity/category-query";
import { ProductType, CategoryType } from "@/lib/sanity/types";
import { getProducts, getSelectedProducts } from "@/lib/sanity/product-query";
export default function Home() {
const { isOpen, onOpen, onClose } = useDisclosure();
const [products, setProducts] = useState<ProductType[]>([]);
const [categories, setCategories] = useState<CategoryType[]>([]);
const [selectedCategory, setSelectedCategory] = useState<string>("");
const [cartItems, setCartItems] = useState<ProductType[]>([]);
const [cartItemsCount, setCartItemsCount] = useState<number>(0);
const localStorageCartItem =
typeof window !== "undefined" && localStorage.getItem("cart");
const parsedCartItems =
localStorageCartItem && JSON.parse(localStorageCartItem);
const itemsInCart = cartItems.length > 0 ? cartItems : parsedCartItems;
const localStorageCartItemCount =
typeof window !== "undefined" && localStorage.getItem("cartCount");
const cartCount: number =
localStorageCartItemCount && JSON.parse(localStorageCartItemCount);
const itemCount = cartItemsCount || cartCount;
useEffect(() => {
async function fetchProducts() {
const allProducts: ProductType[] = await getProducts();
setProducts(allProducts);
}
fetchProducts();
}, []);
useEffect(() => {
async function fetchCategories() {
const allCategories: CategoryType[] = await getCategories();
setCategories(allCategories);
}
fetchCategories();
}, []);
const handleDrawerOpen = () => onOpen();
const handleProductFilter = async (category: string) => {
let product: ProductType[] = [];
if (!!category) {
product = await getSelectedProducts(category);
} else {
product = await getProducts();
}
setProducts(product);
setSelectedCategory(category);
};
const addCartItem = (product: ProductType) => {
let cart: ProductType[] = [];
const count = cartCount + 1;
const products = [];
products.push(product);
if (!!itemsInCart) {
cart = [...itemsInCart, ...products];
} else {
cart = [...products];
}
setCartItems(cart);
setCartItemsCount(count);
updateLocalStorage(count, cart);
};
const removeItemFromCart = (product: ProductType) => {
const count = cartCount - 1;
const filteredItems = itemsInCart.filter(
(item: ProductType) => item._id !== product._id
);
setCartItems(filteredItems);
setCartItemsCount(count);
updateLocalStorage(count, filteredItems);
};
const updateLocalStorage = (count: number, cart: ProductType[]) => {
if (typeof window !== "undefined") {
localStorage.setItem("cartCount", JSON.stringify(count));
localStorage.setItem("cart", JSON.stringify(cart));
}
};
return (
<Fragment>
<Navbar handleDrawerOpen={handleDrawerOpen} itemCount={itemCount} />
<main>
<Hero
categories={categories}
handleProductFilter={handleProductFilter}
/>
<Products
products={products}
selectedCategory={selectedCategory}
addCartItem={addCartItem}
/>
</main>
<Cart
isOpen={isOpen}
onClose={onClose}
itemsInCart={itemsInCart}
removeItemFromCart={removeItemFromCart}
/>
</Fragment>
);
}
//src/app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Please review this repository for additional components like Navbar
, Hero
, Products
, and Cart
used in page.tsx
.
Add localhost:3000
to CORS Origin
After setting up your frontend application and including all necessary components, launch your Next.js application with npm run dev
.
This will start your Next.js app on port 3000
. Open your browser and navigate to localhost:3000. You'll encounter a CORS error preventing your application from loading.
To resolve this issue, include localhost:3000 to the list of allowed hosts that can connect to your project's API.
You can achieve this via the terminal using npx sanity cors add http://localhost:3000
, or by logging into your Sanity account and adding localhost:3000
to the CORS origin list. Refer to the screenshot below for guidance.
Now, restart your application, and you should see a list of products that were added through your Sanity Studio.
You have the option to add products to your cart, remove them, and filter the product list by category.
7. Deploy Sanity Studio
Once your application is up and running locally, you can synchronize your schemas with your remote Sanity Studio by running npx sanity deploy
. This ensures that your remote studio reflects the latest changes made locally.
8. Deploy to Vercel
To make your e-commerce store accessible online, deploy it using Vercel. Vercel provides seamless deployment for Next.js applications, ensuring your site is fast and reliable. Simply link your GitHub repository to Vercel and trigger automatic deployments with every push to your main branch. Once deployed, your store will be live and accessible to users worldwide.
9. Next Steps
While this tutorial has provided a solid foundation, there are numerous ways to further enhance your e-commerce store using Sanity.
Thank you for reading! Remember to like, share, and follow for future updates and more insightful content. Until next time, happy coding!
This content originally appeared on DEV Community and was authored by Enodi Audu
Enodi Audu | Sciencx (2024-07-16T11:01:24+00:00) How to Build an E-commerce Store with Sanity and Next.js. Retrieved from https://www.scien.cx/2024/07/16/how-to-build-an-e-commerce-store-with-sanity-and-next-js/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.