Create a custom microstore with our Links and Micro Frontends.
If you’ve been following our recent (big) news, we just launched a brand new product: Commerce Layer Links. Links allow you to expose a Commerce Layer API resource, at the moment SKUs, SKU Lists, and Orders, using our micro-frontends.
And if you’ve been following my blog posts, you’ll remember that sometime ago I published an article entitled Commercify Anything where I built an ecommerce using an open Pokemon API and Commerce Layer. It was a thought exercise intended to show two things:
- How to start selling stock immediately with Commerce Layer.
- How to customize the experience to match any requirements, even something as playful as Pokémon.
For this post, I used the time I had on sitting on a train ride from Napoli to Milano, which takes about 5 hours to take that first post a bit further. I decided to build a “micro store” that can be used via Commerce Layer’s Links.
The concept
Once again, I want to heavily customize the experience. Visitors to the store don’t just browse a list, they become “trainers” by selecting their Pokémon starter, just like in the game franchise.
The goal is not only to showcase the versatility of Commerce Layer Links, but also to inspire developers and businesses by demonstrating how creative you can get with our micro-frontends. Whether you’re looking to build a themed microstore, create engaging marketing campaigns, or design dynamic customer experiences, the possibilities with Links are endless.
A micro what?
A micro frontend brings the concept of microservices to the frontend, dividing the user interface into smaller, independently deployable pieces. Each focuses on a specific feature, simplifying development and updates. In Commerce Layer, micro-frontends enable modular experiences like a standalone checkout app or a microstore, seamlessly integrating into existing ecosystems. This means with Links you can show a list of SKUs in a stand alone microstore, without having to deploy a full website. Learn more by reading our interactive documentation.
The Plan
The easiest way for me to build this would be to fork our mfe-microstore, customize it, and deploy it as-is. However, I want to truly understand how it works and build it myself from the ground up. That way, I can create a Pokémon-themed microstore and provide a clear tutorial for others.
Here’s my plan:
- Deploy a minimal microstore: Start with a basic setup that retrieves and displays a list of Pokémon SKUs using Commerce Layer Links. I’ll configure Commerce Layer to use this custom microstore as the entry point.
- Customize the UI: Transform the plain display into a pixel-art Pokémon-themed starter choice flow. The goal is to create an immersive, nostalgic experience where users feel like they’re navigating a classic Pokémon game.
- Add the “selecting” functionality (e.g. cart integration): Introduce a gamified cart experience. Instead of adding items to a cart conventionally, shoppers will “select” their first Pokémon. The selected Pokémon will be added to the cart using the Commerce Layer SDK.
- Leverage Hosted Checkout: Seamlessly transition the caught Pokémon into Commerce Layer’s hosted checkout for a streamlined purchasing experience.
Deploy a minimal microstore
Starting from the pokemart repository, the first thing to do is to create a /microstore
route that gets the list. In the current root layout we have elements that we don’t want in the microstore. I’m going to extract the auth and the header into a Page
component that I can use when I need it. Then, create a nested app/microstore/layout.tsx
to handle the and a app/microstore/list/[id]/page.tsx
. For our layout, we don’t need the CommerceLayerAuth
component we built, because the token it’s going to be passed by the link.
// app/microstore/layout.tsx
import { useSearchParams } from "next/navigation";
import {
CommerceLayer,
OrderContainer,
OrderStorage,
} from "@commercelayer/react-components";
export default function MicrostoreLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const searchParams = useSearchParams();
const accessToken = searchParams?.get("accessToken");
const slug = process.env.NEXT_PUBLIC_CL_SLUG;
const market = process.env.NEXT_PUBLIC_CL_MARKET;
if (!accessToken || !slug || !market) {
throw new Error(
"Missing access token, slug, or market in the URL/environment.",
);
}
const persistKey = `order-${market}`;
return (
<CommerceLayer
accessToken={accessToken}
endpoint={`https://${slug}.commercelayer.io`}
>
<OrderStorage persistKey={persistKey}>
{/* @ts-expect-error: OrderContainer children do not align to ReactNode type. */}
<OrderContainer>{children}</OrderContainer>
</OrderStorage>
</CommerceLayer>
);
}
First of all, we want to get the id
from the parameter and the sdkClient
from the useCommerceLayer
hook, which leverages the provider we’ve set in the layout and use to fetch details.
"use client";
import { useEffect, useState } from "react";
import { useParams } from "next/navigation";
import { useCommerceLayer } from "@commercelayer/react-components";
const MicrostorePage = () => {
const { id: skuListId } = useParams() || {};
const { sdkClient } = useCommerceLayer();
useEffect(() => {
const client = sdkClient();
const fetchSkus = async () => {
if (!skuListId) {
console.error("Missing SKU list ID.");
setLoading(false);
return;
}
try {
const skuList = await client?.sku_lists.retrieve(
Array.isArray(skuListId) ? skuListId[0] : skuListId,
{
include: ["skus"],
},
);
if (skuList?.skus) {
setSkuCodes(skuList.skus.map((sku: any) => sku.code)); // Map to an array of SKU codes
}
} catch (error) {
console.error("Error fetching SKU list:", error);
} finally {
setLoading(false);
}
};
fetchSkus();
}, [skuListId, sdkClient]);
if (loading) {
return <p>Loading SKUs...</p>;
}
return <code>{JSON.stringify(skuCodes)}</code>;
};
And now that we have this code, let’s set Commerce Layer to point at our custom microstore.
Use the Provisioning API to update the org
We will need to use the Provisioning API credential keys that are found in the user menu in the top right corner of the dashboard. Authorize the retrieval of the token. Now we’re ready to use the Provisioning API to change organizational Links to point at our microstore with the body looking like this:
{
"data": {
"type": "organizations",
"id": "<organization-id>",
"attributes": {
"config": {
"mfe": {
"default": {
"links": {
"microstore": "<link-to-microstore>/list/:sku_list_id?access_token=:accessToken"
}
}
}
}
}
}
}
Let’s test the Link
I’m going to create a “Starter Pokemon” SKU List and generate the Link to the microstore.
Tadà 🎉 , you should get the list of Pokémon SKU codes as per the code! Excellent.
But what if I want to be really creative and make the microstore a different experience from the main store, more focused? Since this experience will be a starter Pokémon, Pokéfans reading this blog post may know where I’m going with this.
The Starter Selector microstore
First of all, this has to be a laser-focused experience for the user: no menu, no cart, not even a logo. I want the experience to be like the starter selection in the games: three pokéballs, and when you select the Pokémon, it will get you to the cart. I’m taking inspiration from the Pokémon Black and White starter choice:
ChatGPT provides a pixelated lab pic to use as a background, the rest I’ll achieve with CSS 💅.
The experience is missing a last, very important step. When I select “yes”, I want to add a starter Pokémon to my cart and go directly to the checkout page with my brand new pokémon.
Be sure to checkout the repo at the tag step-3
🎉
Add the Pokémon to the order
When a user selects “yes”, I want to:
- Remove any existing line items because I want to allow only one.
- Add the selected Pokémon to the cart.
- Show the selected Pokémon and present a button to checkout.
To include line item data in the OrderContext
used by the useOrderContainer
, we need to wrap this component in the LineItemsContainer
component.
const { sdkClient, accessToken } = useCommerceLayer();
const { current: client } = useRef(sdkClient());
const { order, addToCart } = useOrderContainer();
const handlePokemonSelection = useCallback(async () => {
try {
if (!(client && selectedPokemon)) {
throw new Error("Missing access token, client, or pokemon");
}
if (order) {
await Promise.all(
(order.line_items || []).map((lineItem) => {
return client.line_items.delete(lineItem.id);
}),
);
}
await addToCart({
skuCode: selectedPokemon.name,
quantity: 1,
});
setSelectedPokemon(null);
} catch (error) {
console.error(error);
}
}, [order, selectedPokemon, addToCart, client]);
Then I can add the link to the checkout:
{order?.line_items?.length && selectedPokemon === null && (
<div className="self-center justify-self-center mt-auto relative z-20">
<CheckoutLink>
{({ href }) => {
return (
<a
href={href}
className="flex flex-col items-center justify-center p-5 h-full text-xl text-black shadow-pixel bg-white"
>
You have selected {order?.line_items?.[0]?.name}.
<span className="underline mt-2">
Start your adventure!
</span>
</a>
);
}}
</CheckoutLink>
</div>
)}
And now we have a completely customized microstore for any SKU List. We could have the same for every generation starter, or between Pikachu and Eevee!.
Conclusion
In this project, I harnessed the power of Commerce Layer Links to build a fully customized microstore with minimal effort. What sets this apart is how easily we transformed a simple SKU list into an interactive, immersive experience. This showcases not just the technical flexibility of Links, but also the boundless creative potential they unlock. Whether it’s for a product campaign, a themed promotion, or even a temporary sales channel, Commerce Layer Links enable businesses to spin up unique, targeted microstores that bring their brand vision to life, all without needing a full website. The possibilities are only limited by your imagination.