Skip to main content
This project is part of Week 7 and implements modern SSR patterns including streaming and selective hydration.

Overview

Build a production-ready server-side rendering framework with streaming capabilities, progressive hydration, and file-based routing. This project implements the core architecture of Next.js-style frameworks.

Project requirements

Streaming

renderToPipeableStream implementation

Hydration

Progressive and selective hydration

Routing

File-based routing system

Data fetching

SSG and ISR support

Core components

1

Server-side rendering

Implement SSR with streaming:
  • renderToString vs renderToPipeableStream
  • Progressive hydration and selective hydration
  • Double pass problem and solutions
  • Edge rendering vs Node rendering
Streaming enables faster time to first byte and progressive page loading.
2

File-based routing

Build Next.js-style routing:
  • File-based routing implementation
  • Dynamic routes with parameters
  • Nested layouts and route groups
  • API routes and middleware
The file system structure directly maps to URL routes.
3

Data fetching patterns

Implement data fetching strategies:
  • SSG: getStaticProps build-time execution
  • ISR: revalidation and stale-while-revalidate
  • SSR: per-request data fetching
  • Client-side fetching integration
Different strategies optimize for different use cases.
4

Module bundling

Build bundling and code splitting:
  • Module bundling and code splitting
  • Route-based code splitting
  • Component-based lazy loading
  • Shared chunk optimization
Ensure optimal bundle sizes and loading performance.

Server-side rendering strategies

Synchronous rendering - waits for entire page before sending. Simple but slower time to first byte.
const html = renderToString(<App />);
res.send(html);
Streaming rendering - sends HTML as it’s generated. Much faster time to first byte, progressive loading.
const { pipe } = renderToPipeableStream(<App />, {
  onShellReady() {
    pipe(res);
  }
});

Hydration strategies

Hydration attaches event handlers to server-rendered HTML to make it interactive.

Progressive hydration

Hydrate components progressively as they become visible:
  • Shell first: Hydrate critical above-the-fold content immediately
  • Lazy hydration: Defer non-critical components
  • On-demand hydration: Hydrate on user interaction

Selective hydration

Suspense boundaries

Hydrate around Suspense boundaries independently.

Priority-based

Hydrate high-priority components first.

Interrupt and resume

Interrupt hydration for user interactions.

Streaming

Stream HTML and hydrate as it arrives.

Double pass problem

The double pass problem occurs when server and client render differently, causing hydration mismatches.

Solutions

Deterministic rendering Ensure server and client render exactly the same initial HTML. Suppress hydration warnings Use suppressHydrationWarning for unavoidable differences (timestamps, random IDs). Two-pass rendering Render twice on client - first to match server, then to add client-only features.

File-based routing

1

Directory structure

Map file system to routes:
pages/
  index.js          → /
  about.js          → /about
  blog/
    index.js        → /blog
    [slug].js       → /blog/:slug
2

Dynamic routes

Use brackets for dynamic segments: [id].js, [...slug].js
3

API routes

Handle API endpoints in pages/api/ directory.
4

Middleware

Process requests before routing with middleware functions.

Data fetching strategies

Generate HTML at build time using getStaticProps.
  • Fastest performance - pre-rendered HTML
  • Best for content that doesn’t change often
  • Can be cached on CDN
export async function getStaticProps() {
  const data = await fetchData();
  return { props: { data } };
}
Revalidate static pages after deployment.
  • Combines benefits of static and dynamic
  • Stale-while-revalidate pattern
  • Regenerate pages in background
export async function getStaticProps() {
  return {
    props: { data },
    revalidate: 60 // Revalidate every 60 seconds
  };
}
Render HTML per-request using getServerSideProps.
  • Always fresh data
  • Slower than static (server processing on every request)
  • Use for highly dynamic content
export async function getServerSideProps(context) {
  const data = await fetchData(context.params);
  return { props: { data } };
}

Edge vs Node rendering

Edge rendering

Run at edge locations close to users. Ultra-low latency, limited runtime.

Node rendering

Run on traditional servers. Full Node.js runtime, slightly higher latency.

Code splitting

Split code into smaller chunks to improve initial load time and enable lazy loading.

Splitting strategies

Route-based splitting Automatic code split per route - each page loads only its required code. Component-based splitting Lazy load components with dynamic imports:
const HeavyComponent = lazy(() => import('./HeavyComponent'));
Shared chunks Extract common dependencies into shared chunks to avoid duplication.

Deliverables

  • SSR framework with streaming support (renderToPipeableStream)
  • Progressive hydration implementation
  • Selective hydration with Suspense boundaries
  • File-based routing system with dynamic routes
  • API routes and middleware support
  • SSG with getStaticProps
  • ISR with revalidation
  • SSR with getServerSideProps
  • Code splitting (route-based and component-based)
  • Edge and Node rendering support
  • Example app demonstrating all features
  • Performance comparison of different strategies

Success criteria

  • Streaming SSR shows content progressively
  • Time to first byte is optimized with streaming
  • Hydration works without mismatches
  • Progressive hydration defers non-critical components
  • Selective hydration prioritizes user interactions
  • File-based routing correctly maps files to routes
  • Dynamic routes handle parameters properly
  • SSG generates static HTML at build time
  • ISR revalidates pages in background
  • Code splitting reduces initial bundle size
  • Each route loads only necessary code
  • Framework handles edge cases gracefully

Build docs developers (and LLMs) love