Almost EVERYONE who tried headless systems said they saw benefits. Download the state of CMS now!

Storyblok now on AWS Marketplace: Read more

O’Reilly Report: Decoupled Applications and Composable Web Architectures - Download Now

Empower your teams & get a 582% ROI: See Storyblok's CMS in action

Skip to main content

Building Composable Commerce → Nuxt

INFO:

On May 13th, 2024, Storyblok started gradually rolling out a new design for its Visual Editor. Therefore, the Visual Editor product screenshots depicted in this resource may not match what you encounter in the Storyblok App. For more information and a detailed reference, please consult this FAQ on the new Visual Editor design.

Nuxt is an Intuitive Web Framework that allows you to build your next Vue.js application with confidence. An open source framework under MIT license that makes web development simple and powerful.

It comes with several useful features like:

  • Optimized with code-splitting, tree-shaking, optimized cold-start, link prefetching, payload extraction, just to name a few. Fast by default so you can focus on building.
  • Decide what rendering strategy at the route level: SSR, SSG, CSR, ISR, ESR, SWR. Build any kind of website or web application with optimized performance in mind.
  • By leveraging server-side rendering, ESM format and optimized images, Nuxt websites are indexable by search engines while giving the feeling of an app to the end-users.

Read more about it in the official documentation → https://nuxt.com/

We will be using this framework for our storefront (the frontend of e-commerce application) that will connect later on to the Shopify platform by using the Apollo GraphQL and to Storyblok as CMS.

Section titled Getting started with Nuxt Getting started with Nuxt

In order to get started with Nuxt, the best place to go is the official documentation → https://nuxt.com/docs/getting-started/installation. We will create a simple Nuxt application with the following command in the terminal:

        
      npx nuxi init nuxt-shopify-storyblok
    

This will create a simple Nuxt starter application. We can start this project to see if it works as expected (also, remember to install the dependencies with the package manager of your choice (in my case it was yarn).

Running the project requires the below command:

        
      yarn dev
    

The project should be running right now and when we access the browser we should see the following result:

Now, if that went well, we can move to the next section about adding a bit of styling so that our future e-commerce application will look a bit better.

Section titled Adding styling with TailwindCSS Adding styling with TailwindCSS

One of the things that I love about Nuxt is its ecosystem of modules. They extend the default functionality of the core framework and deliver a great Developer Experience.

Let’s add @nuxtjs/tailwindcss module to our application so that we can have easy and flexible styling for our e-commerce website.

Install the module with the following command:

        
      yarn add --dev @nuxtjs/tailwindcss
    

And now, let’s add it to the modules array in nuxt.config.ts file:

        
      // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss']
})
    

And that’s it! We now have TailwindCSS installed in our application and we can test it out by replacing the code in app.vue component to something like this:

        
      <template>
  <div>
    <h1 class="text-xl text-green-500">Hello Nuxt with TailwindCSS</h1>
  </div>
</template>
    

Section titled The basic layout of our e-commerce website The basic layout of our e-commerce website

To have better reusability of our storefront application elements, let’s create a layout that will be shared across our pages. We will create two components, namely TheHeader and TheFooter and finally a layout that we will be using for our upcoming pages.

Let’s start with TheHeader component that will be a simple navigation with just a logo of Nuxt (company logo) on the left side of the page. I have already added a logo.svg in the /public/logo.svg so that we could use it easily in our app.

components/TheHeader.vue
        
      <template>
  <nav class="py-2 relative w-full">
    <div class="px-6">
      <NuxtLink to="/">
        <img class="h-20 w-20 mr-16" src="/logo.svg" alt="Store logo" />
      </NuxtLink>
    </div>
  </nav>
</template>
    

As you can see here, it is a simple nav element with NuxtLink that when clicked will redirect the user to the homepage. Nothing crazy here 😀

Next, we will create TheFooter component that will be responsible for displaying a footer tag with two links; first to the Nuxt website, and second to your Twitter:

components/TheFooter.vue
        
      <template>
  <footer class="p-4 flex items-center justify-center text-xl">
    <a href="https://nuxt.com/" target="_blank">
      <svg class="h-8 mr-4" viewBox="0 0 204 44" fill="none" xmlns="http://www.w3.org/2000/svg" alt="Nuxt"><path d="M196.302 0H7.69811C3.44656 0 0 3.58172 0 8V36C0 40.4183 3.44656 44 7.69811 44H196.302C200.553 44 204 40.4183 204 36V8C204 3.58172 200.553 0 196.302 0Z" fill="#003C3C"></path><path d="M55.184 28V16.8H56.752L60.784 24.608L64.784 16.8H66.368V28H65.024V19.2L61.28 26.4H60.272L56.528 19.216V28H55.184ZM71.2909 28.192C70.6295 28.192 70.0802 28.08 69.6429 27.856C69.2055 27.632 68.8802 27.3333 68.6669 26.96C68.4535 26.5867 68.3469 26.1813 68.3469 25.744C68.3469 24.9333 68.6562 24.3093 69.2749 23.872C69.8935 23.4347 70.7362 23.216 71.8029 23.216H73.9469V23.12C73.9469 22.4267 73.7655 21.904 73.4029 21.552C73.0402 21.1893 72.5549 21.008 71.9469 21.008C71.4242 21.008 70.9709 21.1413 70.5869 21.408C70.2135 21.664 69.9789 22.0427 69.8829 22.544H68.5069C68.5602 21.968 68.7522 21.4827 69.0829 21.088C69.4242 20.6933 69.8455 20.3947 70.3469 20.192C70.8482 19.9787 71.3815 19.872 71.9469 19.872C73.0562 19.872 73.8882 20.1707 74.4429 20.768C75.0082 21.3547 75.2909 22.1387 75.2909 23.12V28H74.0909L74.0109 26.576C73.7869 27.024 73.4562 27.408 73.0189 27.728C72.5922 28.0373 72.0162 28.192 71.2909 28.192ZM71.4989 27.056C72.0109 27.056 72.4482 26.9227 72.8109 26.656C73.1842 26.3893 73.4669 26.0427 73.6589 25.616C73.8509 25.1893 73.9469 24.7413 73.9469 24.272V24.256H71.9149C71.1255 24.256 70.5655 24.3947 70.2349 24.672C69.9149 24.9387 69.7549 25.2747 69.7549 25.68C69.7549 26.096 69.9042 26.432 70.2029 26.688C70.5122 26.9333 70.9442 27.056 71.4989 27.056ZM80.9071 28.192C80.1178 28.192 79.4245 28.0107 78.8271 27.648C78.2405 27.2853 77.7818 26.7893 77.4511 26.16C77.1311 25.5307 76.9711 24.816 76.9711 24.016C76.9711 23.216 77.1365 22.5067 77.4671 21.888C77.7978 21.2587 78.2565 20.768 78.8431 20.416C79.4298 20.0533 80.1231 19.872 80.9231 19.872C81.5738 19.872 82.1498 20.0053 82.6511 20.272C83.1525 20.5387 83.5418 20.912 83.8191 21.392V16.48H85.1631V28H83.9471L83.8191 26.688C83.5631 27.072 83.1951 27.4187 82.7151 27.728C82.2351 28.0373 81.6325 28.192 80.9071 28.192ZM81.0511 27.024C81.5845 27.024 82.0538 26.9013 82.4591 26.656C82.8751 26.4 83.1951 26.048 83.4191 25.6C83.6538 25.152 83.7711 24.6293 83.7711 24.032C83.7711 23.4347 83.6538 22.912 83.4191 22.464C83.1951 22.016 82.8751 21.6693 82.4591 21.424C82.0538 21.168 81.5845 21.04 81.0511 21.04C80.5285 21.04 80.0591 21.168 79.6431 21.424C79.2378 21.6693 78.9178 22.016 78.6831 22.464C78.4591 22.912 78.3471 23.4347 78.3471 24.032C78.3471 24.6293 78.4591 25.152 78.6831 25.6C78.9178 26.048 79.2378 26.4 79.6431 26.656C80.0591 26.9013 80.5285 27.024 81.0511 27.024ZM90.922 28.192C90.1647 28.192 89.4927 28.0213 88.906 27.68C88.3193 27.328 87.8553 26.8427 87.514 26.224C87.1833 25.6053 87.018 24.8747 87.018 24.032C87.018 23.2 87.1833 22.4747 87.514 21.856C87.8447 21.2267 88.3033 20.7413 88.89 20.4C89.4873 20.048 90.1753 19.872 90.954 19.872C91.722 19.872 92.3833 20.048 92.938 20.4C93.5033 20.7413 93.9353 21.1947 94.234 21.76C94.5327 22.3253 94.682 22.9333 94.682 23.584C94.682 23.7013 94.6767 23.8187 94.666 23.936C94.666 24.0533 94.666 24.1867 94.666 24.336H88.346C88.378 24.944 88.5167 25.4507 88.762 25.856C89.018 26.2507 89.3327 26.5493 89.706 26.752C90.09 26.9547 90.4953 27.056 90.922 27.056C91.4767 27.056 91.9407 26.928 92.314 26.672C92.6873 26.416 92.9593 26.0693 93.13 25.632H94.458C94.2447 26.368 93.834 26.9813 93.226 27.472C92.6287 27.952 91.8607 28.192 90.922 28.192ZM90.922 21.008C90.282 21.008 89.7113 21.2053 89.21 21.6C88.7193 21.984 88.4367 22.5493 88.362 23.296H93.354C93.322 22.5813 93.0767 22.0213 92.618 21.616C92.1593 21.2107 91.594 21.008 90.922 21.008ZM102.298 28L99.9778 20.064H101.322L103.002 26.288L104.858 20.064H106.378L108.25 26.288L109.914 20.064H111.274L108.954 28H107.578L105.626 21.456L103.674 28H102.298ZM113.576 18.304C113.31 18.304 113.086 18.2187 112.904 18.048C112.734 17.8667 112.648 17.6427 112.648 17.376C112.648 17.12 112.734 16.9067 112.904 16.736C113.086 16.5653 113.31 16.48 113.576 16.48C113.832 16.48 114.051 16.5653 114.232 16.736C114.414 16.9067 114.504 17.12 114.504 17.376C114.504 17.6427 114.414 17.8667 114.232 18.048C114.051 18.2187 113.832 18.304 113.576 18.304ZM112.904 28V20.064H114.248V28H112.904ZM119.724 28C118.999 28 118.428 27.824 118.012 27.472C117.596 27.12 117.388 26.4853 117.388 25.568V21.2H116.012V20.064H117.388L117.564 18.16H118.732V20.064H121.068V21.2H118.732V25.568C118.732 26.0693 118.834 26.4107 119.036 26.592C119.239 26.7627 119.596 26.848 120.108 26.848H120.94V28H119.724ZM122.776 28V16.48H124.12V21.424C124.387 20.9333 124.765 20.5547 125.256 20.288C125.747 20.0107 126.28 19.872 126.856 19.872C127.773 19.872 128.509 20.16 129.064 20.736C129.619 21.3013 129.896 22.176 129.896 23.36V28H128.568V23.504C128.568 21.8507 127.901 21.024 126.568 21.024C125.875 21.024 125.293 21.2747 124.824 21.776C124.355 22.2667 124.12 22.9707 124.12 23.888V28H122.776ZM135.947 28V16.8H137.995L143.259 24.688V16.8H145.307V28H143.259L137.995 20.128V28H135.947ZM150.407 28.192C149.415 28.192 148.647 27.8827 148.103 27.264C147.569 26.6453 147.303 25.7387 147.303 24.544V20.064H149.335V24.352C149.335 25.0347 149.473 25.5573 149.751 25.92C150.028 26.2827 150.465 26.464 151.063 26.464C151.628 26.464 152.092 26.2613 152.455 25.856C152.828 25.4507 153.015 24.8853 153.015 24.16V20.064H155.063V28H153.255L153.095 26.656C152.849 27.1253 152.492 27.4987 152.023 27.776C151.564 28.0533 151.025 28.192 150.407 28.192ZM156.413 28L159.293 24.032L156.413 20.064H158.605L160.621 22.88L162.621 20.064H164.829L161.933 24.032L164.829 28H162.621L160.621 25.184L158.605 28H156.413ZM169.67 28C168.838 28 168.171 27.7973 167.67 27.392C167.168 26.9867 166.918 26.2667 166.918 25.232V21.776H165.558V20.064H166.918L167.158 17.936H168.966V20.064H171.11V21.776H168.966V25.248C168.966 25.632 169.046 25.8987 169.206 26.048C169.376 26.1867 169.664 26.256 170.07 26.256H171.062V28H169.67ZM175.844 28.192C174.756 28.192 173.881 27.888 173.22 27.28C172.569 26.6613 172.244 25.7867 172.244 24.656H174.292C174.303 25.1787 174.425 25.5947 174.66 25.904C174.895 26.2133 175.273 26.368 175.796 26.368C176.287 26.368 176.644 26.224 176.868 25.936C177.092 25.6373 177.204 25.248 177.204 24.768V16.8H179.252V24.768C179.252 25.8667 178.943 26.7147 178.324 27.312C177.716 27.8987 176.889 28.192 175.844 28.192ZM185.256 28.192C184.434 28.192 183.709 28.0533 183.08 27.776C182.45 27.488 181.954 27.0827 181.592 26.56C181.229 26.0267 181.042 25.3813 181.032 24.624H183.192C183.213 25.1467 183.4 25.5893 183.752 25.952C184.114 26.304 184.61 26.48 185.24 26.48C185.784 26.48 186.216 26.352 186.536 26.096C186.856 25.8293 187.016 25.4773 187.016 25.04C187.016 24.5813 186.872 24.224 186.584 23.968C186.306 23.712 185.933 23.504 185.464 23.344C184.994 23.184 184.493 23.0133 183.96 22.832C183.096 22.5333 182.434 22.1493 181.976 21.68C181.528 21.2107 181.304 20.5867 181.304 19.808C181.293 19.1467 181.448 18.5813 181.768 18.112C182.098 17.632 182.546 17.264 183.112 17.008C183.677 16.7413 184.328 16.608 185.064 16.608C185.81 16.608 186.466 16.7413 187.032 17.008C187.608 17.2747 188.056 17.648 188.376 18.128C188.706 18.608 188.882 19.1787 188.904 19.84H186.712C186.701 19.4453 186.546 19.0987 186.248 18.8C185.96 18.4907 185.554 18.336 185.032 18.336C184.584 18.3253 184.205 18.4373 183.896 18.672C183.597 18.896 183.448 19.2267 183.448 19.664C183.448 20.0373 183.565 20.336 183.8 20.56C184.034 20.7733 184.354 20.9547 184.76 21.104C185.165 21.2533 185.629 21.4133 186.152 21.584C186.706 21.776 187.213 22 187.672 22.256C188.13 22.512 188.498 22.8533 188.776 23.28C189.053 23.696 189.192 24.2347 189.192 24.896C189.192 25.4827 189.042 26.0267 188.744 26.528C188.445 27.0293 188.002 27.4347 187.416 27.744C186.829 28.0427 186.109 28.192 185.256 28.192Z" fill="#E6F0F0"></path><path d="M22.891 15.818c-.572-.983-2-.983-2.572 0l-6.23 10.712c-.572.983.142 2.212 1.285 2.212h4.864a1.424 1.424 0 0 1-.3-1.8l4.719-8.088-1.766-3.036Z" fill="#80EEC0"></path><path d="M26.839 18.168c.473-.805 1.656-.805 2.129 0l5.156 8.764c.473.805-.119 1.81-1.065 1.81H22.747c-.946 0-1.538-1.005-1.064-1.81l5.156-8.764Z" fill="#00DC82"></path><path d="M45 0h-1v44h1V0Z" fill="#2A5B5B"></path></svg>
    </a>
    by
    <a
      class="text-green-600 ml-2"
      target="_blank"
      href="https://twitter.com/jacobandrewsky"
      >@jacobandrewsky</a
    >
  </footer>
</template>
    

Nothing crazy here as well. Now, let’s create a default.vue layout and add two previously created components there:

        
      <template>
  <div class="px-20">
    <TheHeader />
    <slot />
    <TheFooter />
  </div>
</template>
    

The content of our pages (home page and product page) will be rendered in <slot />. Finally, let’s add this layout in our global app.vue component:

        
      <template>
  <NuxtLayout name="default">
    <NuxtPage />
  </NuxtLayout>
</template>
    

The <NuxtPage /> will not work yet as we have not created any pages yet (also, the warning in the console will confirm that → Create a Vue component in the pages/ directory to enable <NuxtPage>) but no worries, we will do that in the next section.

If we did everything correctly, we should see the following result in the browser:

We have a header component with the Nuxt logo and also a footer with a link to Nuxt and made by me link. In the next section, we will create the homepage of our e-commerce website.

Section titled Homepage with Banner and Product List Homepage with Banner and Product List

In this section, we will be creating two new components, namely HeroBanner.vue and ProductCard.vue as well as the new page. We won’t be fetching any data yet but instead we will mock the data for now. We will also add a new module @nuxt/image that will be responsible for optimizing our images so that they are more performant out of the box.

To use the @nuxt/image module in our Nuxt app we will use the official docs

In order to use it, we will first install it:

        
      yarn add --dev @nuxt/image-edge
    

And then, add it to our modules array in nuxt.config.ts file:

        
      // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss', '@nuxt/image-edge']
})
    

And that’s it! We can now use NuxtImg component in our app.

You may be wondering why we are using the image-edge version. At the time of me writing this article, this is the recommended version of the image module to work with Nuxt 3

For our new components, let’s start with Hero Banner. As its name suggests it will be the first banner that our users will see so we want to make it big and with a catchy phrase at the top.

        
      <template>
  <section>
    <NuxtImg
      src="https://mdbootstrap.com/img/new/textures/full/142.jpg"
      class="h-[500px] w-full"
      format="webp"
    />

    <div class="mx-auto px-32">
      <div
        class="text-7xl font-bold text-center text-gray-800 rounded-lg shadow-lg py-16 px-12 bg-white/70 -mt-[170px] backdrop-blur-xl"
      >
        <h1 class="mb-3">Find the best products</h1>
        <span class="text-green-600">on the market</span>
      </div>
    </div>
  </section>
</template>
    

The second part of this component is simple, we are just displaying a div with a green text, but let’s stop for a second to discuss what is happening in NuxtImg component.

We are using the component from image module and we are passing a prop attribute format. In here we are saying what format we would like this image to be. So, instead of heavy .jpg we would prefer to have a lighter alternative of .webp that is supported by all modern browsers. The optimization won’t work yet as we need to apply some configuration to the image module.

By default, the IPX image optimizer that the image module is using, needs to have a list of domains that can be used for optimizing the images. For now, what would happen is that our Banner would be displayed, but it wont be optimized. In order to enable the optimization, we need to add the mdbootstrap.com to the allowed domains for IPX. Let’s do this below:

        
      // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss', '@nuxt/image-edge'],

  image: {
    domains: ['mdbootstrap.com']
  }
})
    

Let’s now create our homepage so that we could display the HeroBanner.vue component there:

        
      <template>
  <div>
    <HeroBanner />
  </div>
</template>
    

Thanks to the Nuxt auto-import feature, we do not need to write any import statements.

If we did everything correctly, we should see the following result in the browser:

Now, let’s create a ProductCard.vue component that will be responsible for displaying the data about our product (mocked for now).

components/ProductCard.vue
        
      <script setup lang="ts">
defineProps({
  title: {
    type: String,
    required: true,
  },
  price: {
    type: String,
    required: true,
  },
  image: {
    type: String,
    required: true,
  },
  link: {
    type: String,
    required: true,
  },
  description: {
    type: String,
    required: true,
  },
});
</script>

<template>
  <div class="mx-2">
    <div class="relative rounded-lg shadow-lg">
      <NuxtLink :to="link">
        <NuxtImg
          :src="image"
          class="shadow-lg rounded-lg opacity-1 hover:opacity-75 transition duration-300 ease-in-out"
          format="webp"
        />
      </NuxtLink>
      <div class="p-6">
        <h5 class="font-bold text-lg mb-3">{{ title }}</h5>
        <pre class="text-gray-500 mb-4">{{ price }}</pre>
        <p>{{ description }}</p>
      </div>
    </div>
  </div>
</template>
    

This component will accept a few props that will be used to display content about our product in a nice way.

Let’s add it to our index.vue (Home Page) to see how it looks like. I will create an array of mocked products to display a list of three products below the HeroBanner.vue component.

pages/index.vue
        
      <script setup lang="ts">
const mockedProducts = [
  {
    id: 0,
    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80',
    title: 'My awesome product 1',
    price: '10$',
    link: '#',
    description: 'My awesome product 1'
  },
  {
    id: 1,
    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80',
    title: 'My awesome product 2',
    price: '15$',
    link: '#',
    description: 'My awesome product 2'
  },
  {
    id: 2,
    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80',
    title: 'My awesome product 3',
    price: '20$',
    link: '#',
    description: 'My awesome product 3'
  }
]
</script>

<template>
  <div>
    <HeroBanner />
    <div class="flex my-20">
      <ProductCard
        v-for="{ id, image, title, price, link, description } in mockedProducts"
        :key="id"
        :image="image"
        :title="title"
        :price="price"
        :link="link"
        :description="description"
      />      
    </div>
  </div>
</template>
    

Nothing crazy here as well, we are just creating three components out of the array of mocked products. Later on, we will replace this array with actual data from the Shopify platform.

Section titled Product Detail Page Product Detail Page

Apart from the Home Page, our e-commerce application would need a solid Product Page that would showcase our product, and related products and would allow us to buy the actual product.

Let’s create a new page in /pages/products/[handle].vue.

pages/products/[handle].vue
        
      <script setup lang="ts">
const mockedProduct = {
  image:
    "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80",
  title: "My awesome product 1",
  price: "10$",
  description: "My awesome product 1",
};
</script>

<template>
  <section>
    <div class="grid grid-cols-2 items-center px-20">
      <NuxtImg
        :src="mockedProduct.image"
        class="rounded-lg shadow-lg -rotate-6"
        alt="Product Image"
        format="webp"
      />
      <div class="rounded-lg shadow-lg p-12 backdrop-blur-2xl">
        <h2 class="text-4xl font-bold mb-6">{{ mockedProduct.title }}</h2>
        <p class="text-gray-500 mb-6">
          {{ mockedProduct.description }}
        </p>

        <button
          class="px-7 py-3 bg-green-600 text-white font-medium text-sm rounded shadow-md hover:bg-green-700 hover:shadow-lg focus:bg-green-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-green-800 active:shadow-lg transition duration-150 ease-in-out"
        >
          Pay {{ mockedProduct.price }}
        </button>
      </div>
    </div>
  </section>
</template>
    

The path in our project will resemble the URL in the browser so:

products/[handle].vuehttp://localhost:3000/products/3

If we did everything correctly, we should see the following result in the browser:

Apart from that, let’s also add the same list of products from the HomePage (don’t bother about code duplication right now, everything will be fixed once we will fetch the real data from Shopify 😀)

        
      <script setup lang="ts">
const mockedProduct = {
  image:
    "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80",
  title: "My awesome product 1",
  price: "10$",
  description: "My awesome product 1",
};

const mockedProducts = [
  {
    id: 0,
    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80',
    title: 'My awesome product 1',
    price: '10$',
    link: '#',
    description: 'My awesome product 1'
  },
  {
    id: 1,
    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80',
    title: 'My awesome product 2',
    price: '15$',
    link: '#',
    description: 'My awesome product 2'
  },
  {
    id: 2,
    image: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8M3x8cHJvZHVjdHxlbnwwfHwwfHw%3D&w=1000&q=80',
    title: 'My awesome product 3',
    price: '20$',
    link: '#',
    description: 'My awesome product 3'
  }
]
</script>

<template>
  <section>
    <div class="grid grid-cols-2 items-center px-20">
      <NuxtImg
        :src="mockedProduct.image"
        class="rounded-lg shadow-lg -rotate-6"
        alt="Product Image"
        format="webp"
      />
      <div class="rounded-lg shadow-lg p-12 backdrop-blur-2xl">
        <h2 class="text-4xl font-bold mb-6">{{ mockedProduct.title }}</h2>
        <p class="text-gray-500 mb-6">
          {{ mockedProduct.description }}
        </p>

        <button
          class="px-7 py-3 bg-green-600 text-white font-medium text-sm rounded shadow-md hover:bg-green-700 hover:shadow-lg focus:bg-green-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-green-800 active:shadow-lg transition duration-150 ease-in-out"
        >
          Pay {{ mockedProduct.price }}
        </button>
      </div>
    </div>
    <div class="flex my-20">
      <ProductCard
        v-for="{ id, image, title, price, link, description } in mockedProducts"
        :key="id"
        :image="image"
        :title="title"
        :price="price"
        :link="link"
        :description="description"
      />      
    </div>
  </section>
</template>
    

Apart from the product detail, we also have a carousel below that shows a list of products (in the future those products will be related to the main product).

That was a lot of work so let’s take a short brake and let’s move to the next section where we will be replacing the mocked data, with real data from Shopify.

NEXT PART:

In the next part of this series, we will see how to implement Shopify with Nuxt.

Author

Jakub Andrzejewski

Jakub Andrzejewski

Senior Developer @VueStorefront, Ambassador @Nuxt.js and @Storyblok. Apart from work, Technical and Recreational Diver, Mountain Hiker, occasional gamer, and a huge fan of Lego and Transformers.