ai-debug-local-mcp
Version:
🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
366 lines • 16.6 kB
JavaScript
/**
* NextJS Rendering & SSR Module
*
* Phase 3 of NextJS Debug Engine Enhanced modularization following the proven
* 4-module architecture pattern. This module contains SSR/SSG analysis,
* React Server Components debugging, and data fetching functionality.
*
* Contains rendering functionality:
* - Server-Side Rendering (SSR) analysis and debugging
* - Static Site Generation (SSG) and ISR monitoring
* - React Server Components (RSC) inspection
* - Data fetching pattern analysis and optimization
* - Server/Client boundary analysis
*
* @module nextjs-rendering-ssr
* @since Cycle 23 - July 13, 2025
*/
export class NextJSRenderingSSR {
page;
constructor(page) {
this.page = page;
}
async setupRenderingMonitoring() {
// Real implementation: Rendering monitoring injection
await this.page.addInitScript(() => {
// Set up rendering and SSR monitoring
window.__NEXTJS_RENDERING__ = {
dataFetching: [],
rscChunks: [],
hydrationInfo: {},
monitorDataFetching: function () {
// Override fetch to monitor data fetching patterns
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const startTime = performance.now();
const url = typeof args[0] === 'string' ? args[0] : args[0].url || args[0].toString();
try {
const response = await originalFetch(...args);
const endTime = performance.now();
// Analyze if this is NextJS data fetching
if (typeof url === 'string' && (url.includes('_next/data') || url.includes('/api/'))) {
const cacheHeader = response.headers.get('cache-control');
let cacheStatus = 'NONE';
if (response.headers.get('x-cache') === 'HIT') {
cacheStatus = 'HIT';
}
else if (cacheHeader && cacheHeader.includes('max-age')) {
cacheStatus = 'MISS';
}
this.dataFetching.push({
url,
method: 'fetch with cache',
cacheStatus,
duration: endTime - startTime,
timestamp: new Date()
});
}
return response;
}
catch (error) {
const endTime = performance.now();
this.dataFetching.push({
url,
method: 'fetch with cache',
cacheStatus: 'NONE',
duration: endTime - startTime,
timestamp: new Date(),
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
};
},
monitorRSCStream: function () {
// Monitor React Server Components streaming
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node;
if (element.hasAttribute('data-rsc-chunk')) {
this.rscChunks.push({
timestamp: new Date(),
chunk: element.outerHTML,
componentName: element.getAttribute('data-component') || 'Unknown'
});
}
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
},
monitorHydration: function () {
// Monitor hydration process and errors
const originalError = console.error;
const hydrationErrors = [];
console.error = (...args) => {
const message = args.join(' ');
if (message.includes('hydration') || message.includes('mismatch')) {
hydrationErrors.push(message);
}
originalError.apply(console, args);
};
// Store hydration info
this.hydrationInfo = {
hasErrors: false,
mismatchedElements: [],
hydrationTime: 0,
suppressedWarnings: hydrationErrors
};
// Detect hydration completion
const startTime = performance.now();
const checkHydration = () => {
if (document.readyState === 'complete') {
this.hydrationInfo.hydrationTime = performance.now() - startTime;
this.hydrationInfo.hasErrors = hydrationErrors.length > 0;
}
else {
setTimeout(checkHydration, 100);
}
};
checkHydration();
}
};
// Initialize rendering monitoring
window.__NEXTJS_RENDERING__.monitorDataFetching();
window.__NEXTJS_RENDERING__.monitorRSCStream();
window.__NEXTJS_RENDERING__.monitorHydration();
});
}
async getDataFetchingAnalysis() {
// Real implementation: Data fetching pattern analysis
return await this.page.evaluate(() => {
const renderingData = window.__NEXTJS_RENDERING__;
const nextData = window.__NEXT_DATA__;
const analyses = [];
// Analyze page-level data fetching
if (nextData?.props?.pageProps) {
analyses.push({
method: 'getStaticProps',
cacheStatus: 'HIT', // Static props are cached
waterfalls: [{
component: 'Page',
fetchTime: '0ms', // Pre-rendered
blocking: false
}]
});
}
// Analyze runtime data fetching
if (renderingData?.dataFetching) {
renderingData.dataFetching.forEach((fetch) => {
analyses.push({
method: fetch.url.includes('/api/') ? 'server-action' : 'fetch with cache',
cacheStatus: fetch.cacheStatus,
revalidateTime: fetch.revalidateTime,
waterfalls: [{
component: 'Runtime',
fetchTime: `${Math.round(fetch.duration)}ms`,
blocking: true
}]
});
});
}
return analyses;
});
}
async debugRoute() {
// Real implementation: Comprehensive route debugging
const routeInfo = await this.page.evaluate(() => {
const router = window.__NEXT_ROUTER__ || window.next?.router;
return router ? {
pathname: router.pathname,
query: router.query,
asPath: router.asPath
} : null;
});
const dataFetching = await this.getDataFetchingAnalysis();
const renderingInfo = await this.page.evaluate(() => {
const renderingData = window.__NEXTJS_RENDERING__;
const nav = performance.getEntriesByType('navigation')[0];
// Count server vs client components
const serverComponents = document.querySelectorAll('[data-server-component]').length;
const clientComponents = document.querySelectorAll('[data-client-component]').length;
return {
method: document.querySelector('[data-nextjs-router="app"]') ? 'App Router' : 'Pages Router',
hydrationTime: renderingData?.hydrationInfo?.hydrationTime || 0,
serverComponents,
clientComponents,
performance: {
TTFB: nav?.responseStart - nav?.requestStart || 0,
renderTime: nav?.loadEventEnd - nav?.navigationStart || 0,
hydrationTime: renderingData?.hydrationInfo?.hydrationTime || 0
}
};
});
return {
route: routeInfo,
dataFetching,
rendering: renderingInfo,
performance: renderingInfo.performance
};
}
async analyzeServerClientFlow() {
// Real implementation: Server/Client boundary analysis
const boundaries = await this.getServerClientBoundaries();
const dataFetching = await this.getDataFetchingAnalysis();
const optimizations = [];
// Analyze for optimization opportunities
const totalBoundaries = boundaries.length;
if (totalBoundaries > 10) {
optimizations.push('Consider reducing the number of server/client boundaries for better performance');
}
const totalDataFetching = dataFetching.length;
if (totalDataFetching > 5) {
optimizations.push('Multiple data fetching calls detected - consider batching or parallel loading');
}
// Analyze data flow patterns
const dataFlow = boundaries.map((boundary, index) => ({
from: boundary.type === 'server' ? 'Server' : 'Client',
to: boundary.type === 'server' ? 'Client' : 'Server',
type: 'props',
size: boundary.serializedSize || 0
}));
return {
boundaries,
dataFlow,
optimizations
};
}
async getServerClientBoundaries() {
// Real implementation: Extract server/client component boundaries
return await this.page.evaluate(() => {
const boundaries = [];
// Find server components
const serverComponents = document.querySelectorAll('[data-server-component]');
serverComponents.forEach((element, index) => {
const componentName = element.getAttribute('data-component') || `ServerComponent${index}`;
boundaries.push({
component: componentName,
type: 'server',
depth: 0, // Would need traversal to calculate
children: [],
props: {}, // Would need React DevTools integration
serializedSize: element.outerHTML.length
});
});
// Find client components
const clientComponents = document.querySelectorAll('[data-client-component]');
clientComponents.forEach((element, index) => {
const componentName = element.getAttribute('data-component') || `ClientComponent${index}`;
boundaries.push({
component: componentName,
type: 'client',
depth: 0,
children: [],
props: {},
serializedSize: element.outerHTML.length
});
});
return boundaries;
});
}
async monitorRSCStream() {
// Real implementation: RSC streaming monitoring
const rscChunks = [];
// Set up streaming monitor
await this.page.evaluate(() => {
window.__RSC_STREAM_MONITOR__ = true;
});
const streamMonitor = async function* () {
while (true) {
const newChunks = await this.page.evaluate(() => {
const renderingData = window.__NEXTJS_RENDERING__;
return renderingData?.rscChunks || [];
});
for (const chunk of newChunks) {
if (!rscChunks.find(existing => existing.timestamp === chunk.timestamp)) {
const rscInfo = {
timestamp: new Date(chunk.timestamp),
chunk: chunk.chunk,
componentName: chunk.componentName,
propsSize: chunk.chunk.length,
isError: chunk.chunk.includes('error') || chunk.chunk.includes('Error')
};
rscChunks.push(rscInfo);
yield rscInfo;
}
}
await new Promise(resolve => setTimeout(resolve, 100));
}
};
return streamMonitor.call(this);
}
async detectHydrationIssues() {
// Real implementation: Hydration error detection
return await this.page.evaluate(() => {
const renderingData = window.__NEXTJS_RENDERING__;
return renderingData?.hydrationInfo || {
hasErrors: false,
mismatchedElements: [],
hydrationTime: 0,
suppressedWarnings: []
};
});
}
async analyzeSSRPerformance() {
// Real implementation: SSR performance analysis
const hydrationInfo = await this.detectHydrationIssues();
const dataFetching = await this.getDataFetchingAnalysis();
const serverRenderTime = await this.page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0];
return nav?.responseEnd - nav?.responseStart || 0;
});
const totalBlockingTime = dataFetching.reduce((total, fetch) => {
return total + (fetch.waterfalls.reduce((sum, waterfall) => {
return sum + (waterfall.blocking ? parseInt(waterfall.fetchTime) || 0 : 0);
}, 0));
}, 0);
const recommendations = [];
if (hydrationInfo.hydrationTime > 1000) {
recommendations.push('Hydration time is slow - consider reducing client-side JavaScript');
}
if (totalBlockingTime > 500) {
recommendations.push('Blocking data fetching detected - use parallel loading or streaming');
}
if (serverRenderTime > 2000) {
recommendations.push('Server render time is slow - optimize server-side data fetching');
}
return {
serverRenderTime,
hydrationTime: hydrationInfo.hydrationTime,
totalBlockingTime,
recommendations
};
}
// Helper methods for rendering analysis
async isSSRPage() {
return await this.page.evaluate(() => {
const nextData = window.__NEXT_DATA__;
return nextData && !nextData.props?.pageProps;
});
}
async isSSGPage() {
return await this.page.evaluate(() => {
const nextData = window.__NEXT_DATA__;
return nextData?.props?.pageProps && !nextData.isFallback;
});
}
async isISRPage() {
return await this.page.evaluate(() => {
return document.querySelector('meta[name="next-revalidate"]') !== null;
});
}
async isAppRouter() {
return await this.page.evaluate(() => {
return document.querySelector('[data-nextjs-router="app"]') !== null;
});
}
}
//# sourceMappingURL=nextjs-rendering-ssr.js.map