UNPKG

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
/** * 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