Building Composable Commerce with Nuxt, Shopify, and Storyblok Crash Course Part Two

Valentsea
Total
0
Shares

This is a part two of the Building Composable Commerce with Nuxt, Shopify, and Storyblok Crash Course.

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.

Nuxt

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.



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
Enter fullscreen mode

Exit fullscreen mode

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 below command:

yarn dev
Enter fullscreen mode

Exit fullscreen mode

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

Nuxt Playground

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.



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.

Nuxt Tailwind

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
Enter fullscreen mode

Exit fullscreen mode

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']
})
Enter fullscreen mode

Exit fullscreen mode

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:


Enter fullscreen mode

Exit fullscreen mode



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


Enter fullscreen mode

Exit fullscreen mode

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


Enter fullscreen mode

Exit fullscreen mode

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


Enter fullscreen mode

Exit fullscreen mode

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


Enter fullscreen mode

Exit fullscreen mode

The 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 ) 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:

Nuxt Layout

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.



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

Nuxt Image

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

yarn add --dev @nuxt/image-edge
Enter fullscreen mode

Exit fullscreen mode

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']
})
Enter fullscreen mode

Exit fullscreen mode

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.


Enter fullscreen mode

Exit fullscreen mode

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](http://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']
  }
})
Enter fullscreen mode

Exit fullscreen mode

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


Enter fullscreen mode

Exit fullscreen mode

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:

Nuxt Hero

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>
Enter fullscreen mode

Exit fullscreen mode

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>
Enter fullscreen mode

Exit fullscreen mode

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.



Product Detail Page

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

Let’s create a new page in /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>
Enter fullscreen mode

Exit fullscreen mode

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:

Nuxt Product Page

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>
Enter fullscreen mode

Exit fullscreen mode

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).

Nuxt Product List

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.

Total
0
Shares
Valentsea

GPT4 CLI for TDD: you write the test, GPT writes the code to pass it ✅

first — let’s grow my twitter, so i can tell about my inventions quicker and make the world…