KeystoneJS is a Node.js headless CMS and application framework that provides a GraphQL API and an auto-generated Admin UI. Since KeystoneJS is a backend framework (not a full CMS with a frontend), analytics issues live in your custom frontend code that consumes the KeystoneJS GraphQL API. Most KeystoneJS sites pair it with Next.js or a similar React framework.
KeystoneJS-Specific Debugging Approach
KeystoneJS serves as both your CMS and your API layer. Debugging analytics means checking the GraphQL API output and your frontend's rendering pipeline.
Verify GraphQL API Data
# Check if your KeystoneJS API returns analytics config
curl -s -X POST "http://localhost:3000/api/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ siteSettings { trackingId gtmContainerId } }"}' \
| python3 -m json.tool
// In browser console on your frontend
console.log('Analytics scripts:', document.querySelectorAll('script[src*="gtag"], script[src*="gtm"]').length);
// Check if KeystoneJS data made it to the client
console.log('SSR data:', window.__NEXT_DATA__?.props?.pageProps || 'not found');
Check KeystoneJS Server Logs
# KeystoneJS logs to stdout by default
# If running with Node:
pm2 logs keystone
# Or check the terminal where KeystoneJS is running
# Look for GraphQL query errors
grep -i "graphql\|error\|query" keystone.log
Most Common KeystoneJS Analytics Issues
1. GraphQL Query Not Fetching Settings
Symptoms: Frontend renders pages correctly but analytics code is missing. The GraphQL query for site settings returns null.
Root cause: The KeystoneJS list (model) for site settings either does not exist, has no published items, or the frontend query does not match the schema.
Diagnosis:
# Introspect the GraphQL schema
curl -s -X POST "http://localhost:3000/api/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name } } }"}' \
| python3 -m json.tool | grep -i "setting"
Fix: Ensure your KeystoneJS schema includes a settings model:
// In keystone.ts (Keystone 6)
import { list } from '@keystone-6/core';
import { text } from '@keystone-6/core/fields';
export const lists = {
SiteSettings: list({
isSingleton: true, // Keystone 6 singleton pattern
fields: {
trackingId: text({ label: 'GA Tracking ID' }),
gtmContainerId: text({ label: 'GTM Container ID' }),
customHeadScript: text({ ui: { displayMode: 'textarea' } }),
},
}),
// ... other lists
};
2. Access Control Blocking API Reads
Symptoms: GraphQL query returns null or access denied errors. Analytics config exists in the Admin UI but the frontend cannot read it.
Root cause: KeystoneJS access control rules may restrict reading the site settings list. If your frontend queries without authentication, access.operation.query may deny the request.
Fix: Allow public read access for site settings:
SiteSettings: list({
access: {
operation: {
query: () => true, // Allow unauthenticated reads
create: ({ session }) => !!session, // Restrict writes to admin
update: ({ session }) => !!session,
delete: ({ session }) => !!session,
},
},
fields: { /* ... */ },
}),
3. Admin UI Previews Inflating Analytics
Symptoms: Pageviews spike during content editing. KeystoneJS Admin UI URL patterns appear in GA.
Root cause: If your frontend and KeystoneJS run on the same origin, Admin UI interactions may trigger analytics on admin pages.
Fix:
// Suppress analytics on KeystoneJS admin routes
const isAdminUI = window.location.pathname.startsWith('/admin') ||
window.location.pathname.startsWith('/api/graphql');
if (isAdminUI) {
window['ga-disable-G-XXXXXXX'] = true;
}
4. SSR Data Fetching Failing Silently
Symptoms: Analytics works in development but not in production. No error messages visible.
Root cause: In production, the Next.js frontend may use a different URL to reach the KeystoneJS API. If the internal API URL is misconfigured, getServerSideProps or getStaticProps silently returns empty data.
Diagnosis:
# Check if your frontend can reach KeystoneJS from the server side
curl -s "http://keystone-internal:3000/api/graphql" \
-X POST -H "Content-Type: application/json" \
-d '{"query":"{ siteSettings { trackingId } }"}'
Fix: Ensure environment variables for the API URL are correctly set in production:
# .env.production
NEXT_PUBLIC_KEYSTONE_URL=https://api.your-site.com
KEYSTONE_INTERNAL_URL=http://keystone:3000 # For SSR fetches
5. Hot Module Replacement Re-executing Analytics in Dev
Symptoms: During development, analytics fires multiple times on each code change. Pageview counts are inflated in dev.
Root cause: Next.js HMR re-renders the layout component on each file save, re-executing analytics initialization code.
Fix: Guard against re-initialization:
// In your analytics initialization
if (typeof window !== 'undefined' && !window.__analyticsInitialized) {
window.__analyticsInitialized = true;
// Load and configure analytics
gtag('config', 'G-XXXXXXX');
}
Environment Considerations
- KeystoneJS 5 vs 6: Keystone 6 is a complete rewrite with different GraphQL schema generation, access control, and configuration. Ensure your debugging matches your version
- Combined vs. separate deployment: Some sites run KeystoneJS and Next.js in the same process, others deploy them separately. Network connectivity between services matters for SSR
- Database dependency: KeystoneJS requires PostgreSQL or SQLite. Database connectivity issues can cause empty API responses including missing analytics config
- No built-in CDN: KeystoneJS does not include caching by default. Add caching at the reverse proxy level if API latency affects page rendering
Performance Issues
- LCP Issues - GraphQL query latency and SSR rendering overhead
- CLS Issues - Layout shifts from async data fetching and client-side hydration
Tracking Issues
- Events Not Firing - Debug GraphQL access control, API connectivity, and SSR data fetching
Related Resources
- KeystoneJS documentation
- Global Issues Hub for platform-agnostic solutions