Why I Chose App Blocks Over Custom Pages for Shopify Tracking

2026-11-04来源 dev.to WebDevEnglish

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

My inbox was suddenly flooded with "Where is my order?" (WISMO) emails. We were spending hours manually checking tracking numbers across different carrier websites and replying to anxious customers. It was exhausting, unscalable, and felt incredibly unprofessional. I looked at the existing tracking apps on the Shopify App Store, but many required complex setups, felt bloated, or hijacked the post-purchase experience with heavy third-party branding.

So, I decided to build something.

The Post-Purchase Problem

The post-purchase experience is often an afterthought in e-commerce. Merchants spend thousands on ads and checkout optimization, only to leave the customer in the dark for two weeks while waiting for shipping.

The cost of ignoring this isn't just the time spent answering WISMO support tickets. It's the loss of customer trust. If a buyer has to email you to find out their package is delayed, or if they are redirected to a clunky, unbranded third-party page to check their status, the brand experience is broken. They won't come back for a second purchase.

To solve this, store owners and developers generally have two technical approaches: building a completely custom tracking page on a separate subdomain (or headless setup), or using Shopify's native Theme App Extensions (App Embed blocks). Let's analyze the trade-offs of both.

Approach 1: The Custom Subdomain Route

You build a separate React or Next.js application, fetch order data via the Shopify Admin API, and link to it in your post-purchase emails.

Pros: You have total control over the UI/UX and aren't constrained by theme layouts.
Cons: High maintenance. It breaks the native feel of the store, requires managing separate authentication, and adds friction for the merchant to set up DNS and hosting.

Approach 2: Shopify App Embed Blocks

This approach injects the tracking widget directly into the native Shopify order status page using Theme App Extensions.

Pros: Seamless, native feel. It's much easier for merchants to install and configure without touching code.
Cons: You are restricted to the layout constraints of the Shopify order status page, and you have to ensure your CSS doesn't conflict with the merchant's theme.

I ultimately chose App Embed blocks for Parcelglance because the native integration drastically reduces friction for the merchant, and the post-purchase page is already a trusted environment.

The Technical Approach

Building an App Embed block requires a shift in how you think about frontend architecture. You aren't building a standalone app; you are building a widget that needs to live harmoniously inside someone else's DOM.

Here is how the frontend injection works. We use Liquid to pass the order context securely to the client-side script, and then lazy-load the widget.

{% 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> 

By passing the order.id and order.name via data attributes, we avoid making unnecessary API calls on the frontend to figure out which order we are looking at. The script then lazy-loads the tracking widget, ensuring we don't block the main thread or slow down the order status page.

But how do we get the tracking data in the first place? We need to listen to Shopify webhooks to keep our database in sync with carrier updates. Security here is paramount.

const crypto = require('crypto'); function verifyShopifyWebhook(rawBody, hmacHeader, secret) { const hash = crypto .createHmac('sha256', secret) .update(rawBody, 'utf8') .digest('base64'); return hash === hmacHeader; } // Handle fulfillment update webhook app.post('/webhooks/orders/fulfilled', express.raw({ type: 'application/json' }), async (req, res) => { const verified = verifyShopifyWebhook( req.body, req.headers['x-shopify-hmac-sha256'], process.env.SHOPIFY_WEBHOOK_SECRET ); if (!verified) return res.status(401).send('Invalid'); const order = JSON.parse(req.body.toString('utf8')); // Store fulfillment data for tracking lookup await db.fulfillments.upsert({ orderId: order.id, trackingNumber: order.fulfillments[0]?.tracking_number, carrier: order.fulfillments[0]?.tracking_company, status: 'shipped', }); res.status(200).send('OK'); } ); 

We verify the HMAC signature to ensure the webhook actually came from Shopify. Then, we parse the fulfillment data and store it. This architecture allows our widget to fetch real-time status from our own database, rather than constantly hammering the Shopify Admin API on the frontend, which would quickly hit rate limits.

Practical Results and Trade-offs

Moving to App Embed blocks was a massive win for adoption. Merchants could install the widget without touching their theme code, which is a huge barrier to entry for non-technical store owners.

What surprised me the most during development was how much the customizable styling mattered. Even with a native feel, store owners wanted the tracking widget to match their exact brand colors and typography. I had to build a robust CSS variable injection system so the widget seamlessly adapts to any theme without leaking styles or getting overridden by the theme's global CSS.

If I could change one thing about my initial architecture, I would have started with a more robust caching layer for carrier API calls from day one. Early on, I was querying carrier APIs in real-time for every widget load. I quickly had to implement a Redis caching layer to store tracking statuses and only poll the carriers periodically, which saved a lot of overhead.

Wrapping Up

Building for the Shopify ecosystem requires balancing developer control with merchant simplicity. By choosing App Embed blocks over custom subdomains, we traded a bit of UI control for a much smoother installation and native user experience.

If you're a Shopify developer or store owner looking to improve your post-purchase experience and reduce those WISMO tickets, I've made the core widget available as a free tool. You can check out the Shipment Tracking Widget at parcelglance.com to see how App Embed blocks can transform your order status page.

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

原文:https://www.a8q9.cn/en/article/why-i-chose-app-blocks-over-custom-pages-for-shopify-tracking-8d6b5d.html

相关文章