AvalonAvalon
GitHub

Streaming SSR

Avalon streams the layout shell to the browser before page data resolves, so users see content instantly.

Avalon renders HTML in two phases. The layout shell — head, CSS, navigation — is flushed to the browser immediately. The page content renders in the background while the browser is already painting. No configuration, no special APIs.

export default async function BlogIndex() {
  // The shell is already in the browser by the time this fetch starts
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());

  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

The first chunk

The browser receives everything from the shell layout before the page component even starts executing:

  • <!DOCTYPE html> and <html>
  • The entire <head> — meta tags, stylesheets, font links, preload hints
  • Navigation, header, sidebar — anything the shell layout renders before {children}
  • All collected CSS from CSS Modules and global imports

This means fonts and stylesheets start downloading while the server is still waiting on your API calls.

Enabled by default

If your page has a shell layout (one that returns <html>), Avalon streams it. There's nothing to turn on.

Verifying in DevTools

Streamed responses include these headers:

Transfer-Encoding: chunked
X-Avalon-Streaming: 1

X-Avalon-Streaming: 1 is the Avalon-specific signal. Transfer-Encoding: chunked alone doesn't prove streaming — proxies and CDNs add that too.

You can also check the Network tab's waterfall: if sub-resources (CSS, fonts, scripts) start downloading before the document finishes, the shell was flushed early.

Fallback to buffered rendering

Avalon sends the full HTML in one shot when:

  • No shell layout exists (no layout returns <html>)
  • The page skips all layouts via layoutConfig.skipLayouts
  • The page returns a complete HTML document itself
  • The page is outside the modular layout system

Buffered pages still render on the server — they just don't get the two-phase flush.

Impact on Core Web Vitals

MetricWithout streamingWith streaming
FCPBlocked until data resolvesNear-instant (shell only)
LCPDepends on dataSame — content still waits for data
TTFBAfter full renderAfter shell render

Streaming improves FCP and TTFB. LCP stays the same because the largest element (usually the page body) still depends on the data fetch. But the perceived performance is dramatically better — layout instead of a blank screen.