How to Flush the Document Head Early

This guide is part of Streaming SSR & Early Flush, within Network & Server Response Optimization. The document head is almost always the same regardless of the data on the page: the same stylesheet, the same fonts, the same scripts, often a predictable LCP image (a product hero, an article header). Yet servers usually wait until all data is loaded and the whole page is rendered before sending even the first byte of the head. During that time, the browser has an open connection and nothing to do.

Early flush sends the head — <!doctype html>, meta tags, stylesheet links, font preloads, preconnects, and an LCP image preload when its URL is known — immediately, then does the slow work and sends the body. It works in any server stack that can write a response in parts: Node.js, PHP, Python, Ruby, Go, Java. It is the highest-value, lowest-complexity form of streaming.

Time until critical downloads start (uncached page) Bar chart comparing when the stylesheet and LCP image start downloading with and without early head flush. Time until critical downloads start (uncached page) Buffered - CSS request starts 870ms Buffered - LCP image starts 980ms Early flush - CSS request starts 120ms Early flush - LCP image starts 130ms

Rapid Diagnosis

  • Compare TTFB with server time: if TTFB roughly equals total server time, nothing is flushed early.
  • Check when the stylesheet request starts in the waterfall: right after the HTML finishes means no early flush.
  • Check server code for template rendering that returns a full string.
  • Check output buffering settings (PHP output_buffering, framework response buffering).

Root Cause Analysis

1. Render-then-send templates. Most templating returns a complete string.

2. Output buffering. Language runtimes or frameworks buffer by default.

3. Status codes decided late. Code paths decide 404 or redirects after data loads.

4. Head contents depend on data. Titles and meta descriptions need data, discouraging early flush.

Step-by-Step Resolution

1. Split the template into head and body

Render a head template that depends only on request-level information (route, locale) and maybe a fast lookup for the title.

2. Flush the head before slow work

php
<?php
// PHP: flush the head before database work.
header('Content-Type: text/html; charset=utf-8');
echo render_head(['css' => '/css/app.4f2a.css', 'lcp' => "/img/{$id}-1200.avif"]);
if (function_exists('ob_get_level')) { while (ob_get_level() > 0) ob_end_flush(); }
flush();                                           // send the head now
$product = load_product($id);                      // slow work afterwards
echo render_body($product);
// trade-off: output buffering at several layers (PHP-FPM, web server, gzip) can
// still hold the bytes; verify with curl timing in production.
python
# Python (Flask): stream a generator that yields the head first.
from flask import Response, stream_with_context
@app.route('/article/<slug>')
def article(slug):
    def gen():
        yield render_template('head.html', css=CSS_URL)
        data = load_article(slug)                  # slow
        yield render_template('body.html', article=data)
    return Response(stream_with_context(gen()), mimetype='text/html')

3. Decide status codes before flushing

Do a fast existence check (cache lookup or a primary-key query) before sending the head. If the resource does not exist or requires a redirect, respond normally without streaming.

4. Resolve title and meta quickly or update later

Use a fast lookup for the title and description, or emit a generic title and set a more specific one during rendering (search engines use the final DOM). Social preview metadata should be correct in the HTML; for those, fetch it before flushing.

Request flow with early flush Order of operations for a request handled with early head flush. Request flow with early flush Route + exists? fast check Status + headers decided now Flush head CSS, preload, preconnect Slow work queries, APIs Body render + send

Verification

Use curl -sN with timestamps to confirm the head arrives before the body. In DevTools, the stylesheet and LCP image requests should start long before the document finishes downloading. Compare LCP for uncached requests before and after.

Worked Example: A WordPress News Site

A WordPress-based news site had uncached TTFB around 700ms because of plugins and database queries, with the theme rendering the full page before output. The team modified the theme to output the head (stylesheet, font preloads, a preload of the featured image computed from the post ID with a fast lookup) and call flush() before the main loop ran. They disabled an output-buffering plugin and set fastcgi_buffering off for HTML routes in nginx. The stylesheet and featured image now started downloading about 600ms earlier; LCP p75 for uncached article views improved from 2.7 to 2.1 seconds.

What Belongs in the Flushed Head

Include everything the browser needs to start critical work: <meta charset> and viewport, the main stylesheet (or inlined critical CSS), preloads for the main font files (with crossorigin), a preload for the LCP image if its URL is predictable, preconnects to critical third-party origins, and module or deferred scripts. Avoid synchronous third-party scripts — they block parsing of the rest of the stream. If the LCP image is not predictable from the route, flush the head anyway; the stylesheet and fonts alone justify it, and the image will be discovered as soon as the body chunk containing it arrives.

What to put in the early head Elements to include or avoid in an early-flushed document head. What to put in the early head Element Include? Why Stylesheet / critical CSS yes starts the render-blocking download Font preloads yes fonts discovered late otherwise LCP image preload if URL known starts the LCP download early Preconnect to critical origins 1-3 warms connections Sync third-party scripts no block parsing of the stream

Common Mistakes

  • Buffering somewhere downstream. The code flushes, but the bytes do not leave the server.
  • Flushing before deciding status. A 200 for a missing page.
  • Synchronous scripts in the head. Block parsing of streamed body content.
  • Flushing tiny amounts with compression. Compressors may hold small chunks; flush the compressor too.

Edge Cases

Personalised heads. If the head varies per user (CSRF tokens, user-specific preloads), it can still be flushed early as long as it is computed quickly.

Caching. Early-flushed responses can be cached at the CDN; cached responses are served in full immediately.

HTTP/1.1 clients. Streaming works over chunked transfer encoding on HTTP/1.1 and natively over HTTP/2 and HTTP/3.

Early Hints. 103 Early Hints can deliver preloads even before the head; the two techniques combine well.

FAQ

What is the benefit of flushing the head early?

The browser starts downloading CSS, fonts and the LCP image while the server is still generating the page, overlapping network and server time.

Does early flush work with PHP?

Yes, with flush() after disabling output buffering, and making sure the web server and FastCGI layer do not buffer.

Is early flush the same as Early Hints?

No. Early Hints is a separate 103 response with Link headers sent before the final response. Early flush sends the start of the actual HTML. They complement each other.

Can I flush if my title depends on data?

Yes, with a fast title lookup before flushing, or by flushing everything except the title-dependent tags. Most of the gain comes from stylesheet and preload hints.

Does early flush affect caching?

No. Responses can still be cached; caches store the full response.

How do I know if my server buffers?

Measure with curl and timestamps, or compare TTFB with server processing time. If TTFB includes all server time, something buffers.

Does early flush help pages served from CDN cache?

Little. Cached responses arrive quickly in full anyway. Early flush helps uncached, server-rendered responses.

Should I flush the header and navigation too?

If they render without slow data, yes — flushing a visible shell lets the browser paint it early, improving FCP.

Will flushing early break my layout or scripts?

Not if the head is complete and valid on its own. Scripts in the head should be deferred or modules, so they run after the full document has been parsed regardless of when it arrives.

What if my framework buffers responses?

Check whether it has a streaming mode; most modern frameworks do. If not, a small custom server route for key templates can flush the head before handing off to the framework renderer.