Storyblok Raises $80M Series C - Read News

What’s the True Total Price of Enterprise CMS? Find out here.

Skip to main content

How to Build a Storefront with Nuxt and BigCommerce

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

Have you wondered how to integrate Storyblok with an e-commerce solution like BigCommerce and build a Storefront using Nuxt SDK?

In this tutorial, we are going to build this store and go step by step from 0 to a showcase of your products in a modern setup.

You can see the final result here https://storyblok-nuxt-bigcommerce-starter.netlify.app/

if you are in a hurry, you can check the whole source code of the project here github.com/storyblok/big-commerce-nuxt-starter

Storyblok Space

Sign up for a free Storyblok account & choose Create a new space {1}. Then name your Storefront {2}, and click the create space {3} button.

Create a new Space
1
2
3

Once you created a new space, follow our eCommerce Storefront documentation for setting up your storefront in Storyblok.

Storefront using Nuxt

We have prepared a convenient Nuxt starter template to show you how to connect the eCommerce plugin with the eCommerce API to build your storefront. For this specific example, we are going to use BigCommerce API as our eCommerce provider.

Start by cloning the starter repository.

        
      git clone https://github.com/storyblok/big-commerce-nuxt-starter.git
    

Once you have the source code in your local, make sure to install the correspondent dependencies using your package manager of choice npm install .

Connecting your Storyblok Space with the Storefront

To connect our recently created space with our Storyblok instance, jump to the Space Settings {1} and navigate to Access Token {2} to get your credentials.

warn:

To make sure that the visual editor functions correctly, run the server with https enabled.

Add a Location (default environment) {3} pointing to your https://localhost:3000/ which is the default Nuxt server.

Storyblok Settings
1
2
3

Rename the .env-template file into .env and add both your Storyblok Token {2} and your BigCommerce credentials.

        
      BIGCOMMERCE_URL=https://my-brand-store.mybigcommerce.com
BIGCOMMERCE_TOKEN=eyJ0...zA
STORYBLOK_TOKEN=zTa...Lwtt
    

Then pass your Storyblok Token reference to your Nuxt module configuration

nuxt.config.ts
        
      export default defineNuxtConfig({
  modules: [
    [
      '@storyblok/nuxt', {
        accessToken: process.env.STORYBLOK_TOKEN,
      },
    ], 
  ],
})
    

Nuxt provides a handy runtime config API to expose your configuration within your application and server routes. We'll place our BigCommerce credentials here:

nuxt.config.ts
        
      export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      bigCommerce: {
        url: process.env.BIGCOMMERCE_URL,
        token: process.env.BIGCOMMERCE_TOKEN,
      },
    },
  },
})
    

If the API is CORS restricted, you might need to create a separate token for your localhost/production server. In the case of BigCommerce & Nuxt.js, you need to create a new token for https://localhost:3000 for the local development and then create a different token with the real URL once you deploy your store.

After you finish setting up your tokens, we are ready to start. Launch that development server:

        
      npm run dev
    

Changing the Real Path Field

When you open your Home Page in Storyblok, you will see a page not found error. Since Storyblok automatically creates a /home path from the Home story, you want to redirect that to your base URL in the Nuxt application / which uses pages/index.vue component template.

To fix this routing issue, change the real path of your homepage. When you open the home entry, you should already see the running localhost on port 3000 {1}. Now navigate to the Entry Configuration tab {2} and change the Real path field to / {3}.

Changing  the real path for home page on Storyblok visual editor
1
2
3

Reload the page and you should see your Nuxt application in the preview space. Be sure you hit the Save button before reloading. You probably notice that there is only a header component with a Storyblok Store logo and the rest is blank. This is because we haven’t yet created the block schemas to pair with the available components in the starter project.

Setting up the Storyblok Components

For this demo, we are going to build a home page with the following components:

  • A Hero component
  • A Single Product section to show a featured product
  • A Product Grid to showcase the latest products

Hero

This component consists of the following fields:

Hero blok fields
1
2
3
  1. A headline text field. {1}
  2. A subheadline to add a text to invite the user to the store. {2}
  3. A background image of the store. {3}

Once created and added to our Home page, write down the headline and upload an image in the asset field, then click Save. The page should reload and the hero component should appear. Change the headline again to make sure the preview in the Visual Editor is working just fine. You should be seeing something like this:

Hero blok inside visual editorHere is the code for the hero component in Nuxt.

/storyblok/Hero.vue
        
      <script setup lang="ts">
	defineProps({ blok: Object })
</script>

<template>
  <div class="relative md:h-400px overflow-hidden flex justify-end flex-wrap-reverse">
    <div class="container px-4 md:px-0 mx-auto h-full ">
      <div class="flex flex-col justify-center w-full md:w-1/2 h-full">
        <h1 class="text-4xl font-bold text-dark mb-8">
          {{ blok.headline }}
        </h1>
        <p class="text-gray-400">
          {{ blok.subheadline }}
        </p>
        <footer>
          <button class="text-black px-4 py-2 rounded-lg mt-8 inline-block">
            Shop now
          </button>
        </footer>
      </div>
    </div>
    <div
      class="w-2/3 md:w-1/2 md:absolute right-0 top-0 mb-8"
      style="clip-path:polygon(10% 0,100% 0,100% 100%,0 100%)"
    >
      <NuxtImg
        :src="blok.image.filename"
      />
    </div>
  </div>
</template>
    

Single Product

This component showcases a single product retrieved from the eCommerce integration with BigCommerce:

1
2
3
4

Let’s add a Headline field of type Text {1}, a Textarea field for the Description {2} and a Text or Color (Plugin) {3} field for the background of the product image (optional). To get the actual product we need to set up the eCommerce integration {4} with a couple of more steps:

Create eCommerce storyblok plugin field typeCreate a new tab called Integration and inside, add a field of type Plugin . Now click on the recently created field to configure it:

Setting up eCommerce field plugin type variables in Storyblok
1
2
3

On the Custom Type Dropdown {1} select the sb-bigcommerce plugin. Next fill up the options {2} with your BigCommerce credentials:

  • endpoint: Your storefront URL
  • token: Your storefront API token
  • limit: How many items do you want the content editor to be able to select.
  • selectonly: set this product (this limits the selection to either products or categories)

Once you click Save & Back to Fields {3} try to add a product from your store clicking on Select Products, this will open a special modal window. If everything is set up correctly, you should be able to see all your products listed and be able to select one of them.

eCommerce integration modal select productsEt voilá, you managed to get your product showcased on a component right there on the page.

Here is the code for the Nuxt component counterpart:

/storyblok/SingleProduct.vue
        
      <script setup lang="ts">
const props = defineProps({ blok: Object })

const product = computed(() => props.blok['bigcommerce_product'].items[0])
const bgColor = computed(() => props.blok['bg_color'].color)
</script>

<template>
  <section>
    <div class="container py-16 md:py-32 px-4 md:px-0 mx-auto grid grid-cols-1 md:grid-cols-2 gap-8">
      <NuxtImg
        v-if="product"
        class="rounded-lg"
        :style="{ backgroundColor: bgColor }"
        :src="product.image"
        :alt="blok.name"
      />
      <div>
        <h2 class="font-bold text-4xl mb-8 text-gray-700">
          {{ blok.headline }}
        </h2>
        <h3 class="font-bold text-2xl mb-4 text-gray-700">
          {{ product.name }}
        </h3>
        <p>{{ blok.description }}</p>
        <div class="mt-8">
          <button
            class="bg-gray-800 text-white px-4 py-2 rounded-lg"
          >
            Add to cart 
          </button>
        </div>
      </div>
    </div>
  </section>
</template>
    

Product Grid

Now that you know how to set up the eCommerce plugin field type, try to replicate the same on this Blok by yourself. Remember to change the limit of products you can select in the options to be more than 1. You should end with something looking similar to this:

product-grid Storyblok Bigcommerce integrationHere is the code for the component on your Nuxt starter:

/storyblok/ProductGrid.vue
        
      <script setup lang="ts">
const props = defineProps({
  blok: Object,
})

const items = computed(() => props.blok['bigcommerce_products'].items)

const { products, fetchProducts } = useStore()

onMounted(async () => {
  await fetchProducts(items.value.map(item => item.id))
})
</script>

<template>
  <section class="py-16 md:py-32 px-4 md:px-0 flex items-center">
    <div class="container mx-auto ">
      <h2 class="font-bold text-4xl mb-16 text-gray-700">
        {{ blok.headline }}
      </h2>
      <div class="grid grid-cols-1 sm:grid-cols-3 gap-16">
        <div
          v-for="(product, $index) in products"
          :key="product.id"
        >
          <NuxtImg
            class="rounded-lg"
            :style="{ backgroundColor: $index % 2 === 0 ? '#00B3B0' : '#FFB900' }"
            :src="product.thumbnail"
            :alt="product.name"
          />
          <div class="mt-8">
            <NuxtLink
              :to="`/products/${product.id}`"
              class="font-bold text-lg mb-4 text-gray-700"
            >
              {{ product.name }}
            </NuxtLink>
            <p>{{ product.description }}</p>
            <div class="mt-8 flex justify-between flex-wrap">
              <span class="text-sm font-bold flex items-center text-gray-500 p-1.5 rounded">{{ product.price.value }} {{ product.price.currencyCode }}</span>
              <button
                class="bg-gray-800 text-white flex justify-center items-center w-10 h-10 rounded"
              >
                <i class="i-carbon-shopping-cart-plus inline-block" />
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>
    

You might notice that we are using a useStore composable to fetch products based on the ids coming from Storyblok API and then save them in a local state. Why? Let’s find out in the next section.

Fetching product data

If you take a closer look to the JSON we fetch from the Storyblok API, you will notice that the bigcommerce_products field contains and array with bare-minimum necessary info about the product like the id, sku and the image:

        
      {
  "_uid": "20592b1c-8d94-4646-a475-62284d1b1a97",
  "headline": "",
  "component": "product-grid",
  "subheadline": "",
  "bigcommerce_products": {
    "_uid": "cff24b21-826a-49e2-97bf-64cc7079c035",
    "items": [
      {
        "id": 112,
        "sku": "SB-T-HSS",
        "name": "How storytelling scales",
        "type": "product",
        "image": "https://cdn11.bigcommerce.com/s-fmqhavaj36/images/stencil/320w/products/112/381/T-shirt__62666.1684755120.png",
        "description": ""
      },
      {
        "id": 113,
        "sku": "SB-T-SR",
        "name": "Stories on the Road",
        "type": "product",
        "image": "https://cdn11.bigcommerce.com/s-fmqhavaj36/images/stencil/320w/products/113/382/T-shirt-stories-on-the-road__32249.1684755183.png",
        "description": ""
      },
      {
        "id": 114,
        "sku": "SB-T-T",
        "name": "Teamwork",
        "type": "product",
        "image": "https://cdn11.bigcommerce.com/s-fmqhavaj36/images/stencil/320w/products/114/383/T-shirt-teamwork__44875.1684755192.png",
        "description": ""
      }
    ],
    "plugin": "sb-bigcommerce"
  },
  "_editable": "\u003C!--#storyblok#{\"name\": \"product-grid\", \"space\": \"252711\", \"uid\": \"20592b1c-8d94-4646-a475-62284d1b1a97\", \"id\": \"372863931\"}--\u003E"
}
    

If you want to retrieve additional data that would be useful for the end user, such as the price you would need to retrieve that data directly using the GraphQL Storefront API.

So let’s create a composable useStore to save the products on a local state and handle the GraphQL queries:

        
      const state = reactive({
  products: [],
})

export const useStore = () => {

  async function fetchProducts(ids) {
    // fetch products from api
  }

  async function fetchProductsById(ids) {
    // fetch products from api
  }

  return {
    state,
    ...toRefs(state),
    fetchProducts,
    fetchProductById,
  }
}
    

Create a local state using Vue’s reactive with an array property products and some async functions to fetchProducts based on an array of ids and another fetchProductById in case you want to retrieve just one.

The next step is to provide the fetchProducts function with the correct url of the storefront and some http options. Here, the runtime config variables you configured at the beginning of this tutorial will come handy:

        
      export const useStore = () => {
  // Access the runtime config of Nuxt 
  const config = useRuntimeConfig()

	const url = `${config.public.bigCommerce.url}/graphql`

  const baseOptions = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${config.public.bigCommerce.token}`,
    },
  }

// Rest of the code

}
    

Now, time to query some products. First create and options variable and extend the baseOptions + the body with the graphql query.

        
      const options = {
  ...baseOptions,
  body: JSON.stringify({
    query: `query paginateProducts($entityIds: [Int!]!, $pageSize: Int = 3, $cursor: String) {
      site {
        products(entityIds: $entityIds, first: $pageSize, after: $cursor) {
          pageInfo {
            startCursor
            endCursor
          }
          edges {
            cursor
            node {
              id
              entityId
              name
              description
              prices {
                price {
                  value
                  currencyCode
                }
              }
              images{
                edges{
                  node{
                    urlOriginal
                    altText
                  }
                }
              }
            }
          }
        }
      }
    }`,
    variables: {
      entityIds: ids,
    },
  }),
    

In Summary, the query should retrieve products based on the array of ids provided, paginated (3 per page), and with data about the price (value and currency) and some formatting for the product images. Then, the data can be requested and formatted to match what’s expected on the UI.

        
      const response = await fetch(url, { ...options })
const data = await response.json()

const products = data.data.site.products.edges.map(product => ({ 
  id: product.node.entityId,
  name: product.node.name,
  description: product.node.description,
  thumbnail: product.node.images.edges[0].node.urlOriginal,
  price: product.node.prices.price,
}))
    
state.products = products
    

Awesome, now you have everything you need to get the product information on your components.

Conclusion

Connecting two headless-based systems like BigCommerce and Storyblok can greatly enhance your workflow and the user experience. With Storyblok's Visual Editor, you get a great editing experience, while still keeping all your store and product information in your eCommerce system.

The other big advantage is in terms of performance. With a Jamstack-based storefront, you can decrease page loading times and hopefully increase purchases. If you want to learn more about why Storyblok is a great choice as a CMS for your eCommerce experiences, we recommend reading our article CMS for eCommerce.

Author

Alvaro Saburido

Alvaro Saburido

Alvaro, aka Alvarosabu, is a DevRel Engineer at Storyblok. Author of TresJS. He creates a lot of creative content for developers on Youtube, Twitch, and articles on his personal website alvarosaburido.dev