PWA - Next.JS

Next.js is a popular open-source React framework that makes it easy to build fast websites and web applications. To get started using Nacelle with Next.js, follow the quick start guide below or spin up a [Next.js Starter](Next.js Starter) project.

Nacelle’s Next.js accelerator

While we encourage merchant developers to use create-next-app to scaffold new Nacelle x Next projects, Nacelle's Next starter is a viable option for those who prefer a more featureful starting point than a fresh create-next-app project.

To create a new Nacelle Next starter project:

npx degit https://github.com/getnacelle/nacelle-js/starters/next

Scaffold a new Next.js Project

Using create-next-app

To quickly scaffold a Next.js project, use create-next-app. See Next.js Setup Guide for more details.

npx [email protected]

Manual Setup
For more control on the initial setup of a project, follow the Next.js Manual Setup Guide.

Add Nacelle packages

Nacelle provides packages to easily fetch data from a Nacelle Space. We also offer start kits to get started with managing the cart and checkout. To get these packages, run the following command:

npm i @nacelle/storefront-sdk @nacelle/react-hooks @nacelle/shopify-checkout

Environment variables

To use the Nacelle packages, the following environment variables will need to be created. For more information on how Next.js uses environment variables, see the Next.js Environment Variables documentation.

# Nacelle
NEXT_PUBLIC_NACELLE_STOREFRONT_TOKEN="<YOUR_NACELLE_STOREFRONT_TOKEN>"
NEXT_PUBLIC_NACELLE_STOREFRONT_ENDPOINT="<YOUR_NACELLE_STOREFRONT_ENDPOINT>"

# Shopify Checkout (for details, see: https://www.npmjs.com/package/@nacelle/shopify-checkout)
NEXT_PUBLIC_MYSHOPIFY_DOMAIN="<YOUR_MYSHOPIFY_DOMAIN>"
NEXT_PUBLIC_SHOPIFY_STOREFRONT_CHECKOUT_TOKEN="<YOUR_STOREFRONT_CHECKOUT_TOKEN>"
NEXT_PUBLIC_STOREFRONT_API_VERSION="<YOUR_STOREFRONT_API_VERSION>"

Data fetching

The Nacelle Storefront SDK provides multiple methods of getting data from the Nacelle Space. The method used in the examples of this document is a direct query, but you can find others in our Storefront SDK documentation.

For ease of use, it is recommended to instantiate the Storefront SDK client and then export it.

// services/nacelleClient.js

import { Storefront } from '@nacelle/storefront-sdk';

export default new Storefront({
  storefrontEndpoint: process.env.NEXT_PUBLIC_NACELLE_STOREFRONT_ENDPOINT,
  token: process.env.NEXT_PUBLIC_NACELLE_STOREFRONT_TOKEN
});

This will allow the client to be imported wherever it is needed. Next.js provides multiple functions that data can be fetched in. For static generation, getStaticPaths and getStaticProps are explained in the "Static Generation" section of this document. For more details, see Next.js Data Fetching.

Static Generation

There are two key functions when setting up static generation for a Next.js project, getStaticPaths and getStaticProps. More information can be found here, Next.js Page Static Generation, but an example can be found below.

getStaticPaths is used when setting up dynamic routing. In the products page below, a list of product handles is queried from the Nacelle Space and returned as a path.

// pages/products/[handle].js

export async function getStaticPaths() {
  const results = await nacelleClient.query({
    query: HANDLES_QUERY
  });
  const handles = results.products
    .filter((product) => product.content?.handle)
    .map((product) => ({ params: { handle: product.content.handle } }));

  return {
    paths: handles,
    fallback: 'blocking'
  };
}

const HANDLES_QUERY = `
  {
    products {
      content {
        handle
      }
    }
  }
`;

getStaticProps is used when data needs to be provided to the page, in this case, an individual product from the Nacelle Space. Below, the handle obtained during getStaticPaths is used as a variable in the query to get product data. If a product is found, it is returned as a prop. If no product is found, the page returns a 404 error.

// pages/products/[handle].js 

export async function getStaticProps({ params }) {
  const { products } = await nacelleClient.query({
    query: PAGE_QUERY,
    variables: { handle: params.handle }
  });

  if (!products.length) {
    return {
      notFound: true
    };
  }

  return {
    props: {
      product: products[0]
    }
  };
}


const PAGE_QUERY = `
  query ProductPage($handle: String!){
    products(filter: { handles: [$handle] }){
      // fields to return
    }
  }
`;

Cart Management

Nacelle provides a useCart hook from @nacelle/react-hooks for a simple solution to handling cart data. This hook provides the cart line items and various functions to help manage the cart state.

To initialize the cart, add the CartProvider to _app.js, as shown below.

// pages/_app.js

import { CartProvider } from '@nacelle/react-hooks';

function AppContainer({ Component, pageProps }) {
  return (
    <CartProvider>
      <Component {...pageProps} />
    </CartProvider>
  );
}

export default AppContainer;

Below is a brief example of using the useCart hook to add an item to the cart on a product page.

// pages/products/[handle].js

import { useCart } from '@nacelle/react-hooks';

function Product() {
  const [, { addToCart }] = useCart();
  
  const handleAddItem = () => {
    const variant = {
      // product variant fields needed for cart.
    }
    addToCart({
      variant,
      quantity: 1
    })
  }
  
  return (
    <div>
      <button onClick={handleAddItem}>Add to cart</button>
    </div>
  )
}

export default Product;

Here is an example of a cart page, iterating over the line items from cart and setting up a button to clear the cart with the clearCart action, both of which are provided by the useCart hook.

// pages/cart.js

import { useCart } from '@nacelle/react-hooks';

function Cart() {
  const [ { cart }, { clearCart } ] = useCart();
  
  return (
    <div>
      { cart.map((item) => (
        <h2>{ item.variant.title }</h2>
      )}
      <button onClick={ clearCart }>Empty Cart</button>
    </div>
  )
}

export default Cart;

A notice monolithic checkouts

Shopify

Due to the complexity of their monolithic system, Nacelle provides @nacelle/shopify-checkout to generate Shopify checkouts for convenience. This package can be used in tandem with @nacelle/react-hooks and the use checkout hook. Below is how the Shopify Checkout client is initialized and passed to the CheckoutProvider with _app.js.

// pages/_app.js

import { CartProvider, CheckoutProvider } from '@nacelle/react-hooks';
import createShopifyCheckoutClient from '@nacelle/shopify-checkout';

function AppContainer({ Component, pageProps }) {
  const checkoutClient = createShopifyCheckoutClient({
    myshopifyDomain: process.env.NEXT_PUBLIC_MYSHOPIFY_DOMAIN,
    storefrontCheckoutToken:
      process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_CHECKOUT_TOKEN,
    storefrontApiVersion: process.env.NEXT_PUBLIC_STOREFRONT_API_VERSION
  });

  return (
    <CartProvider>
      <CheckoutProvider checkoutClient={checkoutClient}>
        <Component {...pageProps} />
      </CheckoutProvider>
    </CartProvider>
  );
}

export default AppContainer;

With the Shopify Checkout Client set up, the processCheckout action from the useCheckout hook can be used to generate a Shopify Checkout and redirect the user to said checkout. Below is an example of a cart page using cart items from the useCart hook and the processCheckout action from the useCheckout hook.

// pages/cart.js

import { useCart, useCheckout } from '@nacelle/react-hooks';

function Cart() {
  const [{ cart }] = useCart();
  const [, { processCheckout }] = useCheckout();

  const handleProcessCheckout = async () => {
    const cartItems = cart.map((lineItem) => ({
      variantId: lineItem.variant.id,
      quantity: lineItem.quantity
    }));

    await processCheckout({ cartItems })
      .then(({ url, completed }) => {
        if (url && !completed) {
          window.location = url;
        }
      })
      .catch((err) => {
        console.error(err);
      });
  };

  return (
    <div>
      <!-- Cart line items -->
      <button onClick={handleProcessCheckout}>Proceed to Checkout</button>
    </div>
  );
}

export default Cart;