Lab - Set Up a Next.js Frontend

Plan: Composable Developer

Lesson 10 of 27 · 30 min

Introduction

Throughout this course, you will build a Next.js storefront for your BigCommerce store, with basic features demonstrating key aspects of commerce workflow.

Next.js is one of the most popular and rapidly evolving web application frameworks, based on React, with a feature set ideal for composable architecture. It’s the framework of choice for Catalyst, as well as thousands of other popular sites and applications.

In this lab, you will:

  • Provision a starter Next.js site in a local dev environment connected to your BigCommerce store
  • Review the starter architecture, including a basic GraphQL client and a basic “store settings” query

Prerequisites

  • The Git CLI (depending on setup method)
  • An IDE or code editor, like VS Code
  • Node.js 20 or later
  • The pnpm package manager
  • Basic understanding of React

Node Version Manager (nvm) is an alternative way to install Node.js and allows you to install and use different versions.

With nvm installed, you can install a specific version: nvm install 18.18

… and switch to the Node.js runtime of any version you have installed: nvm use 18.18

Whether you use nvm or install Node.js via another method, make sure you are running a compatible version: node -v

From Previous Labs

You will need the following prerequisites generated or collected in previous labs.

  • Your BigCommerce store hash
  • The ID of your headless channel
  • A Private Token for server-to-server authentication with the GraphQL Storefront API (the private_token from the previous lab)

Setup

  1. Run the following, replacing the path to the working directory with your own path:
corepack enable pnpm && pnpm dlx create-next-app@latest -e https://github.com/bigcommerce-edu/lab-nextjs-storefront/tree/bcu-starter /path/to/working/directory

This will create the initial, stripped-down Next.js project from the example repository, including installing all npm dependencies.

The main branch of the project repository contains the completed code for all labs, so make sure you don’t attempt setup by cloning that as your initial state!

Troubleshooting

The pnpm command is unrecognized. In some environments where file permissions are highly restricted, you may need to prepend corepack to the use of pnpm commands (for example, corepack pnpm dlx create-next-app@latest ...)

  1. Open your project in your IDE of choice.
  2. Copy .env.example as .env.local.
  3. Enter your unique config values in .env.local.
VariableValue
BIGCOMMERCE_STORE_HASHYour store hash
BIGCOMMERCE_CHANNEL_IDYour headless channel ID
BIGCOMMERCE_STOREFRONT_TOKENYour Private Token (the private_token from the previous lab)

This app makes server-side GraphQL requests, so it requires a Private Token (not a Storefront token with CORS). Use the private_token value from the previous lab. Next.js apps can use sessions or cookies for stateful behavior in the browser; this setup does not—it is headless. Later labs add cookies for cart and customer session.

  1. Start the dev server process:
cd /path/to/working/directory
pnpm run dev
  1. Browse to the local URL printed in the npm output (usually http://localhost:3000).

You should now see a basic home page, complete with your own store’s logo image or text.

Troubleshooting

If you encounter an error while starting the dev server related to an issue finding a matching keyid, you may need to upgrade a pre-existing corepack version.

npm install -g corepack@latest

Review API Clients and First Query

The starter codebase for your storefront already includes a basic API client for GraphQL. Let’s take a look at their architecture.

  1. Open the file lib/bc-client/bc-client-gql.ts. This defines a bcGqlFetch function that accepts a query string and a variables object.
...
export async function bcGqlFetch<RespType, VarsType>(
query: string,
variables?: VarsType,
customerToken?: string
): Promise<RespType> {
...
const result = await fetch(
`https://store-${BIGCOMMERCE_STORE_HASH}-${BIGCOMMERCE_CHANNEL_ID}.mybigcommerce.com/graphql`,
{
method: 'POST',
headers: {
...
Authorization: `Bearer ${BIGCOMMERCE_STOREFRONT_TOKEN}`,
...(customerToken && { 'X-Bc-Customer-Access-Token': customerToken }),
},
body: JSON.stringify({
query,
...(variables && { variables }),
}),
}
).then(res => res.json());
...
return result satisfies RespType;
}

The function utilizes the store hash, channel ID, and storefront token from your config.

Also note the use of the type variables (between angle brackets) in the various overloads of the function signature. These allow the calling context to pass in Typescript types describing the expected shape of any GraphQL variables being passed in, and the expected shape of the response.

The function also supports a customerToken parameter, passing this in the X-Bc-Customer-Access-Token header if a value exists.

GraphQL Type Safety

Note that the Typescript satisfies operator used above declares what type the response body is expected to conform to, but no actual response validation is being done here! While type safety for API responses is outside the scope of the exercise, there are different possible solutions that avoid the need for laborious checks.

Catalyst utilizes a pattern involving gql.tada for auto-generating type information.

Zod is another tool that allows for easy schema validation using type objects that closely mimic standard type expressions.

  1. Open the file components/header/_data/component-data.ts. The GraphQL fetch that populates the store header is located here. Take a look at the GraphQL query string, types, and function defined here.
...
const getHeaderSettingsQuery = `
query GetSettings($logoSize: Int!) {
site {
settings {
storeName
logoV2 {
... on StoreTextLogo {
text
}
... on StoreImageLogo {
image {
url(width: $logoSize)
}
}
}
}
}
}
`;
interface GetHeaderSettingsVars {
logoSize: number;
}
interface GetHeaderSettingsResp {
data: {
site: {
settings: {
...
}
}
}
}
/**
* Fetch basic store settings for the header
*/
export const getHeaderSettings = cache(async ({
customerToken,
}: {
customerToken?: string,
}) => {
const settingsResp = await bcGqlFetch<GetHeaderSettingsResp, GetHeaderSettingsVars>(
getHeaderSettingsQuery,
{
logoSize: 500,
},
customerToken
);
const settings = settingsResp.data.site.settings;
return ...
});

First, you have a simple string defining a GraphQL query for your store’s logo information. (This will be expanded later with details beyond store settings.)

The various types define the expected shape of the variables passed to the query and the response returned from BigCommerce. These types are passed along with the query string and variables to the generic bcGqlFetch function, so that Typescript understands the data involved.

The Header Component

The final piece to examine in our survey of the app’s starter architecture is the Header component, which is how the store logo finally makes it into the page layout.

  1. Open the file components/header/index.tsx. Observe the use of getHeaderSettings to fetch the required data.
...
const Header = async () => {
const { settings } = await getHeaderSettings({});
const emptySettings = { logoImageUrl: null, logoText: null, storeName: null };
const { logoImageUrl, logoText, storeName } = settings ?? emptySettings;
return (
...
)
}
...

Header, like all components by default with the App Router architecture, is a React Server Component. This means that the fetch being done with getHeaderSettings is performed securely at the server before frontend content is rendered, not in JavaScript in the user’s browser.

  1. Open the file app/layout.tsx. The component structure in this file applies to all pages on the storefront and is where Header is placed.
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html ...>
<body
...
>
<main
...
>
<Header />
{children}
</main>
</body>
</html>
);
}

Resources