Documentation Index
Fetch the complete documentation index at: https://docs.glood.ai/llms.txt
Use this file to discover all available pages before exploring further.
Content Security Policy Setup
To enable Glood pixel tracking, you need to configure your Content Security Policy (CSP) to whitelist Glood’s domains. This allows the SDK to send analytics events to Glood endpoints.
Required CSP Configuration
Add the following Glood domains to your connectSrc directive in your CSP configuration:
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://events.glood.ai',
'https://s-pixel.glood.ai',
'https://w-pixel.glood.ai'
],
});
Glood Domains Explained
Required Pixel Endpoints
| Domain | Purpose | Used By |
https://events.glood.ai | Recommendations pixel tracking | Recommendations app |
https://s-pixel.glood.ai | Search analytics tracking | Search app |
https://w-pixel.glood.ai | Wishlist analytics tracking | Wishlist app |
Why These Domains Are Needed
- Event Transmission: The SDK sends analytics events to these endpoints via
fetch() API calls
- Privacy Compliance: Events are only sent when customer consent is granted
- Performance Tracking: Enables monitoring of user interactions and e-commerce metrics
- Personalization: Allows Glood to provide personalized recommendations and search results
Implementation in Hydrogen
1. Root Layout Configuration
Add the CSP configuration to your root layout file:
// app/root.tsx
import {
json,
type LoaderFunctionArgs,
} from '@shopify/remix-oxygen';
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from '@remix-run/react';
import {
Analytics,
getShopAnalytics,
useNonce,
} from '@shopify/hydrogen';
import { createContentSecurityPolicy } from '@shopify/hydrogen';
export async function loader({context}: LoaderFunctionArgs) {
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://events.glood.ai', // Recommendations pixel
'https://s-pixel.glood.ai', // Search pixel
'https://w-pixel.glood.ai' // Wishlist pixel
],
});
return json({
nonce,
header,
// ... other loader data
});
}
export default function App() {
const nonce = useNonce();
const data = useLoaderData<typeof loader>();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Analytics>
<GloodProvider client={glood} loaderData={data}>
<Layout>
<Outlet />
</Layout>
</GloodProvider>
</Analytics>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
</body>
</html>
);
}
export function headers({loaderHeaders}: {loaderHeaders: Headers}) {
return {
'Content-Security-Policy': loaderHeaders.get('Content-Security-Policy'),
};
}
2. Environment-Specific Configuration
Configure different endpoints for different environments:
export async function loader({context}: LoaderFunctionArgs) {
const isDevelopment = context.env.NODE_ENV === 'development';
const isStaging = context.env.NODE_ENV === 'staging';
const gloodDomains = isDevelopment
? [
'https://dev-events.glood.ai',
'https://dev-s-pixel.glood.ai',
'https://dev-w-pixel.glood.ai'
]
: isStaging
? [
'https://staging-events.glood.ai',
'https://staging-s-pixel.glood.ai',
'https://staging-w-pixel.glood.ai'
]
: [
'https://events.glood.ai',
'https://s-pixel.glood.ai',
'https://w-pixel.glood.ai'
];
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
...gloodDomains
],
});
return json({ nonce, header });
}
3. Custom Domain Configuration
If you’re using custom Glood endpoints:
export async function loader({context}: LoaderFunctionArgs) {
const customEndpoints = [
context.env.GLOOD_RECOMMENDATIONS_PIXEL_ENDPOINT,
context.env.GLOOD_SEARCH_PIXEL_ENDPOINT,
context.env.GLOOD_WISHLIST_PIXEL_ENDPOINT,
].filter(Boolean); // Remove undefined values
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
...customEndpoints
],
});
return json({ nonce, header });
}
CSP Directives Explained
connectSrc
The connectSrc directive controls which URLs can be loaded using script interfaces:
- fetch() - Used by the SDK to send pixel events
- XMLHttpRequest - Alternative HTTP request method
- WebSocket - Real-time connections (not used by Glood)
- EventSource - Server-sent events (not used by Glood)
Why Not Other Directives?
| Directive | Not Required | Reason |
scriptSrc | ❌ | Glood doesn’t load external scripts |
imgSrc | ❌ | Glood doesn’t load tracking pixels as images |
frameSrc | ❌ | Glood doesn’t embed iframes |
mediaSrc | ❌ | Glood doesn’t load media files |
Troubleshooting CSP Issues
1. Check Console Errors
CSP violations appear in the browser console:
Content Security Policy: The page's settings blocked the loading of a resource at https://events.glood.ai/
2. Verify Domain Configuration
Ensure all Glood domains are included:
// ❌ Missing domains will cause CSP violations
connectSrc: [
'https://cdn.shopify.com',
'https://events.glood.ai', // Only recommendations pixel
// Missing search and wishlist pixels!
],
// ✅ All required domains included
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://events.glood.ai', // Recommendations
'https://s-pixel.glood.ai', // Search
'https://w-pixel.glood.ai' // Wishlist
],
3. Test with Debug Mode
Enable debug mode to see pixel transmission attempts:
const glood = createGlood({
apiKey: process.env.GLOOD_API_KEY!,
myShopifyDomain: 'your-store.myshopify.com',
debug: true, // Enable debug logging
});
Debug logs will show:
[Glood Debug] Sending event to https://events.glood.ai (attempt 1)
[Glood Debug] Successfully sent event to https://events.glood.ai
If CSP is blocking requests, you’ll see network errors instead.
Check that CSP headers are being sent:
# Check CSP header in response
curl -I https://your-store.com | grep -i content-security-policy
# Expected output:
Content-Security-Policy: default-src 'self'; connect-src 'self' https://cdn.shopify.com https://*.shopifycloud.com https://events.glood.ai https://s-pixel.glood.ai https://w-pixel.glood.ai;
Advanced CSP Configuration
1. Strict CSP with Nonces
Use nonces for enhanced security:
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
defaultSrc: ["'none'"],
scriptSrc: [`'nonce-${nonce}'`, "'strict-dynamic'"],
connectSrc: [
"'self'",
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://events.glood.ai',
'https://s-pixel.glood.ai',
'https://w-pixel.glood.ai'
],
styleSrc: [`'nonce-${nonce}'`, "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
});
2. Report-Only Mode
Test CSP changes without blocking requests:
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://events.glood.ai',
'https://s-pixel.glood.ai',
'https://w-pixel.glood.ai'
],
reportOnly: true, // Report violations without blocking
});
3. Domain Wildcards
Use wildcards for subdomain flexibility:
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://*.glood.ai', // Wildcard for all Glood subdomains
],
⚠️ Note: Wildcards are less secure than explicit domain lists.
Security Considerations
1. Principle of Least Privilege
Only include domains that are actually needed:
// ✅ Good - Only required domains
connectSrc: [
'https://events.glood.ai',
'https://s-pixel.glood.ai',
'https://w-pixel.glood.ai'
],
// ❌ Avoid - Overly permissive
connectSrc: [
'https://*.glood.ai', // Too broad
'https://*', // Far too broad
'*' // Completely insecure
],
2. Environment Separation
Use different domains for different environments:
// Development: Use dev domains
'https://dev-events.glood.ai'
// Staging: Use staging domains
'https://staging-events.glood.ai'
// Production: Use production domains
'https://events.glood.ai'
3. Regular Review
- Review CSP regularly to ensure it’s still appropriate
- Remove unused domains when disabling Glood apps
- Monitor CSP violations in production
- Test changes in staging environments first
Integration Checklist
Common Issues
Issue: CSP Blocking Pixel Requests
Symptoms: Network errors, no pixel events sent
Solution: Add missing Glood domains to connectSrc
Symptoms: CSP violations not shown, headers missing
Solution: Ensure CSP headers are returned from loader and applied in headers function
Issue: Wrong Environment Domains
Symptoms: 404 errors on pixel endpoints
Solution: Verify correct domains for current environment
Issue: Wildcard Domain Issues
Symptoms: Inconsistent blocking behavior
Solution: Use explicit domain lists instead of wildcards
Example Implementation
Complete working example:
// app/root.tsx
import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {Analytics, createContentSecurityPolicy} from '@shopify/hydrogen';
import {GloodProvider, createGlood} from '@glood/hydrogen';
import {recommendations, search, wishlist} from '@glood/hydrogen';
const glood = createGlood({
apiKey: process.env.GLOOD_API_KEY!,
myShopifyDomain: process.env.PUBLIC_STORE_DOMAIN!,
debug: process.env.NODE_ENV === 'development',
})
.use(recommendations())
.use(search())
.use(wishlist());
export async function loader({context}: LoaderFunctionArgs) {
const {nonce, header} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://events.glood.ai',
'https://s-pixel.glood.ai',
'https://w-pixel.glood.ai'
],
});
return json({
nonce,
header,
analytics: getShopAnalytics({
storefront: context.storefront,
publicStorefrontId: context.env.PUBLIC_STOREFRONT_ID,
}),
});
}
export default function App() {
const {analytics} = useLoaderData<typeof loader>();
return (
<html>
<head>
<Meta />
<Links />
</head>
<body>
<Analytics analytics={analytics}>
<GloodProvider client={glood} loaderData={data}>
<Outlet />
</GloodProvider>
</Analytics>
<Scripts />
</body>
</html>
);
}
export function headers({loaderHeaders}: {loaderHeaders: Headers}) {
return {
'Content-Security-Policy': loaderHeaders.get('Content-Security-Policy'),
};
}
This configuration ensures that:
- ✅ All Glood pixel endpoints are whitelisted
- ✅ CSP headers are properly applied
- ✅ Pixel tracking works correctly
- ✅ Security is maintained with explicit domain lists
See Also