Lab - Build a Basic Cart Workflow
Introduction
This exercise will expand on your basic Next.js storefront with the capability to add products to a cart and check out.
In this lab, you will:
- Implement Add to Cart functionality on product detail pages
- Add a mini-cart to the storefront header, displaying basic cart details
- Create a simple cart page
- Use a checkout redirect URL to navigate customers to checkout on BigCommerce
Prerequisites
This exercise builds on the basic Next.js application begun in previous labs and has the same technical requirements.
Your store should have the following catalog data associated with your headless channel:
- Products without variant options or required modifier options
The simple cart workflow you’ll build in the exercise will not support products that require option selections.
Setup
This exercise continues where you left off previously in your basic Next.js project. Simply start the dev server from the project root if it’s not currently running:
If you need a fresh start, you can follow the instructions below to set up a new project complete with previous exercise code.
- In your terminal, run the following, replacing the path to the working directory with your own path:
- Copy
.env.exampleas.env.localand modify.env.localwith configuration details for your store. - Start the dev server process:
- Browse to the local URL printed in the
pnpmoutput and verify your catalog pages load successfully.
Exercise Reference Copy
You may choose to download or clone the completed version of this lab in a separate location to serve as a reference for the final state.
Create the Add to Cart Button
The objective of this section is to enable customers to add a product to a cart from the product detail page. This will require not one but two GraphQL mutations (for creating a new cart or adding an item to an existing one), as well as tracking the user’s cart ID using a cookie.
We’ll start with the button itself.
- Open the file
components/product/add-to-cart.tsx. and update the component as follows.
Note that the new product prop accepted by the component is expected to match the interface previously defined.
A very simple “loading” state in the component will give the user feedback while the request executes.
Finally, notice the directive 'use client' located at the top of the file. Because this component involves interactivity (responding to a button click), this directive declares it as a client component, with the JSX and logic being executed in the browser.
- In the same file, fill in the logic for
onClick, which will set the loading state, call the appropriate function, then un-set the loading state.
Despite being browser-executed JavaScript, this event handler calls the function addProductToCart directly to execute the proper GraphQL request.
In the file where this function is defined - components/product/_actions/add-product-to-cart.ts - the directive 'use server' declares it as a Server Function. When executed by the browser, the result is actually a network request, with addProductToCart being run at the server. This is a convenient pattern for running server-side logic from the client without the need for manually creating API routes.
We’re not filling in the logic for addProductToCart yet, but it currently doesn’t accept the productId param being passed to it.
- Open the file
components/product/_actions/add-product-to-cart.tsand modify the function as follows.
- In
app/product/[...productPath]/page.tsx, add the appropriate component placement.
- Browse to a product page and verify that the “Add to Cart” button displays. You can interact with the button and initiate a successful request, but this will currently have no effect.
Implement Add to Cart Logic
- Open the file
types/cart.tsand fill in the definitions ofBasicCartandCartFragment.
In this case, you’ve included a GraphQL fragment because this fragment will be needed by mutations in two separate files.
- Open the file
components/product/_actions/add-product-to-cart.tsand add the GraphQL and vars/response types for creating a new cart.
- In the same file, add the
createCartfunction to perform the mutation and return the resulting cart data.
The createCart function will be appropriate for when the user has no pre-existing cart. You’ll need a separate function, similar in most respects, for a different mutation when a cart exists.
- In the same file, add the mutation and type definitions.
You can see that the structure is nearly identical to that of createCart. The chief distinction is the inclusion of a cartId argument for the mutation.
- In the same file, add
addCartLineItemto perform the mutation.
Finally, you’ll need the existing addProductToCart function to act as the main controller for this operation, deciding on whether to create a cart or add to an existing one.
- In the same file, update the
addProductToCartfunction as follows.
The above code is doing several important things:
- A couple of helper functions defined in
lib/cookies.tsare being used to appropriately prefix the cookie name based on whether HTTPS is being used. - The
addCartLineItemmutation is tried if the user has a “cartId” cookie. - If no
cartIdcookie was set or the attempt to add an item failed, thecreateCartmutation is used to generate a new cart with the item. - The ID returned in the cart data is set on the
cartIdcookie. - The basic cart data is returned from the function
- Browse to the detail page of a product without required options and test the Add to Cart button. While you’ll see no change in the storefront yet to indicate your new cart, you should be able to use your browser’s developer tools to verify the value of your
cartIdcookie.
Display Cart in the Header
The storefront not only displays no status update indicating an item has been added to the cart, but also provides no navigation to the cart itself. Let’s implement a mini-cart in the site header to meet both objectives.
The mini-cart will require fetching data matching the user’s cart ID using another GraphQL query, and this data will be required on every page of the storefront. While you might consider including this cart data in the same fetch populating the rest of the header, these details are both user-specific and non-essential for the main content on any given page. Decoupling this into its own component will give you more flexibility in the future for strategies such as caching or partial prerendering.
- Open the file
components/mini-cart/index.tsxand update the component as follows.
The component does the work of checking for a cart ID from the appropriate cookie and passing this to an appropriate data fetching function.
- In
components/header/index.tsx, add theMiniCartcomponent.
The new mini-cart will display a simple cart icon if there is no current cart; the total quantity and grand total will be added to this if cart data is loaded.
Now let’s set up the data fetching.
- Open the file
components/mini-cart/_data/component-data.ts. The logic for thegetCartquery will look similar to that of thecreateCartandaddCartLineItemmutations, just without the need for any arguments. Add the GraphQL query and type definitions.
- Update the
getCartfunction to perform the query.
- Refresh your current page. If you previously added a product to your cart, you should see the header mini-cart information reflect your item total and cart subtotal.
- Browse to a product page for a product with no required options and add it to the cart. Observe that the mini-cart information will be updated accordingly after the operation is complete.
Implement the Cart Details Query
The header mini-cart already links to the path /cart, which is currently a blank page.
The information to be displayed on the cart page includes more details than in the header - namely, the cart line items. These further details are handled with a separate query.
- In
types/cart.ts, fill in the details of theCartItemandBasicCartDetailsinterfaces.
- Open the file
app/cart/page-data.tsand add the query and type definitions.
Note that separate GraphQL types must be queried for physicalItems and digitalItems, but the same fields are queried for both.
- In the same file, modify the
getCartDetailsfunction to perform the query.
- Open the file
app/cart/page.tsx, where we’ll start with the same initial logging step we’ve done for previous pages. Modify the page component to fetch and log the cart details.
- With at least one product added to the cart, browse to the cart page by clicking the icon in the site header and verify that the expected cart details are written to the terminal.
Build the Cart Page
- Open the file
components/cart/item-row.tsx. This component will be used to display an individual line item on the cart page. Update the component definition as follows.
- Open the file
app/cart/page.tsxand update the component logic to render a simple “no items” message if no cart was loaded. We’ll also set up the currency formatter that will be needed for displaying the cart total. (Don’t forget to remove the previous logging statement.)
- Update the
CartPagecomponent with the appropriate content.
- Refresh the cart page and verify that your cart details are displayed.
Redirect to Checkout
For completion of checkout, your storefront will redirect users to the BigCommerce storefront using the checkout URL set on your Channel Site. (Unless you have updated it, this will be the URL of your store’s default channel.)
- Open the file
app/cart/page-data.tsand add the query and type definitions.
- Update the definition of
createCartRedirect.
- In
app/cart/page.tsx, update the component logic to fetch and return the checkout redirect URL.
- In the same file, update the
CartPagecomponent to add a “Proceed to Checkout” link in the JSX.
- Browse to your cart page with products in the cart, and test out the new “Proceed to Cart” link. You should be redirected to the BigCommerce storefront with your cart items populated.
The Effects of the Checkout Base URL
The base URL used to generate the cart redirect URLs depends on the checkout URL set on the Channel Site. In production, it is important to make sure you have correctly configured this URL for the channel associated with your headless storefront using the Update a Channel Site API endpoint, or you will not get correct redirect URLs.
Remember that, for these lab exercises, we have left the default checkout URL in place, meaning the shopper arrives at the store’s default Stencil storefront for checkout.
The configured checkout URL will affect the experience of getting back to the main storefront. You may notice that after arriving on the order confirmation page in your sandbox environment, the links on the page (such as the main store logo) navigate to your default Stencil storefront.
In production with a single-channel setup and properly configured domains, all navigation links on the order confirmation page should properly take the customer back to the headless storefront. As a reminder, you should update both the primary and checkout URLs on the Channel Site for this behavior:
- primary: Set this to the domain where your headless storefront is deployed (for example,
mystore.com). With this in place, the BigCommerce hosted storefront will know what base URL to use for the links generated on the order confirmation page. - checkout: A common strategy is to use a subdomain (for example,
checkout.mystore.com) and configure DNS for this subdomain to point to your headless channel’s canonical BigCommerce URL.
If you do legitimately need to redirect shoppers to a different channel for checkout in production, then additional customization will be necessary to direct them back to the proper storefront after placing an order.
Full Lab Code
Taking It Further
This cart implementation is a good demonstration of the essential workflows involved, but to be truly complete, a few things are still missing. Try the following on your own if you like.
- Add the ability to update item quantities and delete items from the cart page. This will require new server functions for the right mutations and interaction in the
CartItemRowcomponent. - Implement support for product options. Add *productOptions *to the main product query, then build a form with the appropriate controls on the product detail page. (The control required for a given option will depend on its GraphQL
__typenamein the returned data.) Capture selected options and translate them into the appropriate arguments in the Add to Cart mutations.