Spree Commerce is a Ruby on Rails-based open-source ecommerce platform. Analytics issues on Spree typically involve the Rails asset pipeline (Sprockets/Propshaft) modifying JavaScript, Turbo (or Turbolinks in older versions) intercepting page navigation, Deface overrides not injecting tracking code correctly, and the complexity of building an ecommerce dataLayer from Spree's order/product models.
Spree-Specific Debugging Approach
Spree is a Rails engine. Your storefront is a Rails app that mounts the Spree engine. Analytics code can be injected through the Rails layout, Deface overrides, or Spree extensions.
Check the Rails Layout
# SSH into your Spree server
# Find the main application layout
find app/views/layouts -name "*.html.erb" -o -name "*.html.slim" -o -name "*.html.haml" | head -5
# Check for analytics in the layout
grep -rn "gtag\|gtm\|analytics\|dataLayer" app/views/layouts/
# Check Spree's default layout (if not overridden)
grep -rn "gtag\|gtm\|analytics" $(bundle show spree_frontend)/app/views/layouts/ 2>/dev/null
Check Turbo/Turbolinks Behavior
// In browser console, check if Turbo or Turbolinks is active
console.log('Turbo:', typeof Turbo !== 'undefined' ? 'Active' : 'Not found');
console.log('Turbolinks:', typeof Turbolinks !== 'undefined' ? 'Active' : 'Not found');
console.log('Page visits:', performance.getEntriesByType('navigation')[0]?.type);
// Check if analytics scripts are in the head
document.querySelectorAll('script[src*="gtag"], script[src*="gtm"]').forEach(s => {
console.log('Analytics:', s.src, '| data-turbo-track:', s.dataset.turboTrack || 'none');
});
Most Common Spree Analytics Issues
1. Turbo/Turbolinks Breaking Pageview Tracking
Symptoms: Only the first page load fires a pageview. Navigating between pages (products, categories, cart) shows no additional analytics hits.
Root cause: Spree 4.x+ uses Turbo (or Turbolinks in 3.x). These libraries intercept link clicks and replace the <body> without a full page reload, so gtag.js never re-fires.
Fix: Listen for Turbo navigation events:
// For Spree 4.x+ with Turbo
document.addEventListener('turbo:load', function() {
gtag('event', 'page_view', {
page_path: window.location.pathname,
page_title: document.title
});
});
// For older Spree with Turbolinks
document.addEventListener('turbolinks:load', function() {
gtag('event', 'page_view', {
page_path: window.location.pathname,
page_title: document.title
});
});
2. Asset Pipeline Mangling Analytics JavaScript
Symptoms: Analytics JavaScript has syntax errors after deployment. Works in development but breaks in production.
Root cause: Rails' asset pipeline (Sprockets) can mangle JavaScript during compression/minification. Terser or UglifyJS may break non-standard JavaScript patterns.
Fix: Exclude analytics from the asset pipeline by loading it directly:
<%# In app/views/layouts/application.html.erb %>
<head>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%# Load analytics OUTSIDE the asset pipeline %>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX" data-turbo-track="reload"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXX');
</script>
<%= stylesheet_link_tag 'application' %>
<%= javascript_include_tag 'application', 'data-turbo-track': 'reload' %>
</head>
3. Ecommerce DataLayer Missing Order Data
Symptoms: Pageviews track but purchase events have no revenue, product, or transaction data.
Root cause: Spree's order confirmation page needs to expose order data to the analytics dataLayer. This is not automatic — you must extract data from Spree's @order object.
Fix: Create a partial for the order confirmation page:
<%# In app/views/spree/checkout/complete.html.erb (or via Deface override) %>
<script>
dataLayer.push({
'event': 'purchase',
'ecommerce': {
'transaction_id': '<%= @order.number %>',
'value': <%= @order.total.to_f %>,
'currency': '<%= @order.currency %>',
'shipping': <%= @order.ship_total.to_f %>,
'tax': <%= @order.tax_total.to_f %>,
'items': [
<% @order.line_items.each_with_index do |item, i| %>
{
'item_id': '<%= item.variant.sku %>',
'item_name': '<%= j(item.product.name) %>',
'price': <%= item.price.to_f %>,
'quantity': <%= item.quantity %>
}<%= ',' unless i == @order.line_items.size - 1 %>
<% end %>
]
}
});
</script>
4. Deface Override Not Injecting Code
Symptoms: You created a Deface override to inject analytics but it does not appear in the rendered page.
Root cause: Deface overrides target specific selectors in Spree's views. If Spree's view structure changed in an upgrade, or if your storefront uses a custom theme that replaced the default views, the Deface selector no longer matches.
Diagnosis:
# Check if Deface overrides are loading
rails console
Deface::Override.all.each { |k, v| puts "#{k}: #{v.map(&:name).join(', ')}" }
# Check the target view exists
find $(bundle show spree_frontend)/app/views -name "*.erb" | head -20
Fix: Update the Deface selector to match your current view structure, or inject directly into your layout instead.
5. Spree API Storefront (Headless) Not Tracking
Symptoms: Using Spree's API with a JavaScript frontend (React, Vue, Next.js). No analytics tracking at all.
Root cause: Spree's headless API mode serves JSON, not HTML. The Rails layout with analytics code never renders.
Fix: Implement analytics entirely in your JavaScript frontend, using Spree API response data:
// After fetching order confirmation from Spree API
const order = await spreeClient.checkout.complete(orderToken);
gtag('event', 'purchase', {
transaction_id: order.data.attributes.number,
value: parseFloat(order.data.attributes.total),
currency: order.data.attributes.currency
});
Environment Considerations
- Ruby/Rails version: Spree 4.x requires Rails 6+/7+. Older Spree versions may use Turbolinks instead of Turbo, and Sprockets instead of Propshaft
- Spree extensions: Analytics extensions like
spree_analytics_trackersexist but may not support GA4. Check compatibility before installing - Docker development: Many Spree setups use Docker. Ensure analytics configuration survives container rebuilds by placing it in version-controlled files, not in the database
- Spree Commerce vs Solidus: Solidus is a fork of Spree. The analytics approach is similar but template paths differ
- Redis/Sidekiq: Spree uses background jobs for order processing. Analytics must fire from the client-side confirmation page, not from a background job
Performance Issues
- LCP Issues - Rails rendering overhead, asset pipeline loading, and Turbo prefetch timing
- CLS Issues - Layout shifts from Turbo page swaps and dynamic cart widget updates
Tracking Issues
- Events Not Firing - Debug Turbo navigation, asset pipeline conflicts, and ecommerce dataLayer gaps
Related Resources
- Spree Commerce documentation
- Global Issues Hub for platform-agnostic solutions