16 October 2023
It takes a lot of thinking when launching even a simple greenfield e-commerce site (without any legacy) for a small business. Apart from considering tech options, I also had to think about the UX, and customer journey, and think about not only good outcomes but also the unlikely and undesirable ones, for example, if for some reason a customer can’t reach us through the contact form, how can we provide them with other options and a pleasant user experience overall.
Just thought I’d share some things I went through when setting this digital asset for my sister, who is an artist and a writer.
To start off, as a proper front-end dev, I had to choose a JS framework. I’m always leaning towards React, so Next.js seemed like an obvious choice.
Nothing much to say here. There are plenty of other sources that talk about why Next.js is the best for, almost any use case there is.
Then I had to choose an e-commerce platform, a headless one because I wanted to be in control of the front-end experience. There were few options available.
At first, I was a fan of Commerce.js. But then they stopped their development, I haven’t seen a release from them in a while, the last one was back in Sep 2021. Also noticed a few bugs and weird behaviours in their SDK (can’t remember which now).
Shopify was expensive. For some reason, if you want to build headless Shopify sites, you need to have a premium membership that costs $2000/mo. Which seemed ridiculous for such a small business case.
There was also Medusa Commerce. I liked it, I’ve even applied for a job there, and spoken with the founders. They’re a very enthusiastic, passionate and determined bunch, and that was reflected on the platform, so I’d definitely give Medusa a go on one of the next projects. But this time I didn’t want to create and deploy/host the back-end myself, so I was looking for a more “cloud-y” solution.
Then there were also Commerce Layer and Saleor.
Saleor seemed like an enterprise-grade solution. I might give it a go for bigger projects with more complicated shipping, stock management, and internationalization rules. For this project it seemed like too much.
And Commerce Layer seemed immature as the product at that time.
Swell seemed like the best option. It’s cloud-based, had clear (enough) documentation, not overengineered, GraphQL was a nice bonus, pleasant minimalistic interface. It checked a lot of boxes!
HTML + CSS isn’t the hard part. Design was provided to me in Figma. Then fetching products and rending it at build time was as simple as
export async function getStaticProps() { const { results: books } = await swell.products.list(); return { props: { books, }, }; }
I started this project when there was no app routing, so I kept it this way even now.
The most thought I’ve put into the checkout experience.
Firstly, I needed to think about successful and failed interactions when adding a product to a cart. I mostly handled notifications with toasts.
With toasts users are always in the loop of what’s happening:
Toasts have all that information.
Another important factor is loading states. It’s a crucial part of UI feedback. Especially when it takes time to process something like it is with coupon/discount codes.
Without the loading state, the user will keep clicking the button until something changes. Loading state is that something that we need to communicate to the user what exactly is happening on the page.
Note: I do need to do optimistic updates there because there is a slight delay between applying the coupon and fetching and updating the state of the cart (that now has a coupon code).
And in case of failure, we have a toast again in our catch
block.
const removeCoupon = async (id: string) => { try { await swell.cart.removeCoupon(id); mutate(); } catch (e) { showError("Something went wrong when removing the coupon"); } };
One of the issues that isn’t dev that we needed to think about was shipping. For that, one MUST have a business account with a shipping provider, whether it’s DHL, UPS, or, in our case, Canada Post, since our target market is Canada and, maybe, the US.
I remember when my wife and I had an online store, we had a Royal Mail business account, where they provided us with a lot of envelops, first-class stickers, stamps, and all the other necessary stuff. Very convenient.
Canada Post is different but it still gave us the costs, and, basically, we divided shipping into 2 shipping zones: domestic and international. Funny enough, doesn’t matter where you send internationally from Canada, the price is more or less the same.
Swell allows you to set up zones and rates based on selected countries.
So what it took us is a bit of research, and organizing it all in swell, and we can now see the shippment options based on the users’ delivery addresses.
So what about customer serice? What if a customer has a question or has an issue with an order? Well, there is an email to use!
BUT it can be hard to keep track of all the conversations in your mailbox. We need something appropriate for the business: Freshdesk.
When you email hello[at]victellstales.com, it’s getting redirected to freshdesk and a ticket is being created.
Similarly, when you submit a contact form, a quick POST request to Freshdesk API creates a ticket for us there
const data = await ky .post("https://{freshdesk-id}.freshdesk.com/api/v2/tickets", { json: { email, description: message, subject: "Contact form request", priority: 1, status: 2, custom_fields: { cf_order: order, cf_contact_name: name, }, }, headers: { Authorization: "Basic " + Buffer.from(process.env.FRESHDESK_API_KEY + ":X").toString( "base64" ), "Content-Type": "application/json", }, }) .json();
One last thing, we need to know about everything that’s wrong with our site. We don’t want failed payments and lost customers. Let’s put some error monitoring, and we’ll use Sentry.io for that!
Now we’re prepared for everything!
This is an MVP. It has a few small things that I’d like to improve, like more custom animations, fine-tune the design and fonts, optimise the code and UX with optimistic updates, and etc.
But the main things that’re missing are
But that’s for later.