TanStack / TanStack/router

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

Open Beginner friendly
#6,749 9 comments 7 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

start-head
Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in headContentUtils.tsx and trace how preloadLinks and links are assembled into the rendered head. Verify the current SSR output and change the ordering so stylesheet links appear before modulepreload links. Done means the generated head consistently uses that order and the reported HTTP/1.1 waterfall no longer discovers CSS after modulepreload links.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.