Next.js E-Commerce Tutorial: SPA Example
Tags: react, tutorial, webdev, nextjs
Published: 2021-07-30 00:32:07

---

title: Next.js E-Commerce Tutorial: SPA Example
published:
date: 
tags: react, tutorial, webdev, next

canonical_url: https://snipcart.com/blog/next-js-ecommerce-tutorial-example

---
> In a rush? Skip to the [tutorial](#tutorial) or [live example](https://snipcart-next-2021.vercel.app/)

Like many web developers, I have been doing quite a lot of React stuff recently. The last few years have seen its usage in the industry grow in a trajectory commensurate to its [parent company](https://www.facebook.com/).

These days, there isn’t much you can’t do with React, whether you’re a seasoned developer or a complete beginner.

This is mostly due to the creation of tools such as Next.js that have successfully simplified React frontend development.

![Next.js Simplified React](https://snipcart.com/media/205932/nextjs_simplify_react_2.gif)

So, today, we'll **explore how to craft a Next.js e-commerce single-page application quickly.**

In the technical tutorial below, I’ll show you how to:

- Set up a Next.js development environment
- Create new pages & components
- Fetch data & import components
- Create serverless API routes in Next
- Add a shopping cart to a Next.js app
- Style the app

But before we go through this, let’s make sure we understand what Next.js is and how it can improve your next e-commerce projects.

## What's Next.js?

**In a nutshell, Next.js is a lightweight framework for React applications that allows you 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. Next.js does this thanks to multiple built-in configurations—automatic code-splitting, file-system routing, server-side rendering, static files exporting, and styling solutions.

Trust me when I say that you can build a LOT of different things with Next.js:

- Static websites—we’ve listed it as one of the [top static site generators for 2021](https://snipcart.com/blog/choose-best-static-site-generator).
- [Progressive Web Apps](https://snipcart.com/blog/pwa-example-ecommerce-gatsby) (PWAs)
- Server-rendered applications
- SEO-friendly websites—as we’ve demonstrated [here](https://snipcart.com/blog/react-seo-nextjs-tutorial).
- Mobile apps

It was built by [Zeit](https://zeit.co/) (now Vercel) back in 2016 and has quickly gained traction to the point of becoming one of the [most popular tools](https://jamstack.org/generators/) of its kind. I mean, it’s used by Marvel, Netflix, Uber, Nike… and the list goes on.

Okay, this is all great, and I’m genuinely excited to play with Next.js here. But is it any good for e-commerce?

## Next.js & e-commerce: a good fit?

Like any static site generator or JavaScript framework out there, one of its most significant advantages, vs. more traditional e-commerce platforms, is the options it gives to developers to create a kickass shopping UX while removing the burden of many implementation details required for building a web app. 

![Next.js kickass for e-commerce](https://snipcart.com/media/205928/kickass.gif)

Depending on your need, you can easily build a server-side or statically rendering app with Next, which implements those for you while also abstracting other details such as [app-bundling](https://webpack.js.org/) and [transcompilation](https://babeljs.io/).

The power of the [Jamstack](https://snipcart.com/blog/jamstack) right here!

We’ve covered the general [React e-commerce ecosystem](https://snipcart.com/blog/react-ecommerce-tutorial) and its benefits in an earlier post. I would strongly suggest reading it to understand further why it’s a great fit.

But on the probable chance that you're pressed for time, here’s a TL;DR:

**→ The use of components for flexibility.**

Component-based development enables easy code reuse through your app but also the writing of small features. Or, in our case, small e-commerce functionalities. This comes in handy once you start scaling and expanding your shopping cart integration.

**→ Virtual DOM (document object model) for performance.**

React’s [virtual DOM](https://www.rigelnetworks.com/using-virtual-dom-react-js-top-5-benefits/) provides a more efficient way of updating the view in a web application. Performance is Everything in e-commerce; all milli-seconds count.

![Better Speed = Better UX & SEO = More profit](https://snipcart.com/media/205929/speed-uxplusseo.png)

**→ 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.

> Still need social proof? Here are some [Next.js e-commerce site examples](https://nextjs.org/showcase#e-commerce).

<h2 id="tutorial">Technical tutorial: a Next.js e-commerce SPA</h2>

Okay, time to jump into code and create our own handcrafted Next.js e-commerce app with the help of Snipcart. For you fish aficionados — or really anyone waiting for the beta of any cool software library — rest assured because we will make a betta fish store today.

![https://snipcart.com/media/205930/nextjs-ecommerce-fish-store.png](https://snipcart.com/media/205930/nextjs-ecommerce-fish-store.png)

### Pre-requisites

- Basic understanding of [single-page applications](https://snipcart.com/blog/react-nextjs-single-page-application-seo#spa) (SPAs)
- [A Snipcart account](https://app.snipcart.com/register) (forever free in test mode)
- [Npm](https://www.npmjs.com/) and [node.js](https://nodejs.org/en/) installed

Basic knowledge of [React](https://reactjs.org/tutorial/tutorial.html) & [TypeScript](https://snipcart.com/blog/learn-typescript-why-use-ts) will also help you here, but it is not mandatory to follow along.

### 1. Setting up the development environment

First, let set up our environment so that we can start building.

Open a terminal and type the following command:
`npx create-next-app --typescript`

A prompt will appear asking you for the project's name. It will then install all of the project dependencies for you and create files and folders. We'll look into these further in this tutorial.

> Beginner note: The command's `--typescript` options will set up a Typescript project, which I often favor. The typing system helps prevent many runtime errors and perky bugs. It also allows for better refactoring in the long run!

Then, run `npm run dev`. Your app should now be served at `localhost:3000`.

### 2. Defining a layout

With our environment ready, let's create a layout for our store. It will include a Header and a Footer with links to our cart and contact information.

We will add this layout to the app's main entry point. In `Next`, this entry point is located at `pages/_app.tsx`. You can see that the `MyApp` function returns the `pageProps`. We will use this function to create our app's layout:

At the project's root, create a `components` directory in which — you guessed it — we will create our components.

### 1. Creating components

Now let's create the components we need.

In the `components` directory, create a `Header.tsx` file with the following content:

```tsx
// components/Header.tsx
import Link from "next/link";

export default function Header() {
    return (
        <header >
            <Link href="/">
                <img src="/static/logo.svg" alt="" >
            </Link>
            <Link href="/">
                <h1 >FishCastle</h1>
            </Link>
            <a href="#" style={{textDecoration: "none"}}>
                <svg width="31" height="27" viewBox="0 0 31 27" fill="none" xmlns="<http://www.w3.org/2000/svg>">
                    <path d="" fill="#9094FF"/>
                </svg>
                <span></span>
            </a>
        </header>
    )
}
```

The `Link` component from Next.js allows us to convert most HTML elements into in-website links.

Still in the `components` directory, create a `Footer.tsx` file with the following content:

```tsx
// components/Footer.tsx

export default function Footer(){
    return (
        <footer>
            <p>
                Next.js app with a&nbsp;<a href="<https://snipcart.com>">Snipcar        t</a>&nbsp;- powered store
                <div >
                    <a href="<https://github.com/snipcart/snipcart-nextjs-spa>">Github</a>
                </div>
            </p>
        </footer>
    )
}
```

### 2. Integrating components

Let's now integrate those components into our app. First, create a `Layout` component and put the `Header` and `Footer` in it:

```tsx
import Header from "./Header";
import Footer from "./Footer";
import {PropsWithChildren} from "react";

export default function Layout({ children  }: PropsWithChildren<any>) {
  return (
      <>
          <Header />
          <main>{children}</main>
          <Footer />
      </>
  )
}
```

With your layout components created, all that's left to do is to add it to the `_app.tsx` file:

```jsx
// _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
  return <>
    <Layout>
      <Component {...pageProps} />
    </Layout>
    </>
}
```

If you run your app's dev mode and go to your localhost page, you should now see your app's layout created. Later in this tutorial, we will see how to add style to it and give it that **drip**. 

But first things first, let's give our homepage the content it deserves.

### 3. Customizing your homepage

As we need to display both information about our store and the products we will sell, we will create a few different components to keep things modular and maintainable. Then, we will look at how to assemble them:

### 1. Creating required components

### The product component

As this is a Next.js tutorial for an e-commerce app, you will need a `Product` component to show on your homepage.

The component will output whatever information you need to display about our particular product. You can create an `IProduct` interface that matches [Snipcart's product definition](https://docs.snipcart.com/v3/setup/products) and an `IProductProps` interface to define the types of our props, which will be pass as a parameter to the function.

```tsx
// components/Product.tsx

export interface IProduct {
    id: string
    name: string
    price: number
    url: string
    description: string
    image: StaticImageData
}
```

Underneath the interface, add this component:

```tsx
// components/Product.tsx

interface IProductProps {
    product: IProduct
}

const Product = (props: IProductProps) => {
    return (
        <div className={styles.product}>
            <h2 className={styles.product__title}>{props.product.name}</h2>
            <p className={styles.product__description}>{props.product.description}</p>
            <div className={styles.product__image}>
            <Image src={props.product.image} alt={props.product.image.src} />
            </div>
            <div className="product__price-button-container">
                <div className={styles.product__price}>${props.product.price.toFixed(2)}</div>
                <button
                    className={`snipcart-add-item ${styles.product__button}`}
                    data-item-id={props.product.id}
                    data-item-name={props.product.name}
                    data-item-price={props.product.price}
                    data-item-url={props.product.url}
                    data-item-image={props.product.image.src}>
                    Add to cart
                </button>
            </div>
        </div>
    )
}
```

### A note on the Image component

Notice in the block below we are using Next.js's [Image component](https://nextjs.org/docs/basic-features/image-optimization) instead of a good ol' `img` tag. The former is actually an extension of the latter. It allows automatic image optimization, lazy loading by default, and providing images in [WebP](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types) when the browser allows it, which optimizes images to the client device. Moreover, the component optimizes image on requests, which saves you build time. This helps to reduce your website loading time and thus keep your users' interest! 

### 2. The product list component

We will integrate this product component into a `ProductList` component, whose name is quite self-explanatory. The `ProductList.tsx` component will be used to display our list of products on the homepage. Therefore, you can create an `IProductListProps` interface that describes an array of `IProduct`, which is eventually going to be passed by our website:

```tsx
import Product, {IProduct} from "./Product";

interface IProductListProps {
    products: IProduct[]
}

const ProductList = (props: IProductListProps) => {
    return (
        <div className="product-list">
            {props.products.map((product, index) => <Product product={product} key={index}/>)}
        </div>
    )
}

export default ProductList
```

### 4. Pre-rendering data and importing components

At this stage, you'll probably want to populate your products to the ProductList component. In pure React, you could use [React's useEffect lifecycle](https://reactjs.org/docs/hooks-reference.html#useeffect) inside the `ProductList` to fill the data. However, this method won't get called on the server during a static or server-side rendering.

Thankfully Next.js adds two ways to pre-render the data: `getStaticProps`, which fetches data at build time, and `getServerSideProps`, which fetches data *on each request*. The latter may be useful, for example, for an auction store where the price may be rapidly fluctuating. In our use case, since the product doesn't change often, we will use the former as the pre-rendering will decrease loading time by saving the user a request.

```tsx
<main className="main">
	<Jumbotron />
	<ProductList products={products}/>
	<Contact/>
</main>

export const products: IProduct[] = [
    {
        id: "halfmoon",
        name: "Halfmoon Betta",
        price: 25.00,
        image: halfmoonBettaPicture,
        description: "The Halfmoon betta is arguably one of the prettiest betta species. It is recognized by its large tail that can flare up to 180 degrees.",
        url: '/api/products/halfmoon'
    },
    (...)
    {
        id: "veiltail",
        name: "Veiltail Betta",
        price: 5.00,
        image: veiltailBettaPicture,
        description: "By far the most common betta fish. You can recognize it by its long tail aiming downwards.",
        url: '/api/products/veiltail'
    }
]

export const getStaticProps: GetStaticProps = async (context) => {
    return {
        props: {
            products
        }
    }
}
```

### 5. Importing Snipcart

Now, let's install Snipcart into our website. First, you'll need to import the `Head` component from next/head inside your index.tsx page, which will allow you to add HTML inside the `<Head>` element.

You can do so by adding the following code inside the Index function return clause:

```tsx
// pages/index.tsx
<Head>
    <title>My awesome store</title>
    <link rel="preconnect" href="<https://app.snipcart.com>"/>
    <link rel="preconnect" href="<https://cdn.snipcart.com>"/>
    <link rel="stylesheet" href="<https://cdn.snipcart.com/themes/v3.2.0/default/snipcart.css>"/>
    <link rel="shortcut icon" href="../public/favicon.ico" />
</Head>
```

> For more information on how to use the `Head` component to optimize for SEO, have a look at our [article regarding SEO using Next.js](https://snipcart.com/blog/react-nextjs-single-page-application-seo).

We now need to load Snipcart's script content. Next.js offers a Script component in the `next/script`, module to do so. It allows for performance optimization by offering [different loading strategies](https://nextjs.org/docs/basic-features/script).

```tsx
// pages/index.tsx
<script 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>
```

Don't forget to swap the `data-api-key` attribute with your own API key ;)

### 6. Product validation

Now that Snipcart is installed, the last step before completing orders is to validate your products.

### 1. HTML validation

The first way to do that is by simply changing the URL in your product list to `/` for each product to the homepage for Snipcart's [HTML validation](https://docs.snipcart.com/v3/setup/order-validation). It will read the `/` on our homepage and crawl it in order to validate the products if you want. You can do just that and skip to the next section, and you will have a working e-commerce site!

If you are curious, let's take the opportunity to check out a neat Next.js feature:
[serverless API routes](https://nextjs.org/docs/api-routes/dynamic-api-routes) combined with Snipcart's [JSON validation](https://babeljs.io/).

### 2. JSON validation using Next.js serverless API

For more complex scenarios, it might be useful to have an API returning our products information in JSON format. To do so, we will need to have a unique URL for each product that will return its information in a JSON file.

1. Configuring static API routes

 While technically we only need a dynamic API route returning each product, let's make this API [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) and have a route returning the whole product list.

You may have noticed that an API folder was created with the project. In this folder, create another one called `products` and add an `index.ts` file to it with the following content:

```tsx
// In pages/api/products/index.ts

import {NextApiRequest, NextApiResponse} from "next";
import {products} from "../../index";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json(products);
}
```

If you now go to `https://localhost:3000/${YOUR_PORT}`, you will get a JSON file containing your product list.

1. Configuring dynamic API routes

In the `products` folder, add the following to the `[productId].ts` file. Notice the brackets. This special syntax tells Next.js that [productid] is a dynamic parameter. Hence if you go to `/api/products/ONE_OF_YOUR_PRODUCTS_ID`, you should get the JSON information of one of our products.

```tsx
import {NextApiRequest, NextApiResponse} from "next";
import {products} from "../../index";
import {IProduct} from "../../../components/Product";

export interface ISnipcartProduct {
    id: string
    name: string
    price: number
    url: string
    description: string
    image: string // Hack to pass the image URL instead of the StaticImage object we required
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
    const {productId} = req.query;
    const product: IProduct | undefined = products.find(p => p.id === productId);
    if (!product) {
		res.status(404).json({});
		return ;
    }
    const snipcartProduct: ISnipcartProduct = {...product, image: product?.image.src ?? ""}

    res.status(200).json(snipcartProduct);
}
```

You should now be able to complete a test order! 

It's time to style our website, so it's more appealing to our future customers.

### 7. Styling your Next.js SPA

If you paid attention, you saw that most of the components in this tutorial already had classnames. We will now look at 2 different ways to apply them:

### 1. Setting up a global stylesheet

In the `style`s folder, create a `global.scss` style sheet. Afterwards, simply import it to `pages/_app.tsx`:

```tsx
// in pages/_app.tsx

import "../styles/globals.scss";
```

Global stylesheets can only be imported in the `_app.tsx` file.
I used SCSS, which does not come built-in with Next.js, but can be easily integrated simply by running `npm install sass`.

### 2. Setting up CSS modules for our components

Next.js also supports CSS modules, which can become quite handy if your CSS file gets larger. To use it, simply create a file respecting the `[name].module.css` convention, for example, `Product.module.css` or `Product.module.scss`.

Afterward, you can import it as a `styles` object in the component's file and access the styles with it:

```tsx
import styles from '../styles/Product.module.scss';
(...)

const Product = (props: IProductProps) => {
  return (
      <div className={styles.product}>
          <h2 className={styles.product__title}>{props.product.name}</h2>
          <p className={styles.product__description}>{props.product.description}</p>
      (...)
  )
}
```

For further examples of how these styles are applied, have a look at the project:

- [**Github Repo**](https://github.com/snipcart/snipcart-nextjs-spa)
- [**Demo**](https://snipcart-next-2021.vercel.app/)

> Allow me a shout-out to Michael from Snipcart, who came up with the neat UI design!

And voilà! You're server-side rendered Next.js e-commerce store should be ready to go.

### Closing thoughts

I liked how easy it was to create a static website with great performance using Next.js. I did notice some parts of the Next.js documentation could be more up-to-date.

We could have explored Image optimization on mobile using the `Image` component or Next's dynamic imports to push this demo further.

Are you up to it? If so, let us know how it goes in the comments below!

---

Liked this article? Hit the share buttons below.