Modern Rendering Strategies Implementation

1. Next.js 14 App Router SSR SSG NEW

Feature Syntax Description Use Case
App Router app/ directory structure File-system based routing with layouts and nested routes Modern Next.js architecture
Server Components async function Page() {} Components that render on server by default Zero JS to client, SEO
generateStaticParams export async function generateStaticParams() Static generation of dynamic routes at build time SSG for dynamic routes
Dynamic Rendering export const dynamic = 'force-dynamic' SSR on every request, no caching Personalized content
Revalidation export const revalidate = 60 Incremental Static Regeneration (ISR) interval Periodic content updates
Loading UI loading.tsx Instant loading state while streaming Progressive rendering
Error Boundaries error.tsx Route-level error handling Graceful error recovery

Example: Next.js 14 App Router with SSR, SSG, and ISR

// app/layout.tsx - Root layout with metadata
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'My App',
  description: 'Next.js 14 App Router Example',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <nav>Navigation</nav>
        {children}
      </body>
    </html>
  );
}

// app/page.tsx - Static page (SSG by default)
export default async function HomePage() {
  const data = await fetch('https://api.example.com/posts', {
    cache: 'force-cache', // SSG: cached at build time
  }).then(res => res.json());

  return (
    <div>
      <h1>Home Page (Static)</h1>
      {data.map(post => <div key={post.id}>{post.title}</div>)}
    </div>
  );
}

// app/posts/[id]/page.tsx - Dynamic route with SSG
interface PageProps {
  params: { id: string };
}

// Generate static paths at build time
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts').then(res => res.json());
  
  return posts.map((post: any) => ({
    id: post.id.toString(),
  }));
}

// Revalidate every 60 seconds (ISR)
export const revalidate = 60;

export default async function PostPage({ params }: PageProps) {
  const post = await fetch(`https://api.example.com/posts/${params.id}`, {
    next: { revalidate: 60 }, // Per-fetch revalidation
  }).then(res => res.json());

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// app/dashboard/page.tsx - Dynamic SSR (no cache)
export const dynamic = 'force-dynamic';

export default async function DashboardPage() {
  const user = await getCurrentUser();
  const stats = await fetch(`https://api.example.com/stats/${user.id}`, {
    cache: 'no-store', // SSR: fetch on every request
  }).then(res => res.json());

  return (
    <div>
      <h1>Dashboard (Dynamic SSR)</h1>
      <p>Views: {stats.views}</p>
    </div>
  );
}

// app/products/loading.tsx - Loading state
export default function Loading() {
  return <div>Loading products...</div>;
}

// app/products/error.tsx - Error boundary
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// Client Component with 'use client'
'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

2. Vite React SPA Client Rendering

Feature Configuration Description Benefit
HMR Hot Module Replacement Instant updates without full page reload Fast development
ESBuild transform: { jsx: 'react' } Lightning-fast JavaScript/TypeScript compilation 10-100x faster than webpack
Code Splitting import() Dynamic imports for lazy loading Smaller initial bundle
Tree Shaking build.rollupOptions Dead code elimination in production Optimized bundle size
CSS Modules .module.css Scoped CSS with automatic class names No style conflicts
Environment Variables import.meta.env.VITE_* Access env vars prefixed with VITE_ Configuration management

Example: Vite React SPA with optimized configuration

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    react({
      // Fast Refresh for HMR
      fastRefresh: true,
    }),
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
  build: {
    // Target modern browsers
    target: 'esnext',
    // Manual chunk splitting
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          router: ['react-router-dom'],
          ui: ['@mui/material'],
        },
      },
    },
    // Source maps for production debugging
    sourcemap: true,
    // Chunk size warning limit
    chunkSizeWarningLimit: 1000,
  },
  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
  resolve: {
    alias: {
      '@': '/src',
      '@components': '/src/components',
      '@utils': '/src/utils',
    },
  },
});

// src/main.tsx - Entry point
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// src/App.tsx - Lazy loading routes
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

export default App;

// .env - Environment variables
VITE_API_URL=https://api.example.com
VITE_API_KEY=abc123

// Using environment variables
const apiUrl = import.meta.env.VITE_API_URL;
const apiKey = import.meta.env.VITE_API_KEY;

// CSS Modules - Button.module.css
.button {
  padding: 10px 20px;
  background: blue;
}

// Button.tsx
import styles from './Button.module.css';

function Button() {
  return <button className={styles.button}>Click Me</button>;
}

3. Nuxt 3 Universal Rendering

Feature Syntax Description Use Case
Universal Rendering SSR + Hydration Server renders HTML, client hydrates with interactivity Best of both worlds
Auto Imports No import statements needed Components, composables auto-imported Less boilerplate
useFetch const { data } = await useFetch('/api') Universal data fetching with SSR support Isomorphic data fetching
useAsyncData useAsyncData('key', () => $fetch()) Manual async data fetching with caching Complex data needs
Server Routes server/api/*.ts API routes built into Nuxt Full-stack framework
Middleware middleware/*.ts Route guards for auth, validation Navigation control

Example: Nuxt 3 universal rendering with data fetching

// nuxt.config.ts
export default defineNuxtConfig({
  // Rendering mode
  ssr: true,
  
  // Hybrid rendering per route
  routeRules: {
    '/': { prerender: true }, // SSG
    '/admin/**': { ssr: false }, // SPA
    '/api/**': { cors: true },
    '/blog/**': { swr: 3600 }, // ISR with 1h cache
  },

  // App configuration
  app: {
    head: {
      title: 'Nuxt 3 App',
      meta: [
        { name: 'description', content: 'Universal rendering example' }
      ],
    },
  },

  // Modules
  modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],

  // Auto imports
  imports: {
    dirs: ['composables', 'utils'],
  },
});

// pages/index.vue - SSR page with data fetching
<template>
  <div>
    <h1>Posts</h1>
    <div v-for="post in posts" :key="post.id">
      <h2>{{ post.title }}</h2>
      <p>{{ post.content }}</p>
    </div>
  </div>
</template>

<script setup>
// Auto-imported useFetch
const { data: posts } = await useFetch('/api/posts');

// SEO metadata
useHead({
  title: 'Blog Posts',
  meta: [
    { name: 'description', content: 'Latest blog posts' }
  ],
});
</script>

// pages/post/[id].vue - Dynamic route with SSR
<template>
  <article>
    <h1>{{ post?.title }}</h1>
    <p>{{ post?.content }}</p>
  </article>
</template>

<script setup>
const route = useRoute();

const { data: post } = await useAsyncData(
  `post-${route.params.id}`,
  () => $fetch(`/api/posts/${route.params.id}`)
);
</script>

// server/api/posts.ts - API route
export default defineEventHandler(async (event) => {
  const posts = await fetchPostsFromDB();
  return posts;
});

// server/api/posts/[id].ts - Dynamic API route
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id');
  const post = await fetchPostById(id);
  
  if (!post) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Post not found',
    });
  }
  
  return post;
});

// composables/useAuth.ts - Auto-imported composable
export const useAuth = () => {
  const user = useState('user', () => null);

  const login = async (credentials) => {
    const data = await $fetch('/api/auth/login', {
      method: 'POST',
      body: credentials,
    });
    user.value = data.user;
  };

  const logout = () => {
    user.value = null;
  };

  return { user, login, logout };
};

// middleware/auth.ts - Route middleware
export default defineNuxtRouteMiddleware((to, from) => {
  const { user } = useAuth();

  if (!user.value && to.path !== '/login') {
    return navigateTo('/login');
  }
});

// pages/dashboard.vue - Protected route
<template>
  <div>Dashboard for {{ user?.name }}</div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth',
});

const { user } = useAuth();
</script>

4. React 18 Concurrent Features NEW

Feature API Description Use Case
Concurrent Rendering createRoot() Interruptible rendering for better UX Responsive UI
useTransition const [isPending, startTransition] = useTransition() Marks updates as non-urgent, allows interruption Smooth UI transitions
useDeferredValue const deferredValue = useDeferredValue(value) Defers value updates to prioritize urgent renders Debounced rendering
Suspense <Suspense fallback={...}> Declarative loading states for async components Code splitting, data fetching
startTransition startTransition(() => setState()) Marks state updates as low-priority Heavy computations
useId const id = useId() Generates unique IDs for SSR compatibility Accessible forms

Example: React 18 concurrent features in action

// main.tsx - Concurrent mode with createRoot
import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root')!;
const root = createRoot(container);
root.render(<App />);

// useTransition for non-blocking updates
import { useState, useTransition } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (value: string) => {
    setQuery(value); // Urgent: update input immediately

    // Non-urgent: expensive search operation
    startTransition(() => {
      const filtered = expensiveSearch(value);
      setResults(filtered);
    });
  };

  return (
    <div>
      <input
        value={query}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="Search..."
      />
      {isPending && <div>Searching...</div>}
      <ul>
        {results.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

// useDeferredValue for responsive UI
function ProductList({ searchQuery }: { searchQuery: string }) {
  // Deferred value updates slower, keeps input responsive
  const deferredQuery = useDeferredValue(searchQuery);
  const [products, setProducts] = useState([]);

  useEffect(() => {
    // Heavy filtering operation uses deferred value
    const filtered = expensiveFilter(deferredQuery);
    setProducts(filtered);
  }, [deferredQuery]);

  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
}

// Suspense for code splitting and data fetching
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading component...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

// Suspense with data fetching (using libraries like Relay, React Query)
function UserProfile({ userId }: { userId: string }) {
  return (
    <Suspense fallback={<ProfileSkeleton />}>
      <ProfileDetails userId={userId} />
      <Suspense fallback={<PostsSkeleton />}>
        <UserPosts userId={userId} />
      </Suspense>
    </Suspense>
  );
}

// useId for SSR-safe unique IDs
function FormField({ label }: { label: string }) {
  const id = useId();

  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} type="text" />
    </div>
  );
}

// Combining concurrent features
function Dashboard() {
  const [activeTab, setActiveTab] = useState('overview');
  const [isPending, startTransition] = useTransition();

  const handleTabChange = (tab: string) => {
    startTransition(() => {
      setActiveTab(tab);
    });
  };

  return (
    <div>
      <nav>
        <button onClick={() => handleTabChange('overview')}>Overview</button>
        <button onClick={() => handleTabChange('analytics')}>Analytics</button>
        <button onClick={() => handleTabChange('reports')}>Reports</button>
      </nav>
      {isPending && <div>Loading...</div>}
      <Suspense fallback={<div>Loading content...</div>}>
        <TabContent tab={activeTab} />
      </Suspense>
    </div>
  );
}

// Automatic batching (React 18)
function Counter() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  const handleClick = () => {
    // Both updates batched automatically (even in async)
    setTimeout(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // Only one re-render
    }, 1000);
  };

  return <button onClick={handleClick}>Count: {count}</button>;
}

5. Virtual Scrolling react-window

Component Syntax Description Use Case
FixedSizeList <FixedSizeList height={} itemCount={} itemSize={}> List with fixed-height items Uniform item heights
VariableSizeList <VariableSizeList itemSize={(index) => {}}> List with dynamic item heights Variable content sizes
FixedSizeGrid <FixedSizeGrid columnCount={} rowCount={}> Grid with fixed cell sizes Spreadsheets, tables
Windowing Only renders visible items Renders only what's in viewport + buffer Performance with huge lists
react-virtuoso <Virtuoso data={} itemContent={}> Advanced virtualization with features Complex scrolling needs

Example: Virtual scrolling for large datasets

import { FixedSizeList, VariableSizeList } from 'react-window';
import AutoSizer from 'react-virtualized-auto-sizer';

// Fixed-size list
function VirtualList({ items }: { items: any[] }) {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

// Variable-size list
function VariableList({ items }: { items: any[] }) {
  const itemSizes = useRef<number[]>([]);

  const getItemSize = (index: number) => {
    // Calculate or cache item heights
    return itemSizes.current[index] || 50;
  };

  const Row = ({ index, style }: any) => {
    const rowRef = useRef<HTMLDivElement>(null);

    useEffect(() => {
      if (rowRef.current) {
        itemSizes.current[index] = rowRef.current.offsetHeight;
      }
    }, [index]);

    return (
      <div ref={rowRef} style={style}>
        {items[index].content}
      </div>
    );
  };

  return (
    <VariableSizeList
      height={600}
      itemCount={items.length}
      itemSize={getItemSize}
      width="100%"
    >
      {Row}
    </VariableSizeList>
  );
}

// With AutoSizer for responsive dimensions
function ResponsiveList({ items }: { items: any[] }) {
  return (
    <div style={{ height: '100vh' }}>
      <AutoSizer>
        {({ height, width }) => (
          <FixedSizeList
            height={height}
            width={width}
            itemCount={items.length}
            itemSize={50}
          >
            {({ index, style }) => (
              <div style={style}>{items[index].name}</div>
            )}
          </FixedSizeList>
        )}
      </AutoSizer>
    </div>
  );
}

// Infinite scroll with react-window
function InfiniteList({ loadMore }: { loadMore: () => Promise<void> }) {
  const [items, setItems] = useState<any[]>([]);
  const [hasMore, setHasMore] = useState(true);
  const [isLoading, setIsLoading] = useState(false);

  const loadMoreItems = async () => {
    if (isLoading || !hasMore) return;
    
    setIsLoading(true);
    const newItems = await loadMore();
    setItems(prev => [...prev, ...newItems]);
    setHasMore(newItems.length > 0);
    setIsLoading(false);
  };

  const isItemLoaded = (index: number) => !hasMore || index < items.length;

  return (
    <InfiniteLoader
      isItemLoaded={isItemLoaded}
      itemCount={hasMore ? items.length + 1 : items.length}
      loadMoreItems={loadMoreItems}
    >
      {({ onItemsRendered, ref }) => (
        <FixedSizeList
          height={600}
          itemCount={items.length}
          itemSize={50}
          onItemsRendered={onItemsRendered}
          ref={ref}
          width="100%"
        >
          {({ index, style }) => (
            <div style={style}>
              {isItemLoaded(index) ? items[index].name : 'Loading...'}
            </div>
          )}
        </FixedSizeList>
      )}
    </InfiniteLoader>
  );
}

// react-virtuoso (alternative with better features)
import { Virtuoso } from 'react-virtuoso';

function VirtuosoList({ items }: { items: any[] }) {
  return (
    <Virtuoso
      style={{ height: '600px' }}
      data={items}
      itemContent={(index, item) => (
        <div>
          <h3>{item.title}</h3>
          <p>{item.description}</p>
        </div>
      )}
      endReached={() => console.log('Reached end')}
    />
  );
}

6. Streaming SSR Suspense Components

Feature Implementation Description Benefit
Streaming SSR renderToPipeableStream() Sends HTML chunks as they're ready Faster Time to First Byte
Selective Hydration React 18 automatic Hydrates components in priority order Interactive sooner
Suspense Boundaries <Suspense fallback={...}> Stream different parts independently Progressive enhancement
Server Components Next.js 13+ RSC Zero-JS components on server Reduced bundle size
Progressive Hydration Load JS incrementally Prioritize visible/interactive parts Better perceived performance

Example: Streaming SSR with Suspense boundaries

// server.js - Node.js streaming SSR
import { renderToPipeableStream } from 'react-dom/server';
import App from './App';

app.get('/', (req, res) => {
  res.setHeader('Content-Type', 'text/html');

  const { pipe, abort } = renderToPipeableStream(<App />, {
    bootstrapScripts: ['/main.js'],
    onShellReady() {
      // Send initial shell immediately
      res.statusCode = 200;
      pipe(res);
    },
    onShellError(error) {
      res.statusCode = 500;
      res.send('<h1>Server Error</h1>');
    },
    onError(error) {
      console.error('Streaming error:', error);
    },
  });

  setTimeout(abort, 10000); // Abort after 10s
});

// App.tsx - Multiple Suspense boundaries
function App() {
  return (
    <html>
      <head>
        <title>Streaming SSR</title>
      </head>
      <body>
        <nav>Navigation (renders immediately)</nav>
        
        {/* High priority content */}
        <Suspense fallback={<HeroSkeleton />}>
          <Hero />
        </Suspense>

        {/* Stream independently */}
        <Suspense fallback={<ProductsSkeleton />}>
          <Products />
        </Suspense>

        {/* Lower priority, loads last */}
        <Suspense fallback={<CommentsSkeleton />}>
          <Comments />
        </Suspense>

        <footer>Footer (renders immediately)</footer>
      </body>
    </html>
  );
}

// Async component with data fetching
function Products() {
  const products = use(fetchProducts()); // React 19 use() hook

  return (
    <div>
      {products.map(p => <ProductCard key={p.id} product={p} />)}
    </div>
  );
}

// Next.js 13+ App Router with streaming
// app/page.tsx
import { Suspense } from 'react';

export default function Page() {
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Fast content renders first */}
      <Suspense fallback={<Skeleton />}>
        <FastComponent />
      </Suspense>

      {/* Slow content streams later */}
      <Suspense fallback={<Skeleton />}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

// Server Component (no JS sent to client)
async function FastComponent() {
  const data = await fetch('https://api.example.com/fast').then(r => r.json());
  return <div>{data.title}</div>;
}

// Slow async component
async function SlowComponent() {
  await new Promise(resolve => setTimeout(resolve, 3000));
  const data = await fetch('https://api.example.com/slow').then(r => r.json());
  return <div>{data.content}</div>;
}

// Client Component for interactivity
'use client';

import { useState } from 'react';

function InteractiveWidget() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// Streaming timeline visualization:
// Time 0ms:     HTML shell + nav + footer sent
// Time 100ms:   Hero content streams in
// Time 500ms:   Products stream in
// Time 2000ms:  Comments stream in
// User sees content progressively, not all at once

Rendering Strategy Comparison

Strategy Initial Load SEO Interactivity Best For
SSR (Next.js) Fast (pre-rendered) Excellent Hydration delay Content-heavy, SEO-critical
SSG (Static) Fastest (CDN) Excellent Hydration delay Blogs, docs, marketing
SPA (Vite) Slow (blank page) Poor Instant Web apps, dashboards
Streaming SSR Progressive Excellent Progressive Large pages, mixed content
ISR (Next.js) Fast (cached) Excellent Hydration delay E-commerce, dynamic content