JavaScript SSR

JavaScript Server-Side Rendering: Next.js, Nuxt.js, and Universal Applications

Master server-side rendering with JavaScript. Learn Next.js, Nuxt.js, hydration, static generation, and building universal applications.

By JavaScript Document Team
ssrnextjsnuxtjsuniversal-appshydrationstatic-generation

Server-Side Rendering (SSR) with JavaScript enables building fast, SEO-friendly applications by rendering content on the server. Using frameworks like Next.js and Nuxt.js, developers can create universal applications that render on both server and client. This comprehensive guide covers SSR fundamentals, hydration strategies, static generation, and performance optimization.

SSR Fundamentals and Implementation

Universal JavaScript Engine

// Universal JavaScript Renderer
class UniversalRenderer {
  constructor(options = {}) {
    this.isServer = typeof window === 'undefined';
    this.isClient = !this.isServer;
    this.config = {
      hydration: options.hydration !== false,
      streaming: options.streaming || false,
      caching: options.caching || true,
      preloadData: options.preloadData || true,
    };

    this.componentRegistry = new Map();
    this.routeRegistry = new Map();
    this.middleware = [];
    this.cache = new Map();
    this.store = null;

    if (this.isServer) {
      this.setupServerRendering();
    } else {
      this.setupClientHydration();
    }
  }

  // Register component for universal rendering
  registerComponent(name, component, options = {}) {
    const componentDef = {
      name,
      component,
      options: {
        ssr: options.ssr !== false,
        preload: options.preload || null,
        getServerSideProps: options.getServerSideProps || null,
        getStaticProps: options.getStaticProps || null,
      },
    };

    this.componentRegistry.set(name, componentDef);
  }

  // Register route
  registerRoute(path, componentName, options = {}) {
    const route = {
      path: this.normalizePath(path),
      componentName,
      options: {
        exact: options.exact !== false,
        middleware: options.middleware || [],
        preload: options.preload || [],
        cache: options.cache || false,
        cacheTTL: options.cacheTTL || 300000,
      },
    };

    this.routeRegistry.set(path, route);
  }

  // Server-side rendering
  async renderToString(url, context = {}) {
    if (!this.isServer) {
      throw new Error('renderToString can only be called on server');
    }

    const route = this.findRoute(url);
    if (!route) {
      throw new Error(`Route not found: ${url}`);
    }

    const componentDef = this.componentRegistry.get(route.componentName);
    if (!componentDef) {
      throw new Error(`Component not found: ${route.componentName}`);
    }

    try {
      // Check cache
      if (route.options.cache) {
        const cached = this.getFromCache(url);
        if (cached) {
          return cached;
        }
      }

      // Execute middleware
      for (const middleware of route.options.middleware) {
        await middleware(context);
      }

      // Preload data
      const props = await this.preloadComponentData(componentDef, context);

      // Render component
      const html = await this.renderComponent(
        componentDef.component,
        props,
        context
      );

      // Generate full HTML
      const fullHTML = this.wrapHTML(html, props, context);

      // Cache result
      if (route.options.cache) {
        this.setCache(url, fullHTML, route.options.cacheTTL);
      }

      return fullHTML;
    } catch (error) {
      console.error('SSR Error:', error);
      throw error;
    }
  }

  // Streaming SSR
  async renderToStream(url, context = {}) {
    if (!this.isServer) {
      throw new Error('renderToStream can only be called on server');
    }

    const { Readable } = require('stream');

    return new Readable({
      async read() {
        try {
          const html = await this.renderToString(url, context);
          this.push(html);
          this.push(null); // End stream
        } catch (error) {
          this.destroy(error);
        }
      },
    });
  }

  // Client-side hydration
  async hydrate(containerId = 'app') {
    if (!this.isClient) {
      throw new Error('hydrate can only be called on client');
    }

    const container = document.getElementById(containerId);
    if (!container) {
      throw new Error(`Container element not found: ${containerId}`);
    }

    try {
      // Get hydration data
      const hydrationData = this.getHydrationData();

      // Initialize client state
      if (hydrationData.store) {
        this.store = this.createStore(hydrationData.store);
      }

      // Hydrate components
      await this.hydrateComponents(container, hydrationData);

      // Setup client-side routing
      this.setupClientRouting();

      console.log('Hydration completed');
    } catch (error) {
      console.error('Hydration error:', error);
      throw error;
    }
  }

  // Preload component data
  async preloadComponentData(componentDef, context) {
    let props = {};

    // Get server-side props
    if (componentDef.options.getServerSideProps) {
      const serverProps =
        await componentDef.options.getServerSideProps(context);
      props = { ...props, ...serverProps };
    }

    // Get static props
    if (componentDef.options.getStaticProps) {
      const staticProps = await componentDef.options.getStaticProps(context);
      props = { ...props, ...staticProps };
    }

    // Execute preload functions
    if (componentDef.options.preload) {
      const preloadPromises = componentDef.options.preload.map((fn) =>
        fn(context)
      );
      const preloadResults = await Promise.all(preloadPromises);
      props.preloadData = preloadResults;
    }

    return props;
  }

  // Render component to HTML
  async renderComponent(component, props, context) {
    // This is a simplified example - in practice, you'd use a library like React/Vue
    if (typeof component === 'function') {
      return component(props, context);
    }

    if (component.render) {
      return component.render(props, context);
    }

    throw new Error('Component must be a function or have a render method');
  }

  // Wrap HTML with document structure
  wrapHTML(html, props, context) {
    const serializedProps = JSON.stringify(props).replace(/</g, '\\u003c');
    const hydrationScript = this.config.hydration
      ? this.getHydrationScript(props)
      : '';

    return `
      <!DOCTYPE html>
      <html lang="${context.lang || 'en'}">
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>${context.title || 'Universal App'}</title>
          ${context.meta || ''}
          ${context.styles || ''}
        </head>
        <body>
          <div id="app">${html}</div>
          <script id="__UNIVERSAL_DATA__" type="application/json">
            ${serializedProps}
          </script>
          ${hydrationScript}
          ${context.scripts || ''}
        </body>
      </html>
    `;
  }

  // Generate hydration script
  getHydrationScript(props) {
    return `
      <script>
        window.__UNIVERSAL_RENDERER__ = {
          props: ${JSON.stringify(props)},
          hydrated: false
        };
        
        // Auto-hydrate when DOM is ready
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', function() {
            if (window.universalRenderer) {
              window.universalRenderer.hydrate();
            }
          });
        } else {
          if (window.universalRenderer) {
            window.universalRenderer.hydrate();
          }
        }
      </script>
    `;
  }

  // Get hydration data on client
  getHydrationData() {
    const dataElement = document.getElementById('__UNIVERSAL_DATA__');
    if (!dataElement) {
      return {};
    }

    try {
      return JSON.parse(dataElement.textContent);
    } catch (error) {
      console.error('Failed to parse hydration data:', error);
      return {};
    }
  }

  // Hydrate components on client
  async hydrateComponents(container, hydrationData) {
    const componentElements = container.querySelectorAll('[data-component]');

    for (const element of componentElements) {
      const componentName = element.getAttribute('data-component');
      const componentDef = this.componentRegistry.get(componentName);

      if (componentDef) {
        try {
          await this.hydrateComponent(element, componentDef, hydrationData);
        } catch (error) {
          console.error(`Failed to hydrate component ${componentName}:`, error);
        }
      }
    }
  }

  async hydrateComponent(element, componentDef, hydrationData) {
    // Component-specific hydration logic
    if (componentDef.component.hydrate) {
      await componentDef.component.hydrate(element, hydrationData);
    } else {
      // Default hydration - attach event listeners, etc.
      this.attachEventListeners(element, componentDef);
    }
  }

  attachEventListeners(element, componentDef) {
    // Find and attach event listeners
    const interactiveElements = element.querySelectorAll('[data-event]');

    interactiveElements.forEach((el) => {
      const eventType = el.getAttribute('data-event');
      const handlerName = el.getAttribute('data-handler');

      if (componentDef.component[handlerName]) {
        el.addEventListener(eventType, componentDef.component[handlerName]);
      }
    });
  }

  // Setup server rendering
  setupServerRendering() {
    // Server-specific setup
    this.renderingMode = 'server';
  }

  // Setup client hydration
  setupClientHydration() {
    // Client-specific setup
    this.renderingMode = 'client';

    // Make renderer globally available
    if (typeof window !== 'undefined') {
      window.universalRenderer = this;
    }
  }

  // Client-side routing
  setupClientRouting() {
    if (!this.isClient) return;

    // Simple history API routing
    window.addEventListener('popstate', (event) => {
      this.handleRouteChange(window.location.pathname);
    });

    // Intercept link clicks
    document.addEventListener('click', (event) => {
      const link = event.target.closest('a[href]');
      if (link && link.origin === window.location.origin) {
        event.preventDefault();
        this.navigateTo(link.pathname);
      }
    });
  }

  async navigateTo(path) {
    if (!this.isClient) return;

    try {
      // Update URL
      window.history.pushState({}, '', path);

      // Render new route
      await this.handleRouteChange(path);
    } catch (error) {
      console.error('Navigation error:', error);
    }
  }

  async handleRouteChange(path) {
    const route = this.findRoute(path);
    if (!route) {
      console.error(`Route not found: ${path}`);
      return;
    }

    const componentDef = this.componentRegistry.get(route.componentName);
    if (!componentDef) {
      console.error(`Component not found: ${route.componentName}`);
      return;
    }

    // Client-side rendering
    const context = { url: path, isClient: true };
    const props = await this.preloadComponentData(componentDef, context);
    const html = await this.renderComponent(
      componentDef.component,
      props,
      context
    );

    // Update DOM
    const container = document.getElementById('app');
    if (container) {
      container.innerHTML = html;
      await this.hydrateComponents(container, props);
    }
  }

  // Utility methods
  findRoute(url) {
    const pathname = new URL(url, 'http://localhost').pathname;

    for (const [path, route] of this.routeRegistry) {
      if (this.matchRoute(route.path, pathname)) {
        return route;
      }
    }

    return null;
  }

  matchRoute(routePath, pathname) {
    if (routePath === pathname) return true;

    // Simple pattern matching
    const routeParts = routePath.split('/');
    const pathParts = pathname.split('/');

    if (routeParts.length !== pathParts.length) return false;

    return routeParts.every((part, index) => {
      return part.startsWith(':') || part === pathParts[index];
    });
  }

  normalizePath(path) {
    return path.startsWith('/') ? path : `/${path}`;
  }

  // Cache operations
  getFromCache(key) {
    const cached = this.cache.get(key);
    if (cached && cached.expires > Date.now()) {
      return cached.data;
    }
    this.cache.delete(key);
    return null;
  }

  setCache(key, data, ttl) {
    this.cache.set(key, {
      data,
      expires: Date.now() + ttl,
    });
  }
}

Next.js Implementation Patterns

Advanced Next.js Architecture

// Next.js Enhanced Application Structure
class NextJSEnhancer {
  constructor() {
    this.plugins = new Map();
    this.middleware = [];
    this.dataFetchers = new Map();
    this.cacheStrategies = new Map();
  }

  // Enhanced getServerSideProps wrapper
  withServerSideProps(fetcher, options = {}) {
    return async (context) => {
      const startTime = Date.now();

      try {
        // Apply middleware
        for (const middleware of this.middleware) {
          await middleware(context);
        }

        // Check cache
        if (options.cache) {
          const cached = await this.getFromCache(
            context.resolvedUrl,
            options.cache
          );
          if (cached) {
            return {
              props: cached,
              revalidate: options.revalidate,
            };
          }
        }

        // Execute data fetcher
        const props = await fetcher(context);

        // Cache result
        if (options.cache) {
          await this.setCache(context.resolvedUrl, props, options.cache);
        }

        // Add performance metrics
        props._performance = {
          serverSideTime: Date.now() - startTime,
          timestamp: new Date().toISOString(),
        };

        return {
          props,
          revalidate: options.revalidate,
        };
      } catch (error) {
        console.error('SSR Error:', error);

        if (options.fallback) {
          return {
            props: options.fallback,
            revalidate: 1, // Quick retry
          };
        }

        throw error;
      }
    };
  }

  // Enhanced getStaticProps wrapper
  withStaticProps(fetcher, options = {}) {
    return async (context) => {
      try {
        const props = await fetcher(context);

        return {
          props,
          revalidate: options.revalidate || 3600, // 1 hour default
          notFound: options.notFound || false,
        };
      } catch (error) {
        console.error('Static props error:', error);

        if (options.fallback) {
          return {
            props: options.fallback,
            revalidate: 60, // Quick retry for errors
          };
        }

        return {
          notFound: true,
        };
      }
    };
  }

  // Enhanced getStaticPaths wrapper
  withStaticPaths(fetcher, options = {}) {
    return async () => {
      try {
        const paths = await fetcher();

        return {
          paths,
          fallback: options.fallback || 'blocking',
        };
      } catch (error) {
        console.error('Static paths error:', error);

        return {
          paths: [],
          fallback: 'blocking',
        };
      }
    };
  }

  // Progressive Web App enhancements
  createPWAConfig() {
    return {
      pwa: {
        dest: 'public',
        register: true,
        skipWaiting: true,
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'google-fonts',
              expiration: {
                maxEntries: 4,
                maxAgeSeconds: 365 * 24 * 60 * 60, // 365 days
              },
            },
          },
          {
            urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'google-fonts-static',
              expiration: {
                maxEntries: 4,
                maxAgeSeconds: 365 * 24 * 60 * 60, // 365 days
              },
            },
          },
          {
            urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
            handler: 'StaleWhileRevalidate',
            options: {
              cacheName: 'static-image-assets',
              expiration: {
                maxEntries: 64,
                maxAgeSeconds: 24 * 60 * 60, // 24 hours
              },
            },
          },
          {
            urlPattern: /\.(?:js|css)$/i,
            handler: 'StaleWhileRevalidate',
            options: {
              cacheName: 'static-js-css-assets',
              expiration: {
                maxEntries: 32,
                maxAgeSeconds: 24 * 60 * 60, // 24 hours
              },
            },
          },
          {
            urlPattern: /^\/api\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              networkTimeoutSeconds: 10,
              expiration: {
                maxEntries: 16,
                maxAgeSeconds: 24 * 60 * 60, // 24 hours
              },
              cacheableResponse: {
                statuses: [0, 200],
              },
            },
          },
        ],
      },
    };
  }

  // Image optimization utilities
  createImageOptimization() {
    return {
      // Custom loader for images
      imageLoader: ({ src, width, quality }) => {
        const params = new URLSearchParams();
        params.set('url', src);
        params.set('w', width.toString());
        if (quality) {
          params.set('q', quality.toString());
        }
        return `/api/image?${params}`;
      },

      // Image component with advanced features
      OptimizedImage: ({ src, alt, priority = false, ...props }) => {
        const [isLoaded, setIsLoaded] = useState(false);
        const [error, setError] = useState(false);

        return (
          <div className="relative">
            {!isLoaded && !error && (
              <div className="absolute inset-0 animate-pulse bg-gray-200" />
            )}
            <Image
              src={src}
              alt={alt}
              priority={priority}
              onLoad={() => setIsLoaded(true)}
              onError={() => setError(true)}
              {...props}
            />
            {error && (
              <div className="absolute inset-0 flex items-center justify-center bg-gray-100">
                <span className="text-gray-500">Failed to load image</span>
              </div>
            )}
          </div>
        );
      },
    };
  }

  // SEO optimization utilities
  createSEOUtils() {
    return {
      generateMetaTags: (data) => {
        const meta = [];

        if (data.title) {
          meta.push(<title key="title">{data.title}</title>);
          meta.push(
            <meta key="og:title" property="og:title" content={data.title} />
          );
          meta.push(
            <meta
              key="twitter:title"
              name="twitter:title"
              content={data.title}
            />
          );
        }

        if (data.description) {
          meta.push(
            <meta
              key="description"
              name="description"
              content={data.description}
            />
          );
          meta.push(
            <meta
              key="og:description"
              property="og:description"
              content={data.description}
            />
          );
          meta.push(
            <meta
              key="twitter:description"
              name="twitter:description"
              content={data.description}
            />
          );
        }

        if (data.image) {
          meta.push(
            <meta key="og:image" property="og:image" content={data.image} />
          );
          meta.push(
            <meta
              key="twitter:image"
              name="twitter:image"
              content={data.image}
            />
          );
        }

        if (data.url) {
          meta.push(<meta key="og:url" property="og:url" content={data.url} />);
        }

        // Additional SEO tags
        meta.push(<meta key="og:type" property="og:type" content="website" />);
        meta.push(
          <meta
            key="twitter:card"
            name="twitter:card"
            content="summary_large_image"
          />
        );

        return meta;
      },

      generateStructuredData: (type, data) => {
        const structuredData = {
          '@context': 'https://schema.org',
          '@type': type,
          ...data,
        };

        return (
          <script
            key="structured-data"
            type="application/ld+json"
            dangerouslySetInnerHTML={{
              __html: JSON.stringify(structuredData, null, 2),
            }}
          />
        );
      },
    };
  }

  // Performance monitoring
  createPerformanceMonitor() {
    return {
      // Web Vitals tracking
      trackWebVitals: (metric) => {
        console.log('Web Vitals:', metric);

        // Send to analytics
        if (typeof gtag !== 'undefined') {
          gtag('event', metric.name, {
            event_category: 'Web Vitals',
            value: Math.round(metric.value),
            event_label: metric.id,
            non_interaction: true,
          });
        }
      },

      // Performance observer
      setupPerformanceObserver: () => {
        if (typeof window !== 'undefined' && 'PerformanceObserver' in window) {
          const observer = new PerformanceObserver((list) => {
            for (const entry of list.getEntries()) {
              console.log('Performance entry:', entry);
            }
          });

          observer.observe({
            entryTypes: ['navigation', 'paint', 'largest-contentful-paint'],
          });
        }
      },

      // Bundle analysis
      analyzeBundleSize: async () => {
        if (process.env.ANALYZE) {
          const { BundleAnalyzerPlugin } = await import(
            'webpack-bundle-analyzer'
          );
          return new BundleAnalyzerPlugin({
            analyzerMode: 'static',
            openAnalyzer: false,
            reportFilename: 'bundle-report.html',
          });
        }
        return null;
      },
    };
  }

  // Cache strategies
  async getFromCache(key, strategy) {
    switch (strategy.type) {
      case 'redis':
        return this.getFromRedis(key, strategy);
      case 'memory':
        return this.getFromMemory(key, strategy);
      case 'cdn':
        return this.getFromCDN(key, strategy);
      default:
        return null;
    }
  }

  async setCache(key, data, strategy) {
    switch (strategy.type) {
      case 'redis':
        return this.setToRedis(key, data, strategy);
      case 'memory':
        return this.setToMemory(key, data, strategy);
      case 'cdn':
        return this.setToCDN(key, data, strategy);
    }
  }

  // Redis cache implementation
  async getFromRedis(key, strategy) {
    try {
      const redis = require('redis');
      const client = redis.createClient(strategy.options);
      const data = await client.get(key);
      await client.quit();

      return data ? JSON.parse(data) : null;
    } catch (error) {
      console.error('Redis cache error:', error);
      return null;
    }
  }

  async setToRedis(key, data, strategy) {
    try {
      const redis = require('redis');
      const client = redis.createClient(strategy.options);
      await client.setex(key, strategy.ttl || 3600, JSON.stringify(data));
      await client.quit();
    } catch (error) {
      console.error('Redis cache error:', error);
    }
  }
}

// Custom hooks for SSR
const useSSRSafeEffect = (effect, deps) => {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  useEffect(() => {
    if (isClient) {
      return effect();
    }
  }, [isClient, ...deps]);
};

const useHydrationSafe = (clientValue, serverValue = null) => {
  const [value, setValue] = useState(serverValue);

  useEffect(() => {
    setValue(clientValue);
  }, [clientValue]);

  return value;
};

// Example usage with Next.js
const ExamplePage = ({ data, performance }) => {
  const [hydrated, setHydrated] = useState(false);

  useEffect(() => {
    setHydrated(true);
  }, []);

  // Safe client-side operations
  const clientSideValue = useHydrationSafe(
    typeof window !== 'undefined' ? window.location.href : null,
    ''
  );

  return (
    <div>
      <h1>Universal Page</h1>
      <p>Data: {JSON.stringify(data)}</p>
      {hydrated && <p>Client URL: {clientSideValue}</p>}
      <small>Server render time: {performance?.serverSideTime}ms</small>
    </div>
  );
};

// Enhanced getServerSideProps
export const getServerSideProps = withServerSideProps(
  async (context) => {
    // Fetch data
    const data = await fetchData(context.params?.id);

    return {
      data,
      timestamp: new Date().toISOString(),
    };
  },
  {
    cache: {
      type: 'redis',
      ttl: 300,
      options: { host: 'localhost', port: 6379 },
    },
    revalidate: 60,
    fallback: { data: null, error: 'Failed to load data' },
  }
);

Static Site Generation and ISR

Incremental Static Regeneration System

// ISR (Incremental Static Regeneration) Manager
class ISRManager {
  constructor(options = {}) {
    this.config = {
      revalidateInterval: options.revalidateInterval || 60,
      maxStaleAge: options.maxStaleAge || 86400,
      cacheDirectory: options.cacheDirectory || '.next/cache',
      buildQueue: options.buildQueue || 'memory',
    };

    this.buildQueue = new Map();
    this.buildStatus = new Map();
    this.cacheHeaders = new Map();
  }

  // Generate static page with ISR
  async generateStaticPage(path, context, getStaticProps) {
    const cacheKey = this.getCacheKey(path, context);
    const cachedPage = await this.getCachedPage(cacheKey);

    // Check if revalidation is needed
    const shouldRevalidate = this.shouldRevalidate(cachedPage);

    if (cachedPage && !shouldRevalidate) {
      // Serve from cache
      return {
        html: cachedPage.html,
        props: cachedPage.props,
        headers: this.getCacheHeaders(cachedPage),
      };
    }

    if (cachedPage && shouldRevalidate) {
      // Serve stale while revalidating
      this.queueRevalidation(path, context, getStaticProps);

      return {
        html: cachedPage.html,
        props: cachedPage.props,
        headers: this.getStaleHeaders(cachedPage),
      };
    }

    // Generate new page
    return this.buildPage(path, context, getStaticProps);
  }

  // Build page from scratch
  async buildPage(path, context, getStaticProps) {
    const buildKey = `${path}:${JSON.stringify(context)}`;

    // Check if already building
    if (this.buildStatus.has(buildKey)) {
      return this.waitForBuild(buildKey);
    }

    // Mark as building
    this.buildStatus.set(buildKey, 'building');

    try {
      // Execute getStaticProps
      const staticPropsResult = await getStaticProps(context);

      if (staticPropsResult.notFound) {
        return { notFound: true };
      }

      if (staticPropsResult.redirect) {
        return { redirect: staticPropsResult.redirect };
      }

      const { props, revalidate } = staticPropsResult;

      // Render page to HTML
      const html = await this.renderPageToHTML(path, props, context);

      // Cache the result
      const cacheEntry = {
        html,
        props,
        revalidate: revalidate || this.config.revalidateInterval,
        generatedAt: Date.now(),
        path,
        context,
      };

      await this.cachePage(this.getCacheKey(path, context), cacheEntry);

      // Mark as completed
      this.buildStatus.set(buildKey, 'completed');

      return {
        html,
        props,
        headers: this.getFreshHeaders(cacheEntry),
      };
    } catch (error) {
      this.buildStatus.set(buildKey, 'failed');
      console.error('Page build failed:', error);
      throw error;
    } finally {
      // Clean up build status after delay
      setTimeout(() => {
        this.buildStatus.delete(buildKey);
      }, 60000);
    }
  }

  // Queue revalidation in background
  queueRevalidation(path, context, getStaticProps) {
    const queueKey = `${path}:${JSON.stringify(context)}`;

    if (this.buildQueue.has(queueKey)) {
      return; // Already queued
    }

    this.buildQueue.set(queueKey, {
      path,
      context,
      getStaticProps,
      queuedAt: Date.now(),
    });

    // Process queue
    setImmediate(() => {
      this.processRevalidationQueue();
    });
  }

  // Process revalidation queue
  async processRevalidationQueue() {
    const batch = Array.from(this.buildQueue.entries()).slice(0, 3); // Process 3 at a time

    await Promise.all(
      batch.map(async ([queueKey, item]) => {
        try {
          this.buildQueue.delete(queueKey);
          await this.buildPage(item.path, item.context, item.getStaticProps);
          console.log(`Revalidated: ${item.path}`);
        } catch (error) {
          console.error(`Revalidation failed for ${item.path}:`, error);
        }
      })
    );

    // Continue processing if queue has items
    if (this.buildQueue.size > 0) {
      setTimeout(() => {
        this.processRevalidationQueue();
      }, 1000);
    }
  }

  // Check if page should be revalidated
  shouldRevalidate(cachedPage) {
    if (!cachedPage) return true;

    const age = (Date.now() - cachedPage.generatedAt) / 1000;
    return age > cachedPage.revalidate;
  }

  // Render page to HTML
  async renderPageToHTML(path, props, context) {
    // This is simplified - in practice, you'd use Next.js rendering
    const PageComponent = await this.getPageComponent(path);

    if (!PageComponent) {
      throw new Error(`Page component not found for path: ${path}`);
    }

    // Render with React Server Components or similar
    return this.renderToString(PageComponent, props, context);
  }

  async renderToString(Component, props, context) {
    // Simplified rendering - use actual framework renderer
    try {
      if (typeof Component === 'function') {
        const element = Component(props);
        return this.stringifyElement(element);
      }

      throw new Error('Invalid component');
    } catch (error) {
      console.error('Render error:', error);
      return '<div>Render Error</div>';
    }
  }

  stringifyElement(element) {
    // Very simplified HTML generation
    if (typeof element === 'string') return element;
    if (typeof element === 'number') return element.toString();
    if (!element) return '';

    if (element.type && element.props) {
      const { type, props } = element;
      const children = props.children || '';

      return `<${type}>${this.stringifyElement(children)}</${type}>`;
    }

    return JSON.stringify(element);
  }

  // Cache management
  async getCachedPage(cacheKey) {
    try {
      const fs = require('fs').promises;
      const path = require('path');
      const cachePath = path.join(
        this.config.cacheDirectory,
        `${cacheKey}.json`
      );

      const data = await fs.readFile(cachePath, 'utf8');
      return JSON.parse(data);
    } catch (error) {
      return null; // Cache miss
    }
  }

  async cachePage(cacheKey, cacheEntry) {
    try {
      const fs = require('fs').promises;
      const path = require('path');

      // Ensure cache directory exists
      await fs.mkdir(this.config.cacheDirectory, { recursive: true });

      const cachePath = path.join(
        this.config.cacheDirectory,
        `${cacheKey}.json`
      );
      await fs.writeFile(cachePath, JSON.stringify(cacheEntry, null, 2));
    } catch (error) {
      console.error('Cache write error:', error);
    }
  }

  getCacheKey(path, context) {
    const key = `${path}:${JSON.stringify(context.params || {})}`;
    return key.replace(/[^a-zA-Z0-9]/g, '_');
  }

  // HTTP cache headers
  getCacheHeaders(cachedPage) {
    const age = Math.floor((Date.now() - cachedPage.generatedAt) / 1000);
    const maxAge = cachedPage.revalidate;

    return {
      'Cache-Control': `public, max-age=${maxAge}, s-maxage=${maxAge}`,
      Age: age.toString(),
      'X-Cache': 'HIT',
    };
  }

  getStaleHeaders(cachedPage) {
    const age = Math.floor((Date.now() - cachedPage.generatedAt) / 1000);
    const maxAge = cachedPage.revalidate;

    return {
      'Cache-Control': `public, max-age=${maxAge}, s-maxage=${maxAge}, stale-while-revalidate=86400`,
      Age: age.toString(),
      'X-Cache': 'STALE',
    };
  }

  getFreshHeaders(cachedPage) {
    return {
      'Cache-Control': `public, max-age=${cachedPage.revalidate}, s-maxage=${cachedPage.revalidate}`,
      Age: '0',
      'X-Cache': 'MISS',
    };
  }

  // Utility methods
  async getPageComponent(path) {
    // Dynamic import of page component
    try {
      const component = await import(`../pages${path}`);
      return component.default || component;
    } catch (error) {
      console.error(`Failed to load component for ${path}:`, error);
      return null;
    }
  }

  async waitForBuild(buildKey) {
    return new Promise((resolve, reject) => {
      const checkStatus = () => {
        const status = this.buildStatus.get(buildKey);

        if (status === 'completed') {
          resolve({ success: true });
        } else if (status === 'failed') {
          reject(new Error('Build failed'));
        } else {
          setTimeout(checkStatus, 100);
        }
      };

      checkStatus();
    });
  }

  // Monitoring and analytics
  getStatistics() {
    return {
      queueSize: this.buildQueue.size,
      buildingPages: Array.from(this.buildStatus.entries()).filter(
        ([_, status]) => status === 'building'
      ).length,
      cacheDirectory: this.config.cacheDirectory,
      revalidateInterval: this.config.revalidateInterval,
    };
  }
}

// Edge function for ISR
const edgeISRHandler = async (request, context) => {
  const url = new URL(request.url);
  const path = url.pathname;

  const isrManager = new ISRManager({
    revalidateInterval: 60,
    cacheDirectory: '/tmp/cache',
  });

  try {
    const result = await isrManager.generateStaticPage(
      path,
      { params: context.params },
      getStaticProps
    );

    if (result.notFound) {
      return new Response('Not Found', { status: 404 });
    }

    if (result.redirect) {
      return Response.redirect(
        result.redirect.destination,
        result.redirect.permanent ? 301 : 302
      );
    }

    return new Response(result.html, {
      headers: {
        'Content-Type': 'text/html',
        ...result.headers,
      },
    });
  } catch (error) {
    console.error('ISR handler error:', error);
    return new Response('Internal Server Error', { status: 500 });
  }
};

This comprehensive guide covers JavaScript server-side rendering from universal rendering engines to advanced Next.js patterns and incremental static regeneration. The examples provide production-ready implementations for building fast, SEO-friendly applications with optimal performance and user experience.