Next.js is a lightweight framework for React applications that allows us to build server-side rendering and static applications in React easily. It takes all the good parts of React and makes it even easier to get an app running with optimized rendering performance.
Here is the project GitHub repository; you can clone or download the complete project.
#Why Next.js for eCommerce
Next.js abstracts a lot of the complexity away for us, so we can build our eCommerce application easily having so many features that align with the specifications required for eCommerce projects, such as:
- Live collaboration tooling: Next.js Live generates a live URL for your project that you can use to share, chat, draw, and edit code in the browser. This results in an improved collaboration between you and your team.
- Image optimization: This aids in seamless product discovery by allowing images to load faster, resulting in a better product discovery experience for consumers and higher conversion rates.
- Built-in internationalization: This is extremely useful for global brands because they can statically serve different languages on your site at build time and in record speed. This is accomplished through internationalization routing.
- SEO: Next.js makes our e-commerce site easier for search engines to crawl, which leads to more traffic and, ultimately, more sales. Next.js, for example, has a feature called
next/head
that allows us to append elements to the page's head, such as title and meta tags. - Popularity & vast community: Any issue has probably been documented already, so you're likely to find a solution to any potential pitfalls in your way.
- Next.js features like server-side rendering and static exporting push these React benefits even further by guaranteeing that your website/app will be SEO-friendly. This is something vital to any e-commerce business.
Here are some Next.js e-commerce site examples to help you see examples of many companies and platforms that now make use of Next.js for their eCommerce applications.
#Architecture
For this app, we will use next.js to create an e-commerce application that retrieves products from Hygraph via Apollo client and queries data using GraphQL. We will also use Snipcart to easily integrate a cart system into our e-commerce application. More information on Architectures for Modern eCommerce Applications can be found here.
#Getting Started
We will build a plant store, this store will contain a few plants, their name, prices, and pictures. At the end of this guide, we should have an eCommerce app that functions like the one below:
Requirements/Prerequisites
To understand this guide and code along, you should have:
- Basic understanding of HTML, CSS, and JavaScript
- At least a little experience or knowledge of React and maybe Next.js.
- Node and npm or yarn installed on your computer
- A Snipcart account (forever free in test mode).
Note: This is a "follow along" type of article. You'll get a good result if you keep building. If you're only interested in how this works with Hygraph and Snipcart, you can skip the development phase or clone the starter repository (check out the readme page for instructions on how to use it), which has all the codes covered in the development phase working properly.
Setting up Next.js development environment
Let's start by preparing our environment, so we can start building. In your preferred terminal, type the following command:
npx create-next-app ecommerce-demo
A prompt will appear asking you to confirm the name of the project. After that, it will install all of the project dependencies for you. Then, run npm run dev
to make our app available at localhost:3000
.
Defining a layout
Next.js allows us to create a layout to hold files that will be repeated throughout our application, such as our Header and Footer Components, which are fixed across all pages of our e-commerce application.
Once we are done setting up the layout component, we will add it to the app's main entry point which is located at pages/_app.js
via MyApp
function which returns the pageProps
.
Creating Layout components
For this, we will create a Layout component as well as two additional components for our application's Header and Footer. To begin, we will create a components folder in the base folder, where we will now store all components. Begin by writing the following code in a Header.js
file:
// components/Header.jsimport Link from 'next/link';import styles from '../styles/components/Header.module.css';const Header = () => {return (<nav className={styles.navbar}><Link href="/"><a><div className={styles.logo}><p>PLANTS <span className={styles.logo_span}>☘</span></p></div></a></Link><div className={styles.nav_price}><span>🛒</span><p>$0.00</p></div></nav>);};export default Header;
Still, in the components
directory, let’s also create a Footer.js
file for the footer with the following content:
// components/Footer.jsimport styles from '../styles/components/Footer.module.css';const Footer = () => {return (<footer className={styles.footer}><p>All Rights Reserved. Copyright © 2022</p></footer>);};export default Footer;
Integrating components into Layout
Let's now integrate those components into our app by first creating a Layout
component that will take the children's props and place them wherever we want. In our case, we want the following pages to appear between the header and footer components:
// components/Layout.jsimport Header from './Header';import Footer from './Footer';const Layout = ({ children }) => {return (<div><Header />{children}<Footer /></div>);};export default Layout;
Finally, with our layout components created, all that's left to do is to add it to the _app.js
file, so as to configure our app’s layout:
// _app.jsimport Layout from '../components/Layout';import '../styles/globals.css';function MyApp({ Component, pageProps }) {return (<><Layout><Component {...pageProps} /></Layout></>);}export default MyApp;
If you launch your app in dev mode and navigate to your localhost page, you should now see your app's layout. To obtain the CSS files from the styles folder, go to the starter repository. Now it's time to give our homepage the content it deserves.
Customizing our homepage
So far we have been able to set up our app layout, let’s now work on the index/home page of our application which is the pages/index.js file.
Note: Ensure you visit the repository, to get the
products
array viapages/data/products.json
to get all the data, also ensure you get the images frompublic/images
folder.
For this, we will also pre-render the data we will be getting from pages/data/products.json
via getStaticProps
, which fetches data at build time. This is what our code will look like:
// pages/index.jsimport allProducts from './data/products.json';import Head from 'next/head';import Link from 'next/link';import styles from '../styles/Home.module.css';export default function Home({ allProducts }) {return (<><Head><title>Plants | Home</title></Head><div className="container"><h2 className={styles.title}>All Products <span>🌿</span></h2><div className={styles.products_container}>{allProducts.map((product) => {return (<div className={styles.product_card} key={product.id}><Link href={`products/${product.slug}`}><a><div className={styles.product_img}><img src={product.image.url} alt={product.name} /></div></a></Link><div className={styles.product_content}><h3>{product.name}</h3><p>${product.price}</p><button className="btn">Add to cart 🛒</button></div></div>);})}</div></div></>);}export async function getStaticProps() {return {props: {allProducts,},};}
We returned the data as props and then looped through all the products using JavaScript’s map()
iterator method.
Dynamic routing for each product
When a user attempts to purchase a product, the user will first click on the product to read more information about that specific product. On the development side, you may believe that individual pages are created for each product, but this will be cumbersome and difficult to maintain. This is the reason for dynamic routing.
This would be properly implemented when we fetch data from Hygraph, but for now, let’s create a component in a folder inside the pages' folder. This would look like this pages/products/[productslug].js
. In this component, we will add the following code:
// pages/products/[productslug].jsimport Head from 'next/head';import styles from '../../styles/SingleProduct.module.css';const singleproduct = () => {return (<><Head><title>Dracaena fragrans</title></Head><div className={styles.single_container}><div className={styles.left_section}><img src="/images/croton.png" className={styles.left_img} alt="" /></div><div className={styles.right_section}><h3 className={styles.title}>Dracaena fragrans</h3><p className={styles.price}>$50</p><div className={styles.para}><p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Atimpedit voluptatum vitae labore molestiae, maiores, hic adofficiis laudantium in officia, nam vel quod! Nesciunt aperiamexplicabo facere laboriosam eius.</p></div><button className="btn">Add to cart 🛒</button></div></div></>);};export default singleproduct;
We manually entered all the data in the preceding code, but we will retrieve them from Hygraph shortly. So far, we have been successful in establishing our e-commerce application. If anything confuses you, or if you were unable to configure your app to produce similar results, make sure to check out the starter repository and compare your codes.
#Hygraph
Hygraph is a cutting-edge content management platform that enables teams to provide content to any channel. If this is your first time exploring Hygraph, create a free-forever developer account via either GitHub, Mail, e.t.c.
We will be making use of Hygraph to store and manage all the products we will be using for our e-commerce web application. Once we have created an account or signed In, we can now create a project using any project name of our choice and location that is close to you. At this point, we now have our project set up and the next step would be to create our schema.
Set up Schema
Schema is the structure we develop for storing our content on Hygraph and for us to set up our schema, we can either click on the schema icon in the sidebar or the highlighted steps as seen below:
The next step would be to create a model, since we are creating an e-commerce application the major model we need to create is the products model to store all our products
The next step would be to add our desired fields for the content we wish to store, such as product name, image, description, price, e.t.c
Note: slug is generated from the name template as seen below:
Create & Add content
Once we are done setting up our schema the next step would to start creating/adding our products to the product model based on the fields we created, to do this we can either visit the editing interface via the icon on the sidebar or the highlighted steps as seen below:
We can now click on the model we want to create items for, suppose we have multiple models, and then go ahead to click the “create item” button. Ensure you save and publish them as soon as you are done inputting each content. Once that is done, your model will now have some contents we can fetch into our eCommerce store.
Explore Content API
So far we have been able to set up Hygraph and also added our content, the next step would be to learn how to query Hygraph. We would make use of the inbuilt GraphQL Explorer to create our query, we do this by visiting the Hygraph API playground as seen below:
This would take us to the GraphQL Explorer where we can create our query easily even if we are not used to GraphQL by just ticking the boxes:
Make API Accessible
At this point we have our contents, and we have been able to come up with a query, but we also need to make our API accessible so that these contents can be fetched via the API, to do this we can either click the settings icon on the sidebar or the highlighted steps as seen below:
We can now initialize by defaults or maybe create permissions if you wish:
We would now click on Endpoints to get our content API, which would be used to get content from Hygraph when fetching our data via Apollo client in our Next.js web app.
Note: Ensure you keep this API link as we would make use of it soon.
Fetch data from Hygraph with GraphQL
Once we have successfully set up Hygraph and created our GraphQL query, the next step would be to query Hygraph via our Next.js web app. The first step would be to install Apollo client and GraphQL, which would enable us to construct the query we would send with Apollo:
npm install @apollo/client graphql
Once this is successful, the next step would be to initialize Apollo client by first importing Apollo client, InMemoryCache and gql from @apollo/client
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
Once we have imported, we can now proceed to initialize Apollo client to allow us to fetch via our query later on, but for us to initialize, it would be best to make use of the getStaticProps()
function so that the content is fetched at build time:
export async function getStaticProps(context) {return {props: {},}}
We can now initialize apollo client within the getStaticprops() function as seen below:
export async function getStaticProps() {const client = new ApolloClient({uri: 'https://api-us-east-1.hygraph.com/v2/cl2cupjxo4izz01z8fcm26gxf/master',cache: new InMemoryCache(),});return {props: {},};}
Note: In the code above, we will make use of the content API we copied from Hygraph Endpoints.
Once that is done, the next step would be to get our data via the GraphQL query we created while exploring the Hygraph content API as seen below:
const data = await client.query({query: gql`query ProductsQuery {products {idnameslugpriceimage {url}}}`,});
At the end of the day, our getStaticProps() function will look like this:
export async function getStaticProps() {const client = new ApolloClient({uri: 'https://api-us-east-1.hygraph.com/v2/cl2cupjxo4izz01z8fcm26gxf/master',cache: new InMemoryCache(),});const data = await client.query({query: gql`query ProductsQuery {products {idnameslugpriceimage {url}}}`,});const allProducts = data.data.products;return {props: {allProducts,},};}
Note: We assigned the products array to a new variable, so it’s easier to work with, you can decide to log out the data in your console/terminal to be sure everything works fine. If you are not familiar with
getStaticProps()
function check here.
Making use of data
So far we have been able to fetch our data via getStaticProps()
function so that the content is fetched at build time, let's now make use of these props within our web application by passing in the props:
import Link from 'next/link';export default function Home({ allProducts }) {return (<div className="container"><h2 className="title">All Products 🌿</h2><div className="products-container">{allProducts.map((product) => {return (<div className="product-card" key={product.id}><Link href={`products/${product.slug}`}><div className="product-img"><img src={product.image.url} alt={product.name} /></div></Link><div className="product-content"><h3>{product.name}</h3><p>${product.price}</p><a className="btn">Add to cart 🛒</a></div></div>);})}</div></div>);}
In the above, we destructured props to get the variable without needing to make use of props.``*allProducts*
. We looped through the data, and our app should now look like this:
Dynamic Page creation for each product
For this, we will make use of getStaticPath() function which allows us to define all the paths to be statically generated for that route. Simply we want to be able to access a route link - http://localhost:3000/products/slug where the slug would be dynamic based on the product being fetched.
To do this we are going to make use of dynamic routes which allows us to create dynamic routes with square brackets so that inside the bracket we have a parameter that can be used as the path to dynamically request the page we want to display.
To do this, we would create a file: products/[productSlug].js
, then the next thing will be to tell next.js every single path and the slug that corresponds to the path. For this, we will use getStaticPath()
function which would return an object, but this time around not props but an array of paths. This would be done similarly to what we fetched contents earlier:
The first step would be to import apollo client, InMemoryCache and gql from @apollo/client
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
And then we can now initialize and query Hygraph via apollo client:
export async function getStaticPaths() {const client = new ApolloClient({uri: 'https://api-us-east-1.hygraph.com/v2/cl2cupjxo4izz01z8fcm26gxf/master',cache: new InMemoryCache(),});const data = await client.query({query: gql`query ProductsQuery {products {idnameslugpriceimage {url}}}`,});const paths = data.data.products.map((singleProduct) => {return {params: {productslug: singleProduct.slug,},};});return {paths,fallback: false,};}
In the above code, you will also notice that we looped through the entire products and returned all the slugs, and then returned the entire slugs via the paths array.
The next step would be to make use of the getStaticProps()
function like before to query Hygraph for the particular product that matches our slug, let’s first visit Hygraph API playground to come up with the GraphQL query which would check for a particular slug parameter:
Once that is done, we can now make use of the query in the getStaticProps()
function:
export async function getStaticProps({ params }) {const client = new ApolloClient({uri: 'https://api-us-east-1.hygraph.com/v2/cl2cupjxo4izz01z8fcm26gxf/master',cache: new InMemoryCache(),});const data = await client.query({query: gql`query MyQuery($slug: String) {product(where: { slug: $slug }) {idnamepriceslugdescription {html}image {url}}}`,variables: {slug: params.productslug,},});const product = data.data.product;return {props: {product,},};}
Note: at the end of the query request, we added the variables property to set the slug we want to fetch.
Once this is successful, we can now add this props to this web app page so we can display the fetched content:
const singleproduct = ({ product }) => {return (<div className="container single-container"><div className="left-section"><Image src={product.image.url} width={300} height={700} alt="" /></div><div className="right-section"><h3>{product.name}</h3><p className="price">${product.price}</p><divdangerouslySetInnerHTML={{__html: product.description.html,}}></div><a className="btn">Add to cart 🛒</a></div></div>);};export default singleproduct;
#Implementing Shopping cart and checkout with Snipcart
Hygraph alone can't provide out-of-the-box eCommerce experiences, but paired with other APIs, it's possible to connect Hygraph with a variety of eCommerce APIs to deliver rich buying experiences. You can check here for some headless commerce APIs of which we would be making use, one of which is Snipcart.
Snipcart makes it easy to add a shopping cart to any website and also add a payment gateway easily. The first step is for us to register or sign in, activate our account, and fill in all necessary details. For this guide, we will make use of the test mode just to explain the development process, I also recommend you first work with the test mode before switching to live mode.
Installation
Let’s get started by installing Snipcart to our web application, we need to create a custom document page which is going to allow us to modify outside the body and load the Snipcart installation scripts so that they load globally. This file will be created in the pages folder and named _document.js
. Paste the code below into the file:
import { Html, Head, Main, NextScript } from 'next/document';export default function Document() {return (<Html><Head><link rel="preconnect" href="<https://app.snipcart.com>" /><link rel="preconnect" href="<https://cdn.snipcart.com>" /><linkrel="stylesheet"href="https://cdn.snipcart.com/themes/v3.2.0/default/snipcart.css"/></Head><body><Main /><NextScript /><script async src="https://cdn.snipcart.com/themes/v3.2.0/default/snipcart.js"></script><div hidden id="snipcart" data-api-key="YOUR_PUBLIC_API_KEY"></div></body></Html>);}
In the above code, we have imported Snipcart globally. The only thing we have to do now inserts our API Key into the hidden div
. You can get your API key from the dashboard credentials page. Ensure you restart your server anytime you update this page.
#Adding products to the cart
At this point, we now have to work on our “Add to cart” button so that anytime it’s clicked for a particular item, that item would be added to the cart. To do this, we add some attributes to our button, these attributes consists of information like the product name, price, description, images, etc.:
<buttonclassName="btn snipcart-add-item"data-item-id={product.id}data-item-price={product.price}data-item-url={`products/${product.slug}`}data-item-image={product.image.url}data-item-name={product.name}>Add to cart 🛒</button>
At this point, when you add any item to the cart, a page like this will appear having the information about the item:
We can customize our cart, but for this guide, we will only make our cart appear as a sidebar rather than a full screen. To do that, we will add data-config-modal-style=``"``side``"
to the hidden div in the _document.js
file.
<div hidden id="snipcart" data-api-key="YOUR_PUBLIC_API_KEY" data-config-modal-style="side"></div>
Note: Let’s ensure we update the “Add to cart” button on the single products page which is being fetched dynamically, so it can also be used to add products to the cart as it should.
#Working on Navbar price
Finally, we need to make the price tag on the Navbar reflect the actual total price, and also when we click the price we want the cart summary to open up as a sidebar, so we see the items in our cart.
With Snipcart, we perform all these functions by just adding classes to our markup. To add the total price to the tag we add *className*``="snipcart-total-price"
while to make the text clickable, so it shows the cart summary, we add *className*``="snipcart-checkout``"
meaning, the price section of our Navbar will look like this:
<nav className="navbar container">...<div className="nav-price snipcart-checkout"><span>🛒</span><p className="snipcart-total-price">$0.00</p></div></nav>
#Add Payment gateway in Snipcart
Finally, adding a payment gateway in Snipcart is very easy, all you have to do is ensure you are signed in and then visit the payment gateway page. On this page you will find up to 5 payment gateways, make use of anyone you prefer as all you just have to do is create an account and then click on the connect button to authorize access.
#Conclusion
In this guide, we were able to learn how to create an eCommerce application that retrieved content from Hygraph using this guide. There is a lot that can be done with Hygraph, one of which is product data localization. We also learned how server-side data fetching works, as well as how to integrate Snipcart to handle carting and checkout.