DEV Community

seller-mind
seller-mind

Posted on

Why I Built a Lightweight Shopify Tracking Widget from Scratch

When I first started helping a friend manage their Shopify store, I assumed the hardest part was driving traffic and getting that initial sale. I was completely wrong. The real headache started right after the checkout.

Within a week of launching, my inbox was flooded with variations of the same question: "Where is my order?" We were dealing with a massive spike in WISMO (Where Is My Order) support tickets. Customers were anxious, our response times were slipping, and the post-purchase experience felt completely broken. We were using the default Shopify order status page, which was functional but entirely devoid of our brand's personality.

I looked at the existing third-party tracking apps on the Shopify App Store. Some were incredibly feature-rich but felt bloated, slowing down the storefront and injecting their own upsell banners. Others were lightweight but lacked the real-time carrier integrations we needed.

So I decided to build something.

The Problem with Post-Purchase Tracking

The post-purchase experience is often treated as an afterthought in e-commerce, but it is actually a critical touchpoint for customer retention. When a customer clicks "buy," they enter a period of anticipation. If the tracking experience is clunky, unbranded, or outdated, it creates cognitive dissonance.

The cost of ignoring this is tangible. High WISMO ticket volumes drain support resources, pulling your team away from proactive growth tasks. More importantly, a frustrating tracking experience reduces the likelihood of repeat purchases.

However, solving this technically presents a few trade-offs. You can build a custom Liquid page, which gives you total design control but makes real-time API integrations difficult and breaks easily during theme updates. Alternatively, you can use a heavy third-party app, which is easy to install but often compromises your site's performance and brand consistency. I needed a middle ground: a solution that was easy to install, performed well, and kept the merchant in control of the design.

The Technical Approach: App Embeds vs. Custom Code

After evaluating the trade-offs, I settled on using Shopify's App Embed blocks. This approach allows an app to inject UI components into a theme without requiring the merchant to manually edit their theme code. It keeps the tracking widget sandboxed and ensures that theme updates don't accidentally wipe out the tracking functionality.

The core challenge was passing the necessary order context from Shopify's Liquid templating engine to our JavaScript widget, without blocking the main thread or hurting Core Web Vitals.

Here is the architecture I used for the widget injection:

{% comment %}
  Renders shipment tracking widget on order status pages
{% endcomment %}

<div id="parcelglance-widget" 
     data-order-id="{{ order.id }}"
     data-order-number="{{ order.name }}"
     data-store-name="{{ shop.name }}"
     style="min-height: 200px;">
</div>

<script>
(function() {
  const script = document.createElement('script');
  script.src = 'https://cdn.parcelglance.com/widget.js';
  script.async = true;
  script.onload = function() {
    Parcelglance.init({
      orderId: document.getElementById('parcelglance-widget').dataset.orderId,
      orderNumber: document.getElementById('parcelglance-widget').dataset.orderNumber,
      storeName: document.getElementById('parcelglance-widget').dataset.storeName,
      container: 'parcelglance-widget',
    });
  };
  document.head.appendChild(script);
})();
</script>
Enter fullscreen mode Exit fullscreen mode

Let's break down the key decisions here:

  1. Data Attributes for Context: Instead of injecting Liquid variables directly into a JavaScript block (which can cause syntax errors if a store name contains an unescaped quote), I used HTML data-* attributes. The JavaScript then reads these safely using the dataset API.
  2. Asynchronous Loading: The script is created dynamically and set to async = true. This is crucial. Tracking widgets are not critical for the initial paint of the page. By loading it asynchronously, we ensure that the widget's JavaScript doesn't block the rendering of the rest of the order status page.
  3. Minimum Height: The container has a min-height: 200px inline style. This prevents Cumulative Layout Shift (CLS) when the widget finally loads and renders its content, keeping the page visually stable.

On the backend, instead of polling carrier APIs directly from the frontend (which exposes API keys and causes CORS headaches), the app listens to Shopify's fulfillment webhooks. When an order is fulfilled, Shopify sends a webhook, and our backend securely fetches the real-time tracking status from the carrier, storing it for the widget to retrieve.

Practical Results and Takeaways

Building this out taught me a lot about the realities of merchant needs versus developer assumptions.

What worked really well was the App Embed approach. Merchants loved that they could toggle the widget on or off from the theme editor without touching a single line of code. The async loading also meant that our Lighthouse performance scores remained virtually identical with or without the widget installed.

What surprised me was how much merchants valued simple customization over complex features. Initially, I thought about adding complex timeline animations and upsell carousels. But when I tested it, merchants just wanted the tracking page to match their store's fonts and colors. Keeping the widget highly customizable via simple CSS variables was a much bigger win than adding flashy animations.

If I were to change one thing, I would have spent more time on the initial carrier mapping logic. Every carrier formats their tracking events slightly differently. Normalizing "Out for Delivery," "Arriving Today," and "Transit" into a unified status enum took more backend tweaking than I initially anticipated.

Moving Forward

If you are running a Shopify store and struggling with the post-purchase experience, you don't necessarily need a massive, expensive enterprise app. Sometimes, a lightweight, well-architected widget is all you need to keep your customers informed and reduce those support tickets.

I packaged the approach I built into a free tool called Parcelglance. It provides a branded, real-time tracking widget that installs easily via Shopify App Embed blocks, letting you focus on your store rather than answering "where is my order?" emails.

You can check out the free Shipment Tracking Widget at parcelglance.com.


Full disclosure: I'm the developer of Parcelglance. I built this because I needed it myself.

Top comments (0)