Our Writing

Building a JAMstack shop with Strapi 4, Nuxt 3, Snipcart - part 4.

Gemma

Gemma /

This is part four in our series on how to create a JAMStack e-commerce site with Nuxt 3, Strapi 4 and Snipcart. We will line all the dots up and connect our shop to Snipcart.

Almost there gals and guys! This has been a fun little project for us to do. We’ve learnt a lot ourselves doing the YouTube videos, talking on camera isn’t something that comes naturally to me, but we hope you’ve found it useful. This episode is short and sweet, just like me 🍭🤣 ! This is the final part of this series where it will all come together and your shop will be ready to go. We’re going to connect Snipcart and make it so you can actually buy a sick candle!! Wooooooo, do I hear you all say? If you have no idea what I’m talking about (most people don’t) then here’s the previous episodes in this series:

So as I said this is the is the final step to our shining bright candle shop - Pick a Sick Wick! Snipcart is an e-commerce solution that is fully flexible and works great with JAMStack sites. It’s a dream for designers and devs unlike a lot of things out there! It’s super easy to integrate and all the hard work of setting up a cart and checkout is handled for you. It’s absolutely tropical.

If you don’t have a Snipcart account, then go ahead and create one, it’s very simple you just need to sign up and verify your account.

We now need to import the Snipcart scripts into the head of our pages in our Nuxt 3 frontend. We do this by adding the links and scripts to our Nuxt config. It should now look something like this:

nuxt.config.ts
HTML
import { defineNuxtConfig } from 'nuxt3'

export default defineNuxtConfig({
  // Add entry css file
  css: ['tailwindcss/tailwind.css'],
  build: {
    postcss: {
      // add Postcss options
      postcssOptions: require('./postcss.config.js'),
    },
  },
  meta: {
    link: [
      { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
      { rel: 'preconnect', href: 'https://fonts.gstatic.com' },
      { href: 'https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800;900&family=Source+Sans+Pro:wght@300;400;600;700;900&display=swap', rel: 'stylesheet' },
      { rel: 'preconnect', href: "https://app.snipcart.com" },
      { rel: 'preconnect', href: "https://cdn.snipcart.com" },
      { rel: 'stylesheet', href: "https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.css" },

    ],
    script: [
      { src: 'https://cdn.snipcart.com/themes/v3.2.1/default/snipcart.js', async: true }
    ],
  },

  publicRuntimeConfig: {
    API_URL: process.env.API_URL
  },
})

Next, we need to add a div with your Snipcart API key, you can find the API key in your account dashboard in Snipcart. If you click the user icon on the top left and > API keys

Snipcart API key 🔑

Now, we need to include Snipcart let Snipcart know who we are by adding a special div to all out pages. We will add the div to our layout page so it appears on all pages. The <div> should be hidden and look like so:

layouts/default.vue
HTML
<template>
  <div class="relative font-body">
    <HeaderSection />
    <div class="pt-24 bg-brand-beige-300">
      <slot />
      <div
        hidden
        id="snipcart"
        data-api-key="YOUR_PUBLIC_API_KEY"
      ></div>
    </div>
    <FooterSection />
  </div>
</template>



<style scoped>
body {
  -webkit-font-smoothing: antialiased;
}
</style>

Believe it or not, we’re not far off!!

Now we want add a class and some other bits to the Add to basket button that’s inside the pages/products/[id].vue .

The snipcart-add-item class added informs Snipcart that it should react to that element's click event and add it to the basket. Then the data attributes bind all the product details to Snipcart including the price, description, quantity etc. When a product is added to the cart, the checkout will automatically come out and will be all styled using the stylesheet we added earlier.

The image URL that Strapi gives back is relative, so we need to make it absolute and build up the URL, therefore we’ve created a computed property.

Finally one thing to note, is the quantity ref that we pass to the InputField. This is using the new composition API and gives us a reactive variable, it’s fewer lines of code compared to when we would write it in the Options API. Your product page should now look like this:

pages/products/[id].vue
HTML
<template>
  <div>
    <Container class="grid grid-cols-12 gap-2 pt-32 pb-24 md:gap-12">
      <div class="col-span-12 md:col-span-6 lg:col-span-5">
        <ProductImage :product="product.data" />
      </div>
      <div class="col-span-12 md:col-span-6 lg:col-span-7">
        <Heading tag="h2" font-style="h1">{{ product.data.attributes.Title }}</Heading>
        <p class="mb-6 text-2xl text-brand-grey-600">{{ product.data.attributes.Price }}</p>
        <p class="pr-12 mb-6 text-brand-grey-500">{{ product.data.attributes.Description }}</p>
        <div class="flex items-center">
          <InputField type="Number" class="mr-4" min="1" v-model="quantity" />
          <Btn
            class="snipcart-add-item"
            :data-item-id="product.data.id"
            :data-item-price="product.data.attributes.Price"
            :data-item-description="product.data.attributes.Description"
            :data-item-name="product.data.attributes.Title"
            :data-item-image="imageUrl"
            :data-item-quantity="quantity"
          >Add to basket</Btn>
        </div>
      </div>
    </Container>
    <Container>
      <div class="pb-8">
        <Heading tag="h3" font-style="h3">Related products</Heading>
        <Heading tag="h2" font-style="h2">Other sick wicks</Heading>
        <div class="grid grid-cols-2 gap-12 md:grid-cols-4">
          <ProductTeaser
            class="col-span-1"
            v-for="product in products.data"
            :key="product.id"
            :product="product"
          />
        </div>
      </div>
    </Container>
  </div>
</template>


<script setup>
const route = useRoute();
const config = useRuntimeConfig();
const quantity = ref(1);
const { data: products } = await useFetch(`${config.API_URL}/api/products?populate=*`)
const { data: product } = await useFetch(`${config.API_URL}/api/products/${route.params.id}?populate=*`)

const imageUrl = computed(() => {
  if (!product.value.data) {
    return null;
  }
  return `${config.API_URL}${product.value.data.attributes.Image.data.attributes.url}`
})
</script>

Finally, let’s go and add the cart total to the CartInfo.vue component. This will automatically update when new products are added to the cart.

The snipcart-checkout class will add a button to open up the cart whenever it’s clicked, and the snipcart-total-price class added to the span lets Snipcart no where to place the total price of your basket. Easy right?

components/cartinfo.vue
HTML
<button class="flex items-center snipcart-checkout">
  <span class="pr-2 ml-3 text-lg font-semibold text-brand-grey-800 snipcart-total-price" />
  <IconsCart class="w-6" />
</button>

One other thing we need to do is make the Snipcart modal container have a higher z-index so the website header doesn’t go above it and hide the checkout. So in your app.vue just add this into your styles section:

app.vue
CSS
.snipcart-modal__container {
  z-index: 100;
}

So that should be all you need! Lets take a look at it :

Your cart in the top right should now have the total next to the basket.
Here’s your cart summary and checkout 🙂

And that's all! Easy peasy!

Thank you so so much for reading, if you have any questions please give us a shout @pixelhopio.. If you would like any more tutorials also just let us know what kind of ones you would like. It’s something we’re really enjoying and would like to do more of.

Happy candling! 🕯 🪔 🎂 🧨

5 Responses

Want to respond? Tweet this post!

5 Likes

Subscribe.

Like what you’re reading? Sign up to recieve our updates straight to your inbox. No spam, unsubscribe anytime!

Related posts.

Creating an internationalised site with Strapi and Nuxt

We were really excited when Strapi released a new update a few weeks ago that included internationalisation! This post will show you the basics of creating an internationalised site using Strapi and Nuxt.

Read more

Dynamic social images with Nuxt, Cloud Functions and Cloudinary

Every little helps when trying to get your content noticed on social media, and having an eye-catching sharing image could be the difference between getting someone passing you by or someone clicking through. This post will guide you by creating dynamic social sharing images unique to each page/post using Firebase cloud functions, Cloudinary and Nuxt.

Read more