As Sitecore developers transition from traditional MVC development to headless Next.js applications with XM Cloud, one of the most common questions is: “How do I debug my application like I used to in Visual Studio?”
The good news is that modern Next.js development offers even more powerful debugging capabilities than traditional server-side development. This two-part series will equip you with comprehensive debugging strategies specifically tailored for Sitecore XM Cloud development.
Part 1 (this post) covers the essential debugging techniques that form the foundation of your development workflow:
- VS Code Integrated Debugging
- Sitecore JSS Debug Logging
- Custom Debug Logging
- Programmatic Debugging
Part 2 will dive into advanced debugging strategies including browser DevTools, console debugging patterns, and XM Cloud-specific debugging techniques.
Let’s start with the core debugging approaches every XM Cloud developer needs to master.
1. VS Code Integrated Debugging: Your New Visual Studio
Coming from Visual Studio, you’ll feel right at home with VS Code’s debugging capabilities. You can set breakpoints, step through code, and inspect variables just like you’re used to.
Step-by-Step Setup
Step 1: Create Debug Configuration
Create a .vscode folder in your project root if it doesn’t exist, then create a launch.json file inside it:
json
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev",
"console": "integratedTerminal"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
},
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev",
"console": "integratedTerminal",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}
Step 2: Set Your First Breakpoints
Click in the left margin (line numbers area) to set breakpoints in your code:
javascript
// pages/api/sitecore/layout.js
export default async function handler(req, res) {
const { path } = req.query;
// Set breakpoint on this line ← Click here in VS Code
const layoutData = await layoutService.fetchLayoutData(path);
// Set another breakpoint here to inspect the response
res.json(layoutData);
}
Step 3: Start Debugging
- Open the Debug panel in VS Code (Ctrl+Shift+D)
- Select your debug configuration from the dropdown
- Press F5 or click the green play button
- VS Code will start your Next.js app and attach the debugger
Step 4: Debug Your XM Cloud Integration
Once running, navigate to a page that triggers your breakpoints. VS Code will pause execution and show:
- Variables panel – Inspect
layoutData,req,resobjects - Call Stack – See the execution path
- Debug Console – Execute JavaScript expressions
- Watch panel – Monitor specific variables
Advanced VS Code Debugging Features
Conditional Breakpoints: Right-click on a breakpoint to set conditions:
javascript
// Only pause when debugging specific Sitecore items
if (layoutData.sitecore.route.itemId === '{YOUR-ITEM-ID}') {
// This breakpoint will only trigger for this item
}
Logpoints (Non-breaking breakpoints): Right-click in the margin and select “Add Logpoint” to log without stopping execution.
What You Can Debug
- Server-side rendering (SSR) – Debug
getServerSidePropsandgetStaticProps - API routes – Step through your custom API endpoints
- Middleware – Debug authentication, redirects, and custom logic
- Component rendering – Inspect React component lifecycle and state
- Sitecore JSS integration – Debug layout service calls and data fetching
2. Sitecore JSS Debug Logging: Understanding the Framework
Sitecore JSS uses the debug npm package internally, giving you deep visibility into framework operations. This is invaluable for understanding what’s happening behind the scenes with your XM Cloud integration.
Step-by-Step Setup
Step 1: Understanding the Debug Package
The debug package is already included in your JSS project. No additional installation needed! It works by reading environment variables to determine what to log.
Step 2: Enable JSS Debug Logging
Open your terminal and run your Next.js app with debug flags:
bash
# Windows (Command Prompt)
set DEBUG=sitecore-jss:* && npm run dev
# Windows (PowerShell)
$env:DEBUG="sitecore-jss:*"; npm run dev
# macOS/Linux
DEBUG=sitecore-jss:* npm run dev
Step 3: Add Debug Scripts to package.json
Make debugging easier by adding these scripts:
json
{
"scripts": {
"dev": "next dev",
"dev:debug": "DEBUG=sitecore-jss:* npm run dev",
"dev:debug-layout": "DEBUG=sitecore-jss:layout npm run dev",
"dev:debug-http": "DEBUG=sitecore-jss:http npm run dev",
"dev:debug-editing": "DEBUG=sitecore-jss:editing npm run dev"
}
}
Now you can run: npm run dev:debug-layout
Essential JSS Debug Namespaces
Layout Service Debugging:
bash
DEBUG=sitecore-jss:layout npm run dev
What you’ll see: Layout service requests, response data, route resolution Use when: Components aren’t rendering, missing data, routing issues
HTTP Request Debugging:
bash
DEBUG=sitecore-jss:http npm run dev
What you’ll see: GraphQL queries, HTTP requests/responses, API calls Use when: Slow loading, failed requests, GraphQL query issues
Experience Editor Debugging:
bash
DEBUG=sitecore-jss:editing npm run dev
What you’ll see: Editor integration, preview mode, editing context Use when: Experience Editor issues, preview not working
Multiple Namespaces:
bash
DEBUG=sitecore-jss:layout,sitecore-jss:http npm run dev
Advanced Debug Configuration
Environment Variables for Better Output:
bash
# Pretty-print objects on multiple lines
DEBUG_MULTILINE=true DEBUG=sitecore-jss:layout npm run dev
# Increase object inspection depth
DEBUG_DEPTH=4 DEBUG=sitecore-jss:* npm run dev
# Hide timestamps for cleaner output
DEBUG_HIDE_DATE=true DEBUG=sitecore-jss:* npm run dev
# Disable colors (useful for log files)
DEBUG_COLORS=false DEBUG=sitecore-jss:* npm run dev
Reading Debug Output
When you enable debug logging, you’ll see output like this:
sitecore-jss:layout Fetching layout data for path: /products +0ms
sitecore-jss:http POST https://your-xmcloud-instance.sitecorecloud.io/api/graphql +5ms
sitecore-jss:http GraphQL query: query LayoutQuery($path: String!) { ... } +2ms
sitecore-jss:layout Layout data received: { sitecore: { context: { ... } } } +125ms
Understanding the Format:
sitecore-jss:layout– The namespaceFetching layout data...– The debug message+125ms– Time since last debug message in this namespace
3. Custom Debug Logging: Your Application Logic
Implement your own debug logging alongside JSS’s built-in logging to track your custom application behavior.
Step-by-Step Implementation
Step 1: Install debug package (if not already available)
bash
npm install debug
Step 2: Create a debug utility module
Create src/utils/debug.js:
javascript
const debug = require('debug');
// Create different debug channels for different parts of your app
export const componentDebug = debug('myapp:components');
export const dataDebug = debug('myapp:data');
export const authDebug = debug('myapp:auth');
export const personalizationDebug = debug('myapp:personalization');
export const performanceDebug = debug('myapp:performance');
// Helper function for conditional debugging
export const debugIf = (condition, debugFn, message, ...args) => {
if (condition) {
debugFn(message, ...args);
}
};
Step 3: Use in your components
javascript
// components/ProductListing.jsx
import { componentDebug, dataDebug } from '../utils/debug';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';
export default function ProductListing({ fields }) {
const { sitecoreContext } = useSitecoreContext();
componentDebug('ProductListing rendering with fields: %o', fields);
useEffect(() => {
componentDebug('ProductListing mounted');
dataDebug('Sitecore context: %o', sitecoreContext);
}, []);
const handleProductClick = (productId) => {
componentDebug('Product clicked: %s', productId);
// Your click logic here
};
return (
<div>
{/* Your component JSX */}
</div>
);
}
Step 4: Use in API routes
javascript
// pages/api/products/[id].js
import { dataDebug, performanceDebug } from '../../../utils/debug';
export default async function handler(req, res) {
const start = Date.now();
const { id } = req.query;
dataDebug('API called for product ID: %s', id);
dataDebug('Request method: %s', req.method);
dataDebug('Request headers: %o', req.headers);
try {
// Your API logic here
const product = await fetchProductFromXMCloud(id);
const duration = Date.now() - start;
performanceDebug('API call completed in %dms', duration);
res.json(product);
} catch (error) {
dataDebug('API error: %o', error);
res.status(500).json({ error: 'Failed to fetch product' });
}
}
Step 5: Enable your custom debug logs
bash
# Enable only your app's debug logs
DEBUG=myapp:* npm run dev
# Enable both JSS and your app's logs
DEBUG=sitecore-jss:*,myapp:* npm run dev
# Enable specific channels only
DEBUG=myapp:components,myapp:data npm run dev
# Exclude certain channels
DEBUG=myapp:*,-myapp:performance npm run dev
Advanced Custom Debug Patterns
Performance Monitoring:
javascript
// utils/debug.js
export const createPerformanceDebugger = (namespace) => {
const debug = require('debug')(namespace);
return {
start: (operation) => {
const start = Date.now();
debug(`Starting ${operation}`);
return () => {
const duration = Date.now() - start;
debug(`${operation} completed in ${duration}ms`);
};
}
};
};
// Usage in components
import { createPerformanceDebugger } from '../utils/debug';
const perfDebug = createPerformanceDebugger('myapp:performance');
export default function MyComponent() {
const endTimer = perfDebug.start('component-render');
// Your component logic
useEffect(() => {
endTimer(); // Logs: "component-render completed in 15ms"
}, []);
}
Conditional Debug Based on Sitecore Context:
javascript
// utils/sitecoreDebug.js
import { debugIf } from './debug';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';
export const useSitecoreDebug = () => {
const { sitecoreContext } = useSitecoreContext();
const isEditing = sitecoreContext.pageState === 'edit';
const isPreview = sitecoreContext.pageState === 'preview';
return {
debugInEditor: (debugFn, message, ...args) =>
debugIf(isEditing, debugFn, `[EDITOR] ${message}`, ...args),
debugInPreview: (debugFn, message, ...args) =>
debugIf(isPreview, debugFn, `[PREVIEW] ${message}`, ...args),
debugInNormal: (debugFn, message, ...args) =>
debugIf(!isEditing && !isPreview, debugFn, `[NORMAL] ${message}`, ...args)
};
};
Debug Package Scripts for Different Scenarios:
json
{
"scripts": {
"dev": "next dev",
"dev:debug-all": "DEBUG=sitecore-jss:*,myapp:* npm run dev",
"dev:debug-components": "DEBUG=myapp:components npm run dev",
"dev:debug-data": "DEBUG=sitecore-jss:layout,myapp:data npm run dev",
"dev:debug-performance": "DEBUG=myapp:performance npm run dev",
"dev:debug-editor": "DEBUG=sitecore-jss:editing,myapp:components npm run dev"
}
}
Combining with JSS logging
bash
# The power combination - see both framework and app behavior
DEBUG=sitecore-jss:layout,sitecore-jss:http,myapp:components,myapp:data npm run dev
This gives you complete visibility into both what Sitecore JSS is doing internally and how your custom application logic is behaving.
4. Programmatic Debugging: Strategic Breakpoints
Use debugger; statements for targeted debugging in complex scenarios where you need to pause execution programmatically.
Step-by-Step Implementation
Step 1: Understanding the debugger keyword
The debugger; statement acts like a breakpoint written directly in your code. When the browser’s developer tools are open, JavaScript execution will pause at this line.
Step 2: Basic Usage
javascript
// In any JavaScript file
function processData(data) {
console.log('Processing data...');
debugger; // Execution pauses here when dev tools are open
const result = data.map(item => item.value);
return result;
}
Step 3: Conditional Debugging in XM Cloud Scenarios
Debug Only in Experience Editor:
javascript
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';
export default function MyComponent({ fields }) {
const { sitecoreContext } = useSitecoreContext();
// Only pause when in Experience Editor
if (sitecoreContext.pageState === 'edit') {
debugger; // Inspect component state in editor mode
}
return <div>{/* component */}</div>;
}
Debug Specific Content Items:
javascript
export default function ProductDetail({ params }) {
const { sitecoreContext } = useSitecoreContext();
const route = sitecoreContext?.route;
// Debug only problematic items
if (route?.itemId === '{PROBLEMATIC-ITEM-ID}') {
debugger; // Pause for this specific item
}
return <div>{/* component */}</div>;
}
Debug API Routes Conditionally:
javascript
// pages/api/sitecore/layout.js
export default async function handler(req, res) {
const { path } = req.query;
// Debug specific paths
if (path && path.includes('/products/problem-product')) {
debugger; // Pause for problematic routes
}
// Debug based on request method
if (req.method === 'POST') {
debugger; // Pause for POST requests
}
const layoutData = await layoutService.fetchLayoutData(path);
res.json(layoutData);
}
Advanced Debugging Patterns
Step 4: Debug in Loops and Iterations
javascript
// Debug specific iterations
export default function ProductGrid({ products }) {
return (
<div>
{products.map((product, index) => {
// Debug the 5th product
if (index === 4) {
debugger; // Inspect this specific product
}
// Debug products with missing data
if (!product.name || !product.price) {
debugger; // Pause for incomplete data
}
return <ProductCard key={product.id} product={product} />;
})}
</div>
);
}
Debug Based on User Behavior:
javascript
export default function SearchResults({ query }) {
const handleSearch = (searchTerm) => {
// Debug specific search terms
if (searchTerm.toLowerCase().includes('debug')) {
debugger; // Pause for debug searches
}
// Debug empty searches
if (!searchTerm.trim()) {
debugger; // Investigate empty search behavior
}
performSearch(searchTerm);
};
return <SearchComponent onSearch={handleSearch} />;
}
Step 5: Environment-Aware Debugging
javascript
// Only debug in development
const isDevelopment = process.env.NODE_ENV === 'development';
export default function MyComponent({ data }) {
if (isDevelopment && !data) {
debugger; // Only pause in development for missing data
}
return <div>{/* component */}</div>;
}
Debug with Feature Flags:
javascript
// Use environment variables for debug control
const DEBUG_COMPONENTS = process.env.DEBUG_COMPONENTS === 'true';
export default function MyComponent({ fields }) {
if (DEBUG_COMPONENTS) {
debugger; // Controlled by environment variable
}
return <div>{/* component */}</div>;
}
Step 6: Using debugger; with Dev Tools
How to Use:
- Open your browser’s Developer Tools (F12)
- Navigate to your page or trigger the code path
- When JavaScript hits a
debugger;statement, execution pauses - Use the debugging controls:
- Continue (F8) – Resume execution
- Step Over (F10) – Execute next line
- Step Into (F11) – Step into function calls
- Step Out (Shift+F11) – Step out of current function
Inspect Variables:
- Hover over variables to see their values
- Use the Console tab to evaluate expressions
- Check the Scope panel to see all available variables
Debugging XM Cloud GraphQL Responses
javascript
// In your GraphQL client or data fetching logic
const fetchLayoutData = async (path) => {
try {
const response = await graphQLClient.request(layoutQuery, { path });
// Debug unexpected response structure
if (!response.layout || !response.layout.item) {
debugger; // Pause to inspect malformed response
}
return response;
} catch (error) {
debugger; // Pause on GraphQL errors
throw error;
}
};
Best Practices
- Remove before production – Always remove
debugger;statements before deploying - Use conditionally – Don’t pause on every execution, only when specific conditions are met
- Combine with console.log – Use both logging and pausing for complete picture
- Document your debugging – Comment why you’re using debugger statements
javascript
// Good practice: Comment your debugging intent
export default function MyComponent({ data }) {
// TODO: Remove after fixing data loading issue
if (!data.requiredField) {
debugger; // Investigating missing requiredField
}
return <div>{/* component */}</div>;
}
Conclusion
These four essential debugging techniques provide the foundation for effective XM Cloud development:
- VS Code Integrated Debugging gives you the familiar Visual Studio-like debugging experience with breakpoints and variable inspection
- Sitecore JSS Debug Logging provides deep visibility into the framework’s internal operations
- Custom Debug Logging allows you to track your application logic with organized, conditional logging
- Programmatic Debugging offers strategic breakpoints for complex scenarios
With these techniques in your toolkit, you can debug server-side rendering, API routes, component logic, and Sitecore integration issues effectively. The key is knowing when to use each approach and how to combine them for maximum debugging power.
Coming Up in Part 2: We’ll explore advanced debugging strategies including console debugging patterns, browser DevTools mastery, and XM Cloud-specific debugging techniques that will take your debugging skills to the next level.
Happy debugging!


One response to “Part 1: Essential Debugging Techniques for Sitecore XM Cloud Next.js Apps”
[…] Sitecore Saga: Debugging XM Cloud Apps […]
LikeLike