Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Static eCommerce website with Gridsome, Commerce.js and Vercel
Nikita Kakuev
Nikita Kakuev

Posted on

     

Static eCommerce website with Gridsome, Commerce.js and Vercel

In this post I’m going to talk about how to build a performant eCommerce site for a rather simple purpose.

Situation

My wife is a pastry chef who has ablog where she shares her best recipes. It’s currently a bit outdated and hosted on WordPress. Recently she decided to start selling a digital product — e-books, a collection of her unique recipes. For that, we’d needed an eCommerce solution, obviously.

Normally, if you have a WordPress site already, you’d go with something like WooCommerce or maybe a Shopify plugin for WordPress or some other plugin solution, which I don’t like for multiple reasons:

  1. Performance: WordPress sites are not known for their best time to load speeds.
  2. Maintenance:
    • I’d need to write in PHP, which I personally don’t enjoy as a frontend JavaScript developer
    • To update something on a website, I’d need to go through a complicated and lengthy process of updating a theme, building CSS and JavaScript assets locally with Webpack (because I’m also using Vue on her WordPress site, long story), packaging it into .zip folder, naming it differently from what it’s currently is, and then uploading it through FTP and activating that theme. Then if I find something wrong with it, I’d need to go back to fix it and do this whole process all over again.
  3. Customization:
    • Same as 2a
    • If you want to create custom functionality you’d either need a plugin, which would mean a cost of a third party service, and it will damage your performance, or I’d need to do a lot of hacking to make WordPress do something it wasn’t supposed to, which is not ideal.
  4. Cost:
    • I’m currently paying US $5-10 per month to host the WordPress site on AWS’s LightSail service, which isn’t a lot, but if it could be nothing, why don’t I use it?
    • Same as 3b, since my wife has me, i.e. her personal developer, we don’t need to pay for a plugin and overload our site with more scripts/CSS. I could just quickly put together a lambda function that would do what we need through a rest API, e.g. syncing users to a free CRM of choice to start with, such asCapsuleCRM, who seems to have quite a goodAPI Docs.

Solution

To have an easy deployment, freedom in customization, and enjoy the dev process in general, I’ve decided to move away from WordPress once and for all.

Here’s the stack for the new version of the site;

In this post, I'll only cover the Commerce.js integration, but you can always mix and match and use anything you like for your CMS, advertising, etc.

I like working with Vue for my side projects, since in my full-time job I work a lot with React, so I went withGridsome for a static site generator.

Setup

Following theGetting Started guide, let's install Gridsome and create a project

npm install --global @gridsome/cligridsome create static-eshopcd static-eshop
Enter fullscreen modeExit fullscreen mode

To launch our dev server we'll need to run:

yarn develop
Enter fullscreen modeExit fullscreen mode

Now let's addTailwindCSS for nice and easy styling. For that we'll needTailwind Gridsome plugin. It will initialise global styles for us and give us a script to generate atailwind.config.js in case we'll want to customise anything.

yarn add -D gridsome-plugin-tailwindcss./node_modules/.bin/tailwind init
Enter fullscreen modeExit fullscreen mode

Ourgridsome.config.js should be aware that we're using Tailwind, so let's put this in ourplugins property

{use:"gridsome-plugin-tailwindcss";}
Enter fullscreen modeExit fullscreen mode

Now when we runyarn develop, we can see that styling has changed to default tailwindcss.

Before we get to styling and outputting items on a page, let’s get the content first. For that, I put together aGridsome source plugin for Commerce.js that will help you query all your products from your store.

yarn add -D gridsome-source-commercejs
Enter fullscreen modeExit fullscreen mode

For now we'll also use the Commerce.js demo key that they provide in their docs to avoid creating products on our own, and put code for the Commerce.js source plugin setup in gridsome.config.js.

{use:"gridsome-source-commercejs",options:{publicKey:"pk_184625ed86f36703d7d233bcf6d519a4f9398f20048ec",},},}
Enter fullscreen modeExit fullscreen mode

NOTE: in production, you better keep your keys in a separate.env file or elsewhere safe.

Query the data

So now we can launch our dev server, go tohttp://localhost:8080/___explore and query our products with properties that we need. For current purpose I'll only need a few.

query {  allCommercejsProducts {    edges {      node {        price {          formatted        }        name        description        id        media {          source        }      }    }  }}
Enter fullscreen modeExit fullscreen mode

Insert the same query on ourIndex.vue page in<static-query></static-query>.

To query each product's info on a single page, we'll need to use<page-query></page-query> which allows use of dynamic data, i.e. productid in our case. To setup a single page, we'll need to use templates. Ingridsome.config.js we'll put

templates:{CommercejsProducts:"/products/:id",},
Enter fullscreen modeExit fullscreen mode

Note: in templates, the property should always be namedCommercejsProducts because that's the name for it ingridsome-source-plugin.

So now, when we go to/products/product_id we'll useCommercejsProducts.vue page intemplates folder.

Page query for a single page would look like this:

query ($id: ID!) {  commercejsProducts(id: $id) {    price {      formatted_with_code    }    name    description    id    media {      source    }  }}
Enter fullscreen modeExit fullscreen mode

Now let's put all this data on a page, and create designs.

Design

For that we'll need a<Product /> component with tailwindcss styling (only!).

<template><divclass="max-w-sm rounded overflow-hidden shadow-lg relative"><g-link:to="`/products/${id}`"><imgclass="w-full":src="image":alt="name"/></g-link><divclass="px-6 py-4"><divclass="flex justify-between align-center mb-2"><divclass="font-bold text-lg"v-html="name"></div><divclass="font-semibold text-gray-800"v-html="`$${price}`"></div></div><pclass="text-gray-700 text-base mb-8"v-html="description"></p><buttonclass="text-sm bg-green-500 hover:bg-green-700 text-white font-semibold py-2 px-4 w-full absolute bottom-0 left-0"@click="onAddToCart(id)">Addtocart</button></div></div></template><script>exportdefault{name:"Product",props:{description:String,id:String,image:String,name:String,onAddToCart:Function,price:String,},};</script>
Enter fullscreen modeExit fullscreen mode

And modify the layout a bit. Add margins at the bottom to make it look a bit better. And use our<Product /> component

<template><Layout:quantity="quantity":checkout-link="checkoutLink"><h1class="text-lg font-semibold mb-8 text-gray-700">AllProducts</h1><divclass="grid grid-cols-3 gap-4"><Productv-for="product in $static.allCommercejsProducts.edges":key="product.node.id":name="product.node.name":image="product.node.media.source":description="product.node.description":id="product.node.id":price="product.node.price.formatted":onAddToCart="addToCart"/></div></Layout></template><static-query>query{allCommercejsProducts{edges{node{price{formatted}namedescriptionidmedia{source}}}}}</static-query><script>importProductfrom"../components/Product";importcommercefrom"../utils";exportdefault{metaInfo:{title:"Hello, world!",},components:{Product,},data:()=>({quantity:0,checkoutLink:null,}),};</script>
Enter fullscreen modeExit fullscreen mode

We'll also need a<Cart /> component to have a checkout button and display number of items in a basket.

<template><divclass="font-sans block mt-4 lg:inline-block lg:mt-0 lg:ml-6 align-middle text-black hover:text-gray-700"><a:href="checkoutLink"target="_blank"class="relative flex"><svgclass="flex-1 w-8 h-8 fill-current text-green-300"viewbox="0 0 24 24"><pathd="M17,18C15.89,18 15,18.89 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20C19,18.89 18.1,18 17,18M1,2V4H3L6.6,11.59L5.24,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42A0.25,0.25 0 0,1 7.17,14.75C7.17,14.7 7.18,14.66 7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.58 17.3,11.97L20.88,5.5C20.95,5.34 21,5.17 21,5A1,1 0 0,0 20,4H5.21L4.27,2M7,18C5.89,18 5,18.89 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20C9,18.89 8.1,18 7,18Z"/></svg><spanclass="absolute right-0 top-0 rounded-full bg-gray-600 w-4 h-4 top right p-0 m-0 text-white font-mono text-sm  leading-tight text-center">{{quantity}}</span></a></div></template><script>exportdefault{name:"Cart",props:{quantity:Number,checkoutLink:String,},};</script>
Enter fullscreen modeExit fullscreen mode

Cart

<static-query/> andgridsome-source-commercejs plugin allows us only to query the data. To actually create a cart, add, delete, and remove items from it and create a checkout process, we'll need a@chec/commerce.js SDK. Let's install it, initialize and export it from a separateutils/index.js file so that we could reuse it across different components.

yarn add @chec/commerce.js
Enter fullscreen modeExit fullscreen mode
// utils/index.jsimportCommercefrom"@chec/commerce.js";exportdefaultnewCommerce("pk_184625ed86f36703d7d233bcf6d519a4f9398f20048ec");
Enter fullscreen modeExit fullscreen mode

Now let's get a number of items in a cart (which will be 0 at first, obviously). In ourIndex.vue page:

// up topimportcommercefrom'../utils.js'// down in Vuemounted(){commerce.cart.retrieve().then((cart)=>{console.log(cart);this.quantity=cart.total_items;this.checkoutLink=cart.hosted_checkout_url});},
Enter fullscreen modeExit fullscreen mode

We'll pass this info to our<Layout /> component (just because I want to, no particular reason) where our<Cart /> component is. So our<Layout /> component would look like this.

<template><divclass="layout"><headerclass="header"><strong><g-linkto="/">{{$static.metadata.siteName}}</g-link></strong><navclass="flex align-center"><g-linkclass="nav__link"to="/">Home</g-link><Cart:quantity="quantity":checkout-link="checkoutLink"/></nav></header><slot/></div></template><static-query>query{metadata{siteName}}</static-query><script>importCartfrom"../components/Cart";exportdefault{name:"Layout",components:{Cart},props:{quantity:Number,checkoutLink:String,},};</script><style>body{font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;margin:0;padding:0;line-height:1.5;}.layout{max-width:760px;margin:2emauto;padding-left:20px;padding-right:20px;}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;height:80px;}.nav__link{margin-left:20px;}</style>
Enter fullscreen modeExit fullscreen mode

Since we already have theCommerce.js SDK initialized, we can add ouraddToCart method for our<Product /> component. We'll keep it inIndex.vue though, and pass it down to Product component as a prop.

methods:{addToCart(id){commerce.cart.add(id,1).then((response)=>{console.log(response);this.quantity=response.cart.total_items;}).catch((e)=>console.log(e));},},
Enter fullscreen modeExit fullscreen mode

Note: I’m being very lazy here and doing console.logs, when in real eCommerce store we should probably setup something likevue-notifications to have a better feedback for the user.

Let's not forget the singleCommercejsProducts.vue template. Together with styling and query, it'll look like this:

<template><Layout:quantity="quantity":checkoutLink="checkoutLink"><divclass="w-full py-8 px-6 bg-gray-200 grid grid-cols-1 md:grid-cols-3 gap-4 rounded shadow-lg"><divclass="col-span-1"><img:src="this.$page.commercejsProducts.media.source"/></div><divclass="col-span-1 md:col-span-2 relative"><divclass="flex justify-between mb-8"><div><divclass="text-2xl font-bold text-gray-800"v-html="this.$page.commercejsProducts.name"></div><divclass="text-gray-700 text-sm italic"v-html="this.$page.commercejsProducts.id"></div></div><divclass="text-gray-700 text-lg"v-html="this.$page.commercejsProducts.price.formatted_with_code"></div></div><divclass="font-semibold text-gray-800 mb-2">Description</div><divclass="mb-16 md:mb-6"v-html="this.$page.commercejsProducts.description"></div><buttonclass="text-sm bg-green-500 hover:bg-green-700 text-white font-semibold py-2 px-4 mx-auto rounded absolute right-0 bottom-0 w-full"@click="onAddToBasket($page.commercejsProducts.id)">Addtobasket</button></div></div></Layout></template><page-query>query($id:ID!){commercejsProducts(id:$id){price{formatted_with_code}namedescriptionidmedia{source}}}</page-query><script>importcommercefrom"../utils";exportdefault{name:"Product",data:()=>({quantity:0,checkoutLink:null,}),mounted(){commerce.cart.retrieve().then((cart)=>{console.log(cart);this.quantity=cart.total_items;this.checkoutLink=cart.hosted_checkout_url;});},methods:{onAddToBasket(id){commerce.cart.add(id,1).then((response)=>{console.log(response);this.quantity=response.cart.total_items;}).catch((e)=>console.log(e));},},};</script>
Enter fullscreen modeExit fullscreen mode

Note: I'm copy/pastingonAddToBasket method, but you can probably abstract it into a util function and import and reuse it.

Checkout

To save some time for this example, I’m going to use ahosted_checkout_link to finish the checkout process (you can code your own checkout using their checkout API but Commerce.js also has hosted checkout pages). We get it from thecart object in ourmounted() method together with the quantity.

Getting ahead of myself, I've already added that link to the<Cart /> component namedcheckoutLink. So now, when you click on a cart icon, it opens a new tab for a hosted checkout. After filling in all the fields and submitting the form, we can return to our site, refresh, and we'll have 0 items in a basket. Magic!

Deploy

Deploying is rather simple. We just to need push this code to any Git repository. Then link that repository in our Vercel admin area, and deploy from there. Vercel even recognizes Gridsome and applies a pre-defined build script, which you can always override if you need to.

And we'll have our shop online athttps://blog-static-eshop.vercel.app.

Last few words

Combination of Gridsome, Commerce.js, and Vercel gives us agility and a lot of freedom to do any checkout experience and any other features we want. For example, we can:

  • create our own styled checkout form
  • capture custom data at the checkout
  • create a custom thank you page
  • create a custom lambda/serverless function to sync user data/info through API with other services, like a CRM, loyalty program/system after the user completes the order.

And if we're updating products in Commerce.js admin area, we simply need a webhook to tell Vercel to rebuild the site every time a product is added or edited.

You can find the final project in myGitHub repo.

Top comments(2)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
sepi2048 profile image
sepi2048
  • Joined

Thank for this in-depth tutorial and github upload!
If I where to ONLY sell digital products using this commerce.js setup, is it possible to create a simplified checkout?

I.e. shipping method seems a bit unnecessary, how would you go about this?

CollapseExpand
 
olusola profile image
olusola ak
I like making things in React
  • Location
    london
  • Work
    Frontend Developer at Lendinvest
  • Joined

thanks for the tutorial, will be checking out commerceJS

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

I do code.
  • Location
    Brighton, 🇬🇧
  • Work
    Software Developer
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp