Skip to content

<head> element ordering: link[stylesheet] should come before link[modulepreload] #6749

@steschi

Description

@steschi

Which project does this relate to?

Start

Describe the bug

In headContentUtils.tsx, preloadLinks are placed before links, which means <link rel="modulepreload"> elements appear before <link rel="stylesheet"> in the rendered <head>.

Browsers fetch resources top-to-bottom. When modulepreload links come first, the render-blocking CSS resources needed to display styled SSR HTML get discovered late. Under HTTP/1.1's 6-connection-per-host limit, the CSS request can't even start until earlier JS downloads free up a connection.

Key result (Using a slow 4G throttle + 22 slow modulepreload links):

Scenario FCP Δ
HTTP/1.1 — modulepreload first 2075ms
HTTP/1.1 — stylesheet first 1260ms −815ms (39%)
HTTP/2 — modulepreload first 1388ms
HTTP/2 — stylesheet first 1380ms −8ms (~1%)

ℹ️ Happy to open a PR if this is something you'd like to see fixed. 🚀

Your Example Website or App

https://github.com/steschi/html-head-order-matters

Steps to Reproduce the Bug or Issue

  1. Create a TanStack Router SSR app with multiple route chunks (so Vite emits many modulepreload links) as well as a stylesheet import.
  2. Build and serve with vite preview (HTTP/1.1)
  3. Open Chrome DevTools → Network tab, throttle to Slow 4G
  4. Load the page and observe the waterfall: modulepreload fetches start before the stylesheet fetch
  5. The stylesheet is blocked behind the 6-connection limit, delaying FCP

Expected behavior

<link rel="stylesheet"> should appear before <link rel="modulepreload"> in the <head>, so the browser discovers and fetches render-blocking CSS first. This matches the capo.js recommended element ordering (stylesheets = weight 5, modulepreload = weight 4).

Screenshots or Videos

See the full reproduction repo for waterfall charts and Chrome DevTools traces:
https://github.com/steschi/html-head-order-matters

Inefficient head order Performant head order

Platform

  • Router / Start Version: 1.162.4 with nitro v3
  • OS: macOS
  • Browser: Chrome
  • Browser Version: Chrome 145

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions