DEV Community

loading...

Next.js E-Commerce Tutorial: SPA Example

Pierre-Guillaume Laurin
Building things @ Visao
Originally published at snipcart.com Updated on ・12 min read

In a rush? Skip to the tutorial or live example

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.

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

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:

It was built by Zeit (now Vercel) back in 2016 and has quickly gained traction to the point of becoming one of the most popular tools 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

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 and transcompilation.

The power of the Jamstack right here!

We’ve covered the general React e-commerce ecosystem 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 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

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

Technical tutorial: a Next.js e-commerce SPA

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

Pre-requisites

Basic knowledge of React & TypeScript 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:

// 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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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:

// 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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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:

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 />
      </>
  )
}
Enter fullscreen mode Exit fullscreen mode

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

// _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
  return <>
    <Layout>
      <Component {...pageProps} />
    </Layout>
    </>
}
Enter fullscreen mode Exit fullscreen mode

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 and an IProductProps interface to define the types of our props, which will be pass as a parameter to the function.

// components/Product.tsx

export interface IProduct {
    id: string
    name: string
    price: number
    url: string
    description: string
    image: StaticImageData
}
Enter fullscreen mode Exit fullscreen mode

Underneath the interface, add this component:

// 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>
    )
}
Enter fullscreen mode Exit fullscreen mode

A note on the Image component

Notice in the block below we are using Next.js's Image component 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 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:

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
Enter fullscreen mode Exit fullscreen mode

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

<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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

// 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>
Enter fullscreen mode Exit fullscreen mode

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.

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.

// 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>
Enter fullscreen mode Exit fullscreen mode

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. 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 combined with Snipcart's JSON validation.

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 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:

// 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);
}
Enter fullscreen mode Exit fullscreen mode

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.

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);
}
Enter fullscreen mode Exit fullscreen mode

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 styles folder, create a global.scss style sheet. Afterwards, simply import it to pages/_app.tsx:

// in pages/_app.tsx

import "../styles/globals.scss";
Enter fullscreen mode Exit fullscreen mode

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:

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>
      (...)
  )
}
Enter fullscreen mode Exit fullscreen mode

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

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.

Discussion (7)

Collapse
payapula profile image
payapula

Great Article. 👏🏼

One question on the getStaticProps and getServerSideProps.

getStaticProps would be called only at build time, since we are using that here, if we update our database with new products it would be fetched only on fresh build.

If we have used getServerSideProps with API routes, for the above use case, the new products would be reflected on a page reload/new request. Is my understanding is correct?

Collapse
pierreguillaumelaurin profile image
Pierre-Guillaume Laurin Author

@payapula much thanks! 🙇

Indeed getStaticProps will be called at build time, independently of database updates. Although you can use the  revalidate option to refresh the page after a certain delay.

getServerSideProps will pre-render the page on each request as you said, which is most often a good approach if your use case requires very frequent database updates.

So your understanding was indeed correct! 🙂

Collapse
zakiazfar profile image
Mohd Ahmad

how can we persist state when while client side routing

Collapse
pierreguillaumelaurin profile image
Pierre-Guillaume Laurin Author

Hi Mohd! You can setup a custom App component just like we did in Step 2 of this tutorial (integrating components), except you can add your state hooks to it.

Other solutions you might want to explore include shallow routing that enables you to change the URL without fetching data again! Some npm modules I haven't had the chance to try are also starting to be developed if you want to look into this.

If you try any of these solutions, please let me know how it goes!

Collapse
zakiazfar profile image
Mohd Ahmad

I found it, it is already present in next js. It does not work in dev environment where getStaticProps runs on each request

Collapse
monfernape profile image
Usman Khalil

I recently worked on a similar stuff but struggled with header. How do we make header dynamic? I wanted to display different options to logged in user but NEXT won't rerender the layout again.

Collapse
pierreguillaumelaurin profile image
Pierre-Guillaume Laurin Author

Hi Usman,

Depending on your use case, you could use hooks in your custom App component and then pass them to your header, or shallow routing to change your route's query parameter and then, here also, pass them to your header.

Let me know how it goes!