Back to Guides
Technical

Cart Recovery for Headless Commerce: Next.js, Hydrogen & Custom Stacks

How to implement abandoned cart recovery on headless e-commerce sites. Covers Next.js, Shopify Hydrogen, and custom React/Vue frontends.

F
Furkan M

The Headless Commerce Challenge

Headless commerce is the future—but traditional cart recovery tools weren't built for it.

Most abandoned cart solutions rely on:

  • DOM scraping scripts
  • Platform-specific plugins
  • Cookie-based tracking

None of these work well with:

  • Server-side rendered React apps
  • Shopify Hydrogen storefronts
  • Custom Next.js Commerce builds
  • Composable commerce architectures

Why Traditional Tools Fail on Headless

Problem 1: No DOM to Scrape

Classic cart recovery tools inject JavaScript that monitors form fields for email addresses. On headless sites with server-side rendering, this approach is brittle:

// This breaks on SSR and hydration
document.querySelector('[name="email"]').addEventListener('blur', ...)

Problem 2: No Plugin Ecosystem

Shopify apps, WooCommerce plugins, and Magento extensions don't exist for custom frontends.

Problem 3: Data Silos

When your frontend is decoupled from your backend, customer data lives in multiple places. Recovery tools can't access cart data from the API layer.

The Solution: API-First Recovery

Instead of scraping the frontend, send cart events directly from your application code:

// In your checkout component (Next.js App Router)
import { Retake } from '@retakeapi/js';

const retake = new Retake({ apiKey: process.env.RETAKE_KEY });

async function handleCheckoutIntent(customer: Customer, cart: Cart) {
  await retake.track({
    type: "checkout",
    userId: customer.id,
    email: customer.email,
    value: cart.subtotal,
    currency: cart.currency,
    items: cart.lineItems
  });
}

Implementation by Framework

Next.js (App Router)

  1. Install the SDK:
npm install @retakeapi/js
  1. Server-side tracking:
// app/checkout/actions.ts
'use server'

import { Retake } from '@retakeapi/js';

const retake = new Retake({ apiKey: process.env.RETAKE_KEY });

export async function saveCheckoutIntent(formData: FormData) {
  await retake.track({
    type: "checkout",
    userId: formData.get('userId'), // Ensure userId is available
    email: formData.get('email'),
    value: parseFloat(formData.get('total'))
  });
}
  1. Mark conversion on success:
export async function completeCheckout(orderId: string, userId: string, orderValue: number) {
  await retake.trackConversion({
    userId,
    transactionId: orderId,
    value: orderValue
  });
}

Shopify Hydrogen

  1. Add to your Remix loader:
// app/routes/checkout.tsx
import { Retake } from '@retakeapi/js';

export async function action({ request }: ActionArgs) {
  const formData = await request.formData();
  const retake = new Retake({ apiKey: process.env.RETAKE_KEY });
  
  await retake.track({
    type: "checkout",
    userId: formData.get('userId'),
    email: formData.get('email'),
    value: cart.cost.subtotalAmount.amount
  });
 
  // Continue with Shopify checkout
}

Custom React SPA

// hooks/useCartRecovery.ts
import { useEffect } from 'react';
import { Retake } from '@retakeapi/js';

const retake = new Retake({ apiKey: import.meta.env.VITE_RETAKE_KEY });

export function useCartRecovery(cart: Cart, customer: Customer | null) {
  useEffect(() => {
    if (cart.items.length > 0 && customer?.email) {
      retake.track({
        type: "checkout",
        userId: customer.id, // Ensure customer object has id
        email: customer.email,
        value: cart.total
      });
    }
  }, [cart, customer]);
}

Benefits of API-First Recovery

| Aspect | DOM Scraping | API-First | |--------|-------------|----------| | Works with SSR | ❌ | ✅ | | Works with any frontend | ❌ | ✅ | | Type-safe | ❌ | ✅ | | No bundle size impact | ❌ | ✅ | | Full data control | ❌ | ✅ | | Works offline | ❌ | ✅ |

Advanced: Webhook Integration

Listen to recovery events via webhooks:

// app/api/retake/webhook/route.ts
import { NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
  const event = await request.json();
  
  switch (event.type) {
    case 'recovery.sent':
      console.log('Recovery email sent to:', event.email);
      break;
    case 'recovery.opened':
      console.log('Recovery email opened');
      break;
    case 'recovery.converted':
      console.log('Cart recovered! Value:', event.value);
      break;
  }
  
  return Response.json({ received: true });
}

Testing Your Integration

  1. Use test mode: Set testMode: true in your config
  2. Trigger a test abandon: Add to cart, enter email, leave site
  3. Check the dashboard: Verify the intent was tracked
  4. Receive test email: Preview the recovery email
  5. Complete test conversion: Verify conversion tracking

Conclusion

Headless commerce requires headless recovery. By using API-first tools like Retake, you get:

  • Full compatibility with any frontend
  • Type-safe integration
  • Zero performance impact
  • Complete control over your data

Building a headless store? Try Retake's SDK for developer-first cart recovery.