Skip to content

perf: lower manifest script preload priority - #8212

Merged
schiller-manuel merged 4 commits into
TanStack:mainfrom
maxktz:fix/preload-priority
Oct 16, 2026
Merged

schiller-manuel merged 4 commits into
TanStack:mainfrom
maxktz:fix/preload-priority

Conversation

@maxktz

@maxktz maxktz commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

What

Generated script preloads, meaning the modulepreload links (or preload as="script" for IIFE output) that HeadContent renders from the Start manifest, now carry fetchPriority="low". Static Early Hints and the response Link header now carry it too.

Server-rendered HTML doesn't need this JavaScript to paint. Until now Chrome fetched it at High, so on bandwidth-bound connections it competed with render-blocking stylesheets, fonts and in-viewport images. With low, those go first, and the JS still arrives in time for hydration. React already uses this default for its own bootstrapModules preloads, and Next.js inherits it.

Changes

  • getScriptPreloadAttrs (router-core) returns fetchPriority: 'low'. React, Solid and Vue HeadContent all render through this helper, and so does Early Hints.
  • collectStaticHintsFromManifest (start-server-core) builds script hints with the shared addEarlyHintFetchAttrs, so fetchPriority is forwarded along with crossOrigin. Without this, Chromium had already started the hinted fetch at MEDIUM before the HTML attribute arrived.
  • Tests:
    • start-server-core: Early Hints unit tests check the hint objects and the serialized fetchpriority=low.
    • react-start/early-hints e2e: 103 and response Link header assertions.
    • React: SSR and iife tests.
    • Solid: new server-suite SSR test and an iife test.
    • Vue: assertions added, and the SSR hydration fixtures updated.
  • Docs: one bullet in the Early Hints guide.

Evidence

Applied-throttling measurements in this thread show large first-paint gains when the preload set is big, with hydration time unchanged:

  • 71 preloads / ~445 KB: mobile FCP 4.44 s → 2.87 s; hydration 6.71 s → 6.58 s.
  • 143 preloads: FCP 4.65 s → 0.87 s; all JS done 6.56 s → 6.47 s.

With small preload sets, the real-world difference is about zero. Lighthouse's simulated gains on small sets are a Lantern artifact, which GoogleChrome/lighthouse#16869 addresses.

Deliberately out of scope

  • No scoping by ssr mode. This applies to ssr: false, 'data-only' and the SPA shell as well. Stylesheets block rendering even for content drawn by client JS, and React also lowers its bootstrap preloads unconditionally.
  • No escape hatch yet. If one becomes necessary, it should be fetchPriority on the manifest link via transformAssets, so that the HTML, Early Hints and hydration all see the same value.
  • RSC client-reference preloads (ReactDOM.preloadModule) are a follow-up. They need React ≥ 19.3 and a server-only branch.
  • Head order of stylesheets vs. preloads (<head> element ordering: link[stylesheet] should come before link[modulepreload] #6749) gets its own PR, with an HTTP/1.1 measurement.
  • Deferred hydration child chunks are never part of the manifest preloads, so they're unaffected.

Closes #8546

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • JavaScript preload links now use low fetch priority across React, Solid, and Vue, including module and IIFE scripts.
    • Static Early Hints now carry the low-priority setting for JavaScript preloads.
  • Documentation
    • Updated the Early Hints guide to describe low-priority JavaScript hints.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: TanStack/router/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 07229ec2-3547-4caf-bb60-36b72326d609
📥 Commits

Reviewing files that changed from the base of the PR and between 7a06725 and 8606e1a.

📒 Files selected for processing: 10
  • .changeset/quiet-script-preloads.md
  • docs/start/framework/react/guide/early-hints.md
  • e2e/react-start/early-hints/tests/early-hints-protocol.spec.ts
  • packages/react-router/tests/Scripts.test.tsx
  • packages/router-core/src/manifest.ts
  • packages/solid-router/tests/Scripts.test.tsx
  • packages/solid-router/tests/server/HeadContent.test.tsx
  • packages/start-server-core/src/early-hints.ts
  • packages/start-server-core/tests/early-hints.test.ts
  • packages/vue-router/tests/Scripts.test.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Generated script preloads now use low fetch priority. Static Early Hints copy supported fetch attributes from manifest preloads. Framework tests, HTTP/2 protocol tests, documentation, and a changeset describe or verify these updates.

Changes: Script preload priority

Layer / File(s) Summary
Generated preload attributes and rendering
packages/router-core/src/manifest.ts, packages/react-router/tests/Scripts.test.tsx, packages/solid-router/tests/*, packages/vue-router/tests/Scripts.test.tsx
getScriptPreloadAttrs now returns fetchPriority: 'low'. React, Solid, and Vue tests check low-priority module and IIFE script preloads.
Static Early Hints propagation
packages/start-server-core/src/early-hints.ts, packages/start-server-core/tests/early-hints.test.ts, e2e/react-start/early-hints/tests/early-hints-protocol.spec.ts, docs/start/framework/react/guide/early-hints.md, .changeset/quiet-script-preloads.md
Static script preload hints copy supported fetch attributes. Tests and HTTP/2 assertions check low priority in Link values. The guide documents the priority, and the changeset declares patch releases.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Other · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 8606e

The preload-priority change is ready to merge after normal checks. Performance may vary with page preload counts, but no concrete blocking regression is established.

Pre-merge checks: Passed 2 · Failed 3

Failed Failed checks: 3 warnings

Check name Status Explanation Resolution
Linked Issues check Warning Issue #8546 requires low priority for generated preloads and forwarding into static Early Hints. The PR implements both: getScriptPreloadAttrs returns fetchPriority: 'low', `collectStaticHintsFrom… Limit the low-priority default to server-rendered matches. Preserve the existing priority for ssr: false and SPA output. Add the requested supported priority override, such as a transform result or HeadContent option, and ensure HTML an…
Out of Scope Changes check Warning The preload and Early Hints changes are related to issue #8546, and the added tests and documentation support that objective. The implementation also changes ssr: false, data-only, and SPA manifest … Restrict the changed behavior to server-rendered matches. Do not lower priority for ssr: false or SPA manifest preloads unless the issue scope is updated and the behavior is intentionally accepted.
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 8 files. (2 skipped: 2 … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 2 passed
Check name Status Explanation
Description check Passed The description clearly explains the low-priority preload change, motivation, implementation, testing, performance evidence, and scope. It does not use the template headings or include the checklist a…
Title check Passed The title is concise, specific, and accurately summarizes the main change: lowering the priority of manifest script preloads.
Full details: Linked Issues check

Explanation

Issue #8546 requires low priority for generated preloads and forwarding into static Early Hints. The PR implements both: getScriptPreloadAttrs returns fetchPriority: 'low', collectStaticHintsFromManifest uses addEarlyHintFetchAttrs, and tests cover hint objects and serialized headers. However, the issue also requires preserving the existing priority for ssr: false matches and SPA mode, plus an escape hatch. The changed helper applies low unconditionally, and the PR adds neither escape hatch.

Resolution

Limit the low-priority default to server-rendered matches. Preserve the existing priority for ssr: false and SPA output. Add the requested supported priority override, such as a transform result or HeadContent option, and ensure HTML and Early Hints use the same value.

Full details: Out of Scope Changes check

Explanation

The preload and Early Hints changes are related to issue #8546, and the added tests and documentation support that objective. The implementation also changes ssr: false, data-only, and SPA manifest preloads because getScriptPreloadAttrs now returns low for every call. Issue #8546 identifies those client-rendered modes as excluded from the requested default, so this behavior is outside the linked issue scope.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 8 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@quanglam2807

Copy link
Copy Markdown

This is great! We’ve also seen lower Lighthouse scores after migrating to Next.js 16.3, so hopefully this will fix it.

@quanglam2807

quanglam2807 commented Sep 10, 2026 •

Copy link
Copy Markdown

Edited 2026-09-10. My original comment claimed the entry chunk is "the one preload in the set that gates hydration" and proposed exempting it. That was wrong — every generated preload is hydration-critical. Corrected below, and the conclusion has changed with it.

We ran this patch downstream on two SSR marketing apps for a week. I've since reverted it, and the reasons may be useful input on your open questions.

Measured priorities

A probe page through headless Chrome with --log-net-log: three modulepreload links (no hint / low / high), plus an unhinted <script type="module" async> whose statically-imported dependency also had no hint.

resource net priority Blink / DevTools
document, stylesheet HIGHEST Highest
modulepreload (no hint) MEDIUM High
modulepreload fetchpriority="high" MEDIUM High
<script type="module" async> (no preload) MEDIUM High
static dep of that async module (no preload) MEDIUM High
modulepreload fetchpriority="low" LOWEST Low

Two things follow.

An async module script and its static dependencies are already fetched at High. So the generated modulepreload links aren't raising priority above what the module graph gets on its own once the entry parses — they only remove the discovery waterfall. What the patch buys is bandwidth, not the correction of a priority inversion. That still favours the change, but it's a different mechanism than "these compete at high priority with CSS": the stylesheet is Highest and outranks them either way.

fetchpriority="high" on a modulepreload is a no-op versus omitting the attribute. Scripts don't reach Highest that way. Any exemption you might want to build is therefore free — it can't outrank the resources this PR protects — but by the same token it can't buy back much either.

Why an entry-only exemption doesn't work

My first instinct was your question 3's shape: keep low for route assets, exempt the bootstrap chunk so hydration isn't held behind tight mode. That doesn't hold up.

getChunkPreloads emits [chunk.fileName, ...chunk.imports] — rollup's static imports, with dynamicImports excluded — and hydrate() in load-client.js awaits loadRouteChunk for every committed match before hydrating. So the entire preload set is on the hydration critical path, not just the entry. On our marketing pages that's 72 chunks. Promoting one of them starts hydration no earlier; hydration waits on the slowest member of the graph.

That makes this a genuine paint-versus-interactive tradeoff across the whole set, rather than something that can be tuned by classifying assets. Which I think sharpens your risk 1 rather than resolving it.

Why we reverted

Mostly your own numbers. The 1.098 s mobile FCP improvement is under Lighthouse simulated throttling; under applied throttling the same deployments differ by 13 ms. Lighthouse's simulator models priority explicitly, so simulated results are exactly where a priority hint would look strongest. Your write-up is admirably clear about this — I just think the 13 ms number deserves more weight than the 1.1 s one when choosing a framework default.

Against that, the hydration cost is real but also unmeasured: no extra main-thread work, but the whole graph lands after first paint, so the hydration long task shifts into the window where first interactions happen. We have no RUM on those apps, so neither side of the tradeoff is observable in the field for us, and a change we can't falsify wasn't worth the reasoning it required at each review.

None of that argues the patch is wrong — for a content page where nothing interactive matters before paint, biasing to paint is defensible. It argues against it as an unconditional default, which is the question you're actually asking. An opt-in (or a per-route/per-asset override) would let apps make that call with knowledge of their own interactivity, and would sidestep your risks 2 and 5 entirely.

Your point 4 stands independently, and looks like the sharper bug: Early Hints call getScriptPreloadAttrs but don't carry fetchPriority into the HTTP hint, so on a browser that acts on the hint the HTML attribute arrives too late to matter.

Caveats

Priorities measured over local HTTP/1.1, headless, on a single Chrome version. Priority assignment is Blink-side so the protocol shouldn't matter, but these heuristics drift between releases. And as above, we have no field data either — this is a mechanism argument, not a measured INP delta.

@quanglam2807

Copy link
Copy Markdown

Ecosystem update relevant to your closing question about whether this should be a framework default.

React 19.3.0 shipped react/react#36835, so ReactDOM.preloadModule() now forwards fetchPriority — the ask in react/react#36834, alongside vitejs/vite-plugin-react#1264 doing the same for @vitejs/plugin-rsc client references.

Verified against the published packages, production builds, same script against both:

function App() {
  ReactDOM.preloadModule('/dep.js', { as: 'script', fetchPriority: 'low' })
  return <div>hi</div>
}
<!-- react-dom 19.2.8 -->
<link rel="modulepreload" href="/dep.js"/>
<!-- react-dom 19.3.0 -->
<link rel="modulepreload" href="/dep.js" fetchPriority="low"/>

@types/react-dom@19.3.0 hasn't caught up — PreloadModuleOptions still lists only as, crossOrigin, integrity, nonce, so it needs a cast for the moment.

Two things I take from it for this PR:

React exposed a knob rather than changing a default. It still hardcodes fetchPriority: 'low' only for its own bootstrapScripts and bootstrapModules; for everything else the priority is opt-in per call. That seems like a reasonable precedent for the question you put to the maintainers — rather than lowering all generated manifest preloads by default, expose the priority (per asset, or per app) and let applications decide. It also sidesteps risks 2 and 5, where the right answer genuinely differs between a content site and an ssr: false route.

It gives the React renderer a first-class path. If Start routed manifest preloads through ReactDOM.preloadModule() instead of JSX <link> elements, the priority would ride along with React's own resource dedupe, and Flight/RSC would pick it up for free. Not a general solution, though — Solid and Vue would still need the current markup path, so the shared getScriptPreloadAttrs helper stays either way.

It doesn't touch your point 4, which still looks like the sharpest issue in the list: Early Hints don't carry fetchPriority, so wherever a browser acts on the hint, the HTML attribute arrives too late to matter.

@TiagoGranelli

TiagoGranelli commented Sep 27, 2026 •

Copy link
Copy Markdown

Some measurements on the gap between simulated and applied throttling mentioned in the description. They come from a Start app (React 19.3, Vite 8, 10 to 15 generated modulepreloads per page, about 135 KB of brotli JS) served over HTTP/2 behind Caddy, with Lighthouse 13.5 mobile:

FCP LCP Performance
generated preloads as today (High) 1.51 s 1.51 s 99 to 100
same build, preloads with fetchpriority="low" 0.79 s 1.51 s 100
today, Chrome with applied throttling (150 ms, 1.6 Mbps, 4× CPU) 0.53 s

The FCP drop comes from how Lantern builds its FCP graphs, not from bandwidth. FirstContentfulPaint.getFirstPaintBasedGraph keeps every network request that finished before the observed (unthrottled) first paint and has render-blocking priority, and NetworkNode.hasRenderBlockingPriority() treats a Script at High as render-blocking. A modulepreload is High by default. Module evaluation appears in the trace as v8.evaluateModule without a URL, so Lantern can't tell that the script ran after the paint. On a fast local or edge server the preloads finish before the observed paint, and their bytes count against the simulated FCP. With fetchpriority="low" they are Low and drop out of the FCP graphs. The pessimistic LCP graph keeps every request that finished before the observed LCP, whatever its priority, which is why our LCP didn't move.

This matches your applied-throttling result: real FCP is about 0.5 s either way, because the browser paints long before the scripts arrive. A default change would mostly move Lighthouse's FCP, and it would cost hydration time on slow links, since every preload is needed for hydration (as noted above). I'd also go with an opt-in. For now we're keeping the default priority in our app.

@quanglam2807

Copy link
Copy Markdown

Follow-up to my comment from Sept 10, where I said we'd reverted this patch because the hydration cost was unmeasured and the applied-throttling gain was 13 ms in your write-up. I've now measured both on one of those marketing pages (71 generated preloads, about 445 KB of JS). The data changes my conclusion: for an app like ours, fetchpriority="low" is a clear win under applied throttling, and hydration doesn't move.

Setup

  • Real Chrome (not headless-shell) driven by Playwright against the production site. Only the HTML document is swapped per variant, via request interception; every JS, CSS, font and image request goes to the real CDN, same origin, over HTTP/2. The document is held back for the time it would take to arrive over the throttled link, so no variant gets it for free.
  • Three variants of the same SSR HTML snapshot: preloads as generated today, the same preloads with fetchpriority="low", and preloads removed.
  • Applied throttling through CDP, matching Lighthouse's devtools settings. Mobile: 562.5 ms RTT, 1.47 Mbps, 4× CPU, 412×823. Desktop: 40 ms RTT, 10 Mbps, 1× CPU, 1350×940.
  • Fresh context and cache disabled on every load. 5 rounds with variants interleaved, 60 loads in all; medians below, with very little spread between runs.
  • Hydrated is when $_TSR.h() runs (wrapped by an init script). Menu works comes from a second pass that clicks the header menu every 250 ms from FCP until it opens. Slowest click is the largest Event Timing duration over those clicks, the same data INP is built from.

Results

today (High) fetchpriority="low" preloads removed
Mobile FCP (= LCP) 4.44 s 2.87 s 2.86 s
Mobile last JS chunk downloaded 7.47 s 7.34 s 8.07 s
Mobile hydrated 6.71 s 6.58 s 7.33 s
Mobile menu works 7.23 s 7.03 s 7.78 s
Mobile slowest click, median (worst) 96 ms (104) 112 ms (128) 120 ms (128)
Desktop FCP 660 ms 388 ms 444 ms
Desktop hydrated 917 ms 933 ms 1,012 ms
Desktop menu works 1.32 s 1.26 s 1.47 s
Desktop slowest click 32 ms 32 ms 24 ms

What this says about my earlier reasoning

  • "The whole graph lands after first paint, so hydration shifts later": wrong, at least here. On a bandwidth-bound link the total bytes don't change, so the last chunk arrives at the same time either way. low only reorders the queue so CSS, font and the hero image go first. Removing the links does cost about 0.6 s of hydration on mobile (the discovery waterfall), which supports the point that every preload is hydration-critical. That's an argument against dropping preloads, not against lowering their priority.
  • "The 13 ms applied-throttling number should outweigh the 1.1 s simulated one": that depends on how many preload bytes there are. @TiagoGranelli's explanation of the Lantern graphs matches what I see in simulation (FCP moves, LCP doesn't). But with about 445 KB of preloads against about 140 KB of HTML, CSS and font, the contention is real, not just a simulator artifact: −1.57 s FCP on mobile and −270 ms on desktop under applied throttling. With 4 preloads (your app) or 135 KB (Tiago's), there's much less to contend with, which fits the near-zero deltas you both saw.
  • INP. No change on desktop. On mobile the slowest click goes up about 16 ms, all well under 200 ms. Part of that comes from the probe: it starts clicking at FCP, which now happens 1.5 s earlier, so more clicks land before hydration (median 15 vs 10) and the maximum is taken over more samples. The user-facing number, when the menu first works, is 0.2 s sooner.

Where that leaves the default question

I still wouldn't make it an unconditional default. The gain scales with preload bytes, and ssr: false routes (your risk 2) haven't been measured. But the case for an opt-in is stronger than I made it sound: for SSR content pages with a large preload set, it's a real paint win at no measurable hydration or INP cost. Your point 4 (Early Hints not carrying fetchPriority) would need fixing alongside any opt-in, or the attribute is moot wherever hints are sent.

Caveats: one app, one network path (Southeast Asia to a Cloudflare edge), lab rather than field data, and a single interaction probed. For anyone wanting to try this without patching: useTags() and Asset are exported, so a small HeadContent replacement can add fetchPriority: 'low' to the modulepreload tags.

@quanglam2807

Copy link
Copy Markdown

Update to my data comment above: we shipped this to production on two Start sites, and I've changed my position. I now think low should be the default, as this PR proposes, rather than an opt-in.

Production before/after, with fetchpriority="low" on the generated preloads as the only change. Lighthouse 13.5 medians:

Site / page Form Perf FCP
webcatalog.io /en/desktop mobile 47 → 66 6.9 s → 3.5 s
webcatalog.io /en/desktop desktop 71 → 90 2.6 s → 674 ms
webcatalog.io /en/apps/ometv mobile 35 → 67 6.8 s → 3.5 s
webcatalog.io /en/apps/ometv desktop 66 → 83 2.6 s → 641 ms
webcatalog.io /en/apps/country/us mobile 59 → 65 6.4 s → 3.5 s
webcatalog.io /en/apps/country/us desktop 80 → 80 1.3 s → 633 ms
lexibird.com /en/desktop mobile 61 → 67 5.5 s → 3.1 s
lexibird.com /en/desktop desktop 84 → 93 1.7 s → 817 ms

In production on mobile and desktop Chrome: no hydration errors, menus open, client-side navigation works. TBT and TTI are unchanged within noise.

What changed my mind:

  • The hydration cost I worried about in my Sept 10 comment doesn't materialize. On a bandwidth-bound link the last chunk arrives at the same time; low only reorders the queue (applied throttling: 6.71 s → 6.58 s mobile hydration).
  • "Opt-in only" leaves the gain to teams who already know to look. The cost is invisible in code, and small apps lose nothing either way.
  • It matches React's own default for bootstrap preloads, which Next.js inherits.

Two refinements I'd suggest to this PR, both detailed in #8546:

  1. Scope the default to server-rendered matches. Keep today's priority for ssr: false matches and SPA mode, where the JS is the content.
  2. Add an escape hatch (a HeadContent prop next to assetCrossOrigin, or fetchPriority on TransformAssetResult), and forward fetchPriority into Early Hints (your point 4).

The one regression we saw fits the first: on /en/apps/country/us (desktop) the LCP element is a card rendered by client JS after hydration. Simulated LCP went from 2.9 s to 3.6 s there, although observed LCP improved.

Also tracked in #8546 and discussion #8547.

@alexiurkov

Copy link
Copy Markdown

Data point for the open questions here, from a Start app (1.171, React 19, Vite 8 / rolldown) with transitive preloads, i.e. what #8520 would produce.

Rolldown gives only direct imports, so we hoisted the transitive closure ourselves (same effect as #8520): 47 → 143 modulepreloads on the home page, ~890 KB of JS. Over HTTP/2 that blanked the page on slow networks: the stylesheets share the connection with every chunk and finish together with the last one.

Chrome network emulation (Slow 4G: 150 ms, 1.6 Mbps, CPU 4x) against the real CDN over HTTP/2, median of 3:

preloads FCP CSS done first API call after hydration LCP
47 direct, default High 1.78 s 1.71 s 6.57 s 8.18 s
143 transitive, default High 4.65 s 4.58 s 6.09 s 7.69 s
143 transitive, fetchPriority="low" 0.87 s 0.80 s 6.01 s 7.66 s

So with a large preload set the applied-throttling gain is big, and low does not delay hydration on this page (all JS done at 6.47 s vs 6.56 s). Lighthouse mobile (simulated): 46 → ~61, FCP 9.1 s → 1.7 s.

If #8520 lands without this default, every rolldown app gets the blank first paint we hit. For now we rewrite { rel: "modulepreload" } in router-core at build time and would be glad to drop that.

@DylanVann

DylanVann commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

I believe that the current priority is optimal for faster hydration and isn't actually blocking / increasing FCP. This PR just landed that addresses Lighthouse's heuristic being inaccurate - GoogleChrome/lighthouse#16869

maxktz and others added 4 commits October 16, 2026 02:11
Static script hints were built from getScriptPreloadAttrs but copied only
href, rel, as and crossOrigin, so Chromium started hinted preloads at
MEDIUM before the HTML's fetchPriority="low" arrived. Build them with the
shared addEarlyHintFetchAttrs helper, which the dynamic hint path already
uses, so fetchPriority (and crossOrigin) are carried into 103 hints and the
response Link header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Solid server-suite SSR test for module and IIFE preloads and assert
fetchpriority="low" on rendered preloads in the Solid and Vue HeadContent
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mention that JavaScript Early Hints carry fetchpriority=low and extend the
changeset to start-server-core.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8606e1a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@tanstack/router-core Patch
@tanstack/start-server-core Patch
@tanstack/react-router Patch
@tanstack/react-start-client Patch
@tanstack/react-start-rsc Patch
@tanstack/react-start-server Patch
@tanstack/router-generator Patch
@tanstack/router-plugin Patch
@tanstack/solid-router Patch
@tanstack/solid-start-client Patch
@tanstack/solid-start-server Patch
@tanstack/start-client-core Patch
@tanstack/start-plugin-core Patch
@tanstack/start-storage-context Patch
@tanstack/vue-router Patch
@tanstack/vue-start-client Patch
@tanstack/vue-start-server Patch
@tanstack/react-start Patch
@tanstack/solid-start Patch
@tanstack/vue-start Patch
@tanstack/router-cli Patch
@tanstack/router-vite-plugin Patch
@tanstack/start-static-server-functions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@schiller-manuel
schiller-manuel marked this pull request as ready for review October 16, 2026 00:52
@nx-cloud

nx-cloud Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 8606e1a

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 10m 27s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2m 4s View ↗

☁️ Nx Cloud last updated this comment at 2026-10-16 01:04:57 UTC

@pkg-pr-new

pkg-pr-new Bot commented Oct 16, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@8212

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@8212

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@8212

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@8212

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@8212

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@8212

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@8212

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@8212

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@8212

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@8212

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@8212

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@8212

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@8212

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@8212

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@8212

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@8212

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@8212

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@8212

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@8212

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@8212

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@8212

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@8212

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@8212

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@8212

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@8212

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@8212

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@8212

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@8212

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@8212

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@8212

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@8212

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@8212

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@8212

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@8212

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@8212

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@8212

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@8212

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@8212

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@8212

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@8212

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@8212

commit: 8606e1a

@codspeed

codspeed Bot commented Oct 16, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

ℹ️ 31 benchmarks have no flame graph

The flamegraph could not be generated because too much time was spent in runtime internals, making the visualization not useful for performance analysis.

✅ 194 untouched benchmarks
⏩ 3 skipped benchmarks1


Comparing maxktz:fix/preload-priority (8606e1a) with main (7a06725)

Open in CodSpeed

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@schiller-manuel
schiller-manuel merged commit 18154b1 into TanStack:main Oct 16, 2026
24 of 26 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Start] Generated route preloads should default to fetchPriority="low" (they delay first paint at High)

6 participants