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.
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)
- Install the SDK:
npm install @retakeapi/js
- 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'))
});
}
- Mark conversion on success:
export async function completeCheckout(orderId: string, userId: string, orderValue: number) {
await retake.trackConversion({
userId,
transactionId: orderId,
value: orderValue
});
}
Shopify Hydrogen
- 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
- Use test mode: Set
testMode: truein your config - Trigger a test abandon: Add to cart, enter email, leave site
- Check the dashboard: Verify the intent was tracked
- Receive test email: Preview the recovery email
- 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.