Hybrid Rendering Concepts
This demo showcases modern web rendering strategies using Next.js 14+. Each approach has unique benefits and use cases. Explore the concepts below to understand when and why to use each strategy.
Quick Navigation
Static Site Generation (SSG)
Pages are pre-rendered at build time, resulting in the fastest possible loading experience.
Used in this demo:
How it works:
- Pages are generated during the build process
- HTML, CSS, and JavaScript are pre-computed
- Served directly from CDN with minimal server processing
// app/page.tsx
// ā
SSG happens automatically when:
// 1. No request-specific data (no cookies, headers, searchParams)
// 2. All data can be fetched at build time
// 3. No dynamic segments without generateStaticParams
export default async function Home() {
// This runs at BUILD TIME, not request time
const [recipes, categories] = await Promise.all([
getRecipes(), // Static data from JSON/database
getCategories() // Available at build time
]);
const featuredRecipes = recipes.slice(0, 3);
return (
<div>
<h1>Featured Recipes</h1>
{featuredRecipes.map((recipe) => (
<RecipeCard key={recipe.id} recipe={recipe} />
))}
</div>
);
}
// š« This would force SSR instead:
// export default async function Home({ searchParams }) {
// const query = searchParams.q; // Request-specific data = SSR
// const recipes = await searchRecipes(query);
// return <div>...</div>;
// }š What Triggers SSG in Next.js:
- ⢠No request-specific data
- ⢠No searchParams, cookies, headers
- ⢠Static data fetching only
- ⢠generateStaticParams for dynamic routes
- ⢠Using searchParams
- ⢠Reading cookies() or headers()
- ⢠Dynamic data per request
- ⢠No generateStaticParams for [dynamic]
ā Best for:
- ⢠Landing pages
- ⢠Marketing content
- ⢠Blog posts
- ⢠Product catalogs
- ⢠Documentation
ā Not ideal for:
- ⢠Frequently changing data
- ⢠User-specific content
- ⢠Real-time features
- ⢠Dynamic interactions
Server-Side Rendering (SSR)
Pages are rendered on the server for each request, ensuring fresh data and optimal SEO.
Used in this demo:
How it works:
- Server renders HTML for each incoming request
- Fresh data is fetched on every request
- Client receives fully rendered HTML
// Comments are server-rendered for fresh data
export default async function RecipePage({ params }: RecipePageProps) {
const [recipe, comments] = await Promise.all([
getRecipe(params.id),
getComments(params.id) // Fresh comments on every request
]);
return (
<section>
<h2>Reviews ({comments.length})</h2>
{comments.map((comment) => (
<div key={comment.id}>
<div className="font-semibold">{comment.author}</div>
<p>{comment.content}</p>
<time className="text-gray-500">
{new Date(comment.createdAt).toLocaleDateString()}
</time>
</div>
))}
</section>
);
}ā Best for:
- ⢠Dynamic content
- ⢠User-specific data
- ⢠SEO-critical pages
- ⢠Fresh data requirements
- ⢠Social media shares
ā Trade-offs:
- ⢠Slower than SSG
- ⢠Server processing required
- ⢠Higher server costs
- ⢠TTFB (Time to First Byte) delay
Client-Side Rendering (CSR)
JavaScript runs in the browser to render content dynamically, enabling rich interactions.
Used in this demo:
How it works:
- Initial HTML shell is minimal
- JavaScript renders content in the browser
- API calls fetch data after page load
'use client';
export default function SearchPage() {
const [query, setQuery] = useState('');
const [recipes, setRecipes] = useState<Recipe[]>([]);
const [loading, setLoading] = useState(false);
const searchRecipes = useCallback(async (searchQuery: string) => {
setLoading(true);
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`);
const data = await response.json();
setRecipes(data.recipes || []);
} finally {
setLoading(false);
}
}, []);
// Debounced search
useEffect(() => {
const timeoutId = setTimeout(() => {
searchRecipes(query);
}, 300);
return () => clearTimeout(timeoutId);
}, [query, searchRecipes]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search recipes..."
className="w-full px-4 py-2 border rounded-lg"
/>
{loading ? <Spinner /> : <RecipeList recipes={recipes} />}
</div>
);
}ā Best for:
- ⢠Interactive features
- ⢠Real-time updates
- ⢠User interactions
- ⢠Dynamic filtering
- ⢠Complex state management
ā Trade-offs:
- ⢠Poor initial SEO
- ⢠Slower first contentful paint
- ⢠JavaScript dependency
- ⢠Bundle size impact
Incremental Static Regeneration (ISR)
Combines the benefits of SSG with the ability to update static content after deployment.
Used in this demo:
How it works:
- Pages are initially generated at build time
- Background regeneration occurs based on revalidation rules
- Users always get fast static pages, with updates seamlessly applied
// š KEY: This export enables ISR
export const revalidate = 3600; // Revalidate every hour
// Generate static params for all recipe pages
export async function generateStaticParams() {
const recipes = await getRecipes();
return recipes.map((recipe) => ({
id: recipe.id, // Pre-generate /recipe/spaghetti-carbonara, etc.
}));
}
export default async function RecipePage({ params }: RecipePageProps) {
// This runs at build time AND periodically after deployment
const recipe = await getRecipe(params.id);
return (
<article>
<h1>{recipe.title}</h1>
<p>{recipe.description}</p>
<div className="ingredients">
{recipe.ingredients.map((ingredient, i) => (
<div key={`ingredient-${i}`}>{ingredient}</div>
))}
</div>
</article>
);
}
// šÆ What triggers ISR:
// ā
export const revalidate = number
// ā
generateStaticParams for dynamic routes
// ā
No request-specific data (like searchParams)ISR Revalidation Strategies:
Time-based:
export const revalidate = 3600Regenerate every hour
On-demand:
revalidatePath('/recipes/id')Trigger updates manually
ā Best for:
- ⢠E-commerce product pages
- ⢠Blog posts with comments
- ⢠News articles
- ⢠Content that updates periodically
- ⢠High-traffic pages
āļø Considerations:
- ⢠Complexity in cache management
- ⢠Potential stale content windows
- ⢠Build time requirements
- ⢠Revalidation strategy planning
š§ Understanding Hydration
Hydration is the process where React takes over static HTML from the server and makes it interactive. It's the bridge between server-rendered content and client-side interactivity.
1Server Renders HTML
The server generates complete HTML with all content, but without JavaScript event handlers.
2React Hydrates
React JavaScript loads and "hydrates" the static HTML, attaching event listeners and state.
3Seamless Transition
Users see content immediately (server HTML) while JavaScript loads in the background, then interactivity is progressively enhanced.
// This component demonstrates hydration
'use client';
import { useState, useEffect } from 'react';
export function HydrationDemo() {
const [isHydrated, setIsHydrated] = useState(false);
const [count, setCount] = useState(0);
// This effect only runs on the client after hydration
useEffect(() => {
setIsHydrated(true);
}, []);
return (
<div className="p-4 border rounded">
<h3>Hydration Status</h3>
<p>
Status: {isHydrated ? 'ā
Hydrated' : 'ā³ Server HTML'}
</p>
{/* This button starts non-interactive */}
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Count: {count}
</button>
{/* This only appears after hydration */}
{isHydrated && (
<p className="text-green-600 text-sm mt-2">
š JavaScript is now active!
</p>
)}
</div>
);
}Common Hydration Issues
Hydration Timeline Visualization
Try It Yourself
š§ Live Hydration Demo
Button not yet interactive...
Try this: Disable JavaScript in your browser and reload. You'll see the HTML content but no interactivity.
šļø Advanced Rendering Concepts
Modern web development continues to evolve with new patterns like Islands Architecture and Streaming SSR that push the boundaries of performance and user experience.
Islands Architecture
Better implemented in Astro, Fresh, or Qwik
Islands of interactivity in a sea of static HTML. Only specific components are hydrated, dramatically reducing JavaScript bundle size and improving performance.
How Islands Work:
- ⢠Static HTML by default
- ⢠Selective hydration of interactive components
- ⢠Components load independently
- ⢠Minimal JavaScript footprint
<!-- Astro Example (Islands Architecture) -->
---
// This runs on the server only
const staticData = await fetchData();
---
<Layout>
<!-- Static HTML - no JS needed -->
<Header />
<StaticContent data={staticData} />
<!-- Interactive island - hydrated on client -->
<SearchBox client:load />
<!-- Another island - lazy loaded -->
<CommentSection client:visible />
<!-- Static footer - no JS -->
<Footer />
</Layout>
<!-- Result: Only SearchBox and CommentSection
have JavaScript, rest is pure HTML -->Next.js vs Islands:
Next.js hydrates entire pages by default. While you can optimize with dynamic imports and React.lazy, true islands architecture requires frameworks specifically designed for it like Astro or Qwik.
Streaming SSR
Available in Next.js App Router
Stream HTML to the browser as it's generated, showing content progressively instead of waiting for the entire page to render.
Streaming Benefits:
- ⢠First content appears faster
- ⢠Better perceived performance
- ⢠Handles slow data sources gracefully
- ⢠Reduces Time to First Byte (TTFB)
import { Suspense } from 'react';
export default async function RecipePage({ params }) {
// Fast data loads immediately
const recipe = await getRecipe(params.id);
return (
<div>
{/* This renders and streams first */}
<h1>{recipe.title}</h1>
<img src={recipe.image} alt={recipe.title} />
{/* This streams when ready */}
<Suspense fallback={<CommentsSkeleton />}>
<Comments recipeId={params.id} />
</Suspense>
{/* This also streams independently */}
<Suspense fallback={<RelatedSkeleton />}>
<RelatedRecipes category={recipe.category} />
</Suspense>
</div>
);
}
// This component streams when data is ready
async function Comments({ recipeId }) {
// This might be slow (database query, API call)
const comments = await getComments(recipeId);
return (
<div>
{comments.map(comment => (
<div key={comment.id}>{comment.content}</div>
))}
</div>
);
}In This Demo:
Visit any recipe page to see streaming in action. The recipe content loads first, then comments stream in when ready!
Framework Comparison for Advanced Patterns
| Framework | Islands | Streaming | Bundle Size | Best For |
|---|---|---|---|---|
| Next.js | Partial Dynamic imports | Excellent Built-in Suspense | Medium Full hydration | Full-stack apps |
| Astro | Excellent Native islands | Limited Static focus | Minimal Selective JS | Content sites |
| Qwik | Excellent Resumability | Good Progressive | Minimal Fine-grained | Interactive apps |
| Fresh (Deno) | Excellent Island-first | Good Edge focus | Minimal No build step | Edge apps |
Next.js Core Concepts
Beyond rendering strategies, Next.js provides a rich ecosystem of features. Here are key concepts that power modern web applications.
šApp Router
File-system based routing with layouts, nested routes, and parallel routes. Replaces the Pages Router with more powerful patterns.
āļøServer Components
React components that render on the server, reducing client bundle size and enabling direct database access.
š¬Streaming & Suspense
Progressive page loading that streams content as it becomes available, improving perceived performance.
š ļøDeveloper Experience
Built-in tools for debugging, optimization, and development workflow that make building web apps faster and more enjoyable.
š¼ļøBuilt-in Optimizations
Automatic optimizations for images, fonts, scripts, and more. Performance best practices applied by default.
šDeployment & Scaling
Seamless deployment with Vercel, edge functions, and automatic scaling. Built for production from day one.
app/
āāā layout.tsx // Root layout (shared across all pages)
āāā page.tsx // Homepage (/)
āāā loading.tsx // Loading UI for this level
āāā error.tsx // Error UI for this level
āāā not-found.tsx // 404 page
āāā recipes/
ā āāā layout.tsx // Recipes layout
ā āāā page.tsx // Recipes list (/recipes)
ā āāā [id]/
ā āāā page.tsx // Recipe detail (/recipes/[id])
ā āāā loading.tsx // Loading UI for recipe details
āāā api/
āāā search/
āāā route.ts // API endpoint (/api/search)Performance Comparison
| Strategy | TTFB | FCP | SEO | Freshness | Scalability |
|---|---|---|---|---|---|
| SSG | Excellent | Excellent | Excellent | Poor | Excellent |
| SSR | Good | Good | Excellent | Excellent | Poor |
| CSR | Excellent | Poor | Poor | Excellent | Good |
| ISR | Excellent | Excellent | Excellent | Good | Excellent |
TTFB: Time to First Byte | FCP: First Contentful Paint | SEO: Search Engine Optimization
Ready to Explore?
Now that you understand the concepts, explore different pages in this demo to see each rendering strategy in action.