<link>

Declares a linked resource such as a stylesheet, preload or icon that React hoists into the document head.

Signature
<link rel="stylesheet" href="https://example.com/style.css" precedence="default" />
  • Added in React 19.0.0

Parameters

ParameterTypeDescription
$relstringThe relationship to the resource. Required. React treats rel="stylesheet" differently from every other value.
$hrefoptionalstringThe URL of the linked resource.
$precedenceoptionalstringApplies when rel="stylesheet". Ranks this node relative to other stylesheets in <head>, deciding which can override which. Values discovered first rank lower. Required for hoisting, de-duplication and Suspense.
$mediaoptionalstringApplies when rel="stylesheet". Restricts the stylesheet to a media query.
$titleoptionalstringApplies when rel="stylesheet". Names an alternative stylesheet.
$disabledoptionalbooleanDisables the stylesheet. Supplying it turns off React's special stylesheet treatment.
$onLoadoptionalfunctionCalled when the stylesheet finishes loading. Supplying it turns off hoisting and special stylesheet treatment.
$onErroroptionalfunctionCalled when the stylesheet fails to load. Supplying it turns off hoisting and special stylesheet treatment.
$asoptionalstringApplies when rel="preload" or rel="modulepreload". One of audio, document, embed, fetch, font, image, object, script, style, track, video, worker.
$imageSrcSetoptionalstringOnly when as="image". The source set of the image.
$imageSizesoptionalstringOnly when as="image". The sizes of the image.
$sizesoptionalstringApplies when rel="icon" or rel="apple-touch-icon". The sizes of the icon.
$crossOriginoptionalstringThe CORS policy to use: "anonymous" or "use-credentials". Required when as is "fetch".
$referrerPolicyoptionalstring= "no-referrer-when-downgrade"The Referer header to send when fetching. One of no-referrer-when-downgrade (the default), no-referrer, origin, origin-when-cross-origin, unsafe-url.
$fetchPriorityoptionalstring= "auto"A relative fetch priority: "auto" (the default), "high" or "low".
$hrefLangoptionalstringThe language of the linked resource.
$integrityoptionalstringA cryptographic hash of the resource, used to verify its authenticity.
$typeoptionalstringThe MIME type of the linked resource.
$blockingoptionalstringNot recommended with React. Set to "render", it tells the browser not to render the page until the stylesheet loads; Suspense gives finer control.

Return value

Renders nothing in place. React inserts the corresponding <link> node into the document <head> unless one of the hoisting exceptions applies.

Changelog

  • PHP 19.0.0<link> rendered anywhere in the tree is hoisted into the document <head>. Before 19 it rendered in place.
  • PHP 19.0.0rel="stylesheet" with a precedence prop is de-duplicated by href, and the component that renders it suspends while the stylesheet loads.

A <link> declares a resource the document needs — a stylesheet, a preload, an icon, a canonical URL — and React puts the resulting DOM node in <head> no matter how deep in the tree you wrote it. That means a route component can declare its own CSS and its own favicon without a build step editing index.html.

Stylesheets get extra machinery

With rel="stylesheet" and a precedence, three things happen: React de-duplicates by href, so N components asking for the same sheet produce one request; it orders the nodes by precedence, so overrides land after base rules; and the rendering component suspends until the sheet loads, so nothing paints unstyled. Drop precedence and you lose all three — the link is rendered in place like any other element.

What trips people up

Adding onLoad, onError or disabled to a stylesheet link opts you out entirely. React reads them as a declaration that you are managing this resource yourself, so it stops hoisting, stops de-duplicating and stops suspending. An itemProp has the same effect, since the link then describes part of the page rather than the document. Props are also frozen after the first render, so swapping href on a mounted link does nothing.

Example

Example · jsx
import { Suspense } from "react";

function ReportStyles() {
  return (
    <>
      {/* De-duplicated by href, ordered by precedence, and suspends
          this component until the sheet has loaded. */}
      <link rel="stylesheet" href="/css/report.css" precedence="default" />
      <link rel="stylesheet" href="/css/report-print.css" precedence="high" media="print" />
    </>
  );
}

function ReportTable() {
  const rows = [
    { region: "EMEA", total: 41200 },
    { region: "APAC", total: 28650 },
    { region: "AMER", total: 55310 }
  ];

  return (
    <>
      <ReportStyles />
      <table className="report">
        <thead>
          <tr><th>Region</th><th>Total</th></tr>
        </thead>
        <tbody>
          {rows.map((row) => (
            <tr key={row.region}>
              <td>{row.region}</td>
              <td>{row.total.toLocaleString("en-US")}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
}

export default function ReportPage() {
  return (
    <main>
      <link rel="icon" href="/favicon-32.png" sizes="32x32" />
      <link rel="preload" as="font" type="font/woff2" href="/fonts/inter.woff2" crossOrigin="anonymous" />
      <h1>Quarterly report</h1>
      <Suspense fallback={<p>Loading report styles...</p>}>
        <ReportTable />
      </Suspense>
    </main>
  );
}

When to use it

  • Letting a lazily loaded route declare the stylesheet it needs, deduplicated across every entry point
  • Preloading a web font or hero image before the browser discovers it in the markup
  • Setting a favicon or apple-touch-icon from the app shell component
  • Emitting a canonical or alternate-language link for the current page during a server render

Frequently asked questions

Why is my link tag not being moved into the head?

Four things disable hoisting: a rel="stylesheet" without a precedence prop, an itemProp prop, and an onLoad or onError handler. In each case React renders the link exactly where it sits in the tree. For stylesheets specifically, precedence is the switch that turns on hoisting, de-duplication and suspense together — omitting it disables all three.

How do I load CSS for one route only in React 19?

Render <link rel="stylesheet" href="..." precedence="default" /> inside the route component itself. React hoists it into <head>, de-duplicates it against any other component asking for the same href, and suspends until it loads, so the route never flashes unstyled. React may leave the node in the DOM after the route unmounts, which is harmless.

What does the precedence prop actually do?

It ranks stylesheets against each other inside <head>, which decides who wins a specificity tie. React treats precedence values it encounters first as lower and later ones as higher, and the ranking is shared across <link>, <style> and preinit. One value is enough when your rules are atomic; add a second only when a theme layer must reliably override a base layer.

More examples

De-duplication across independent components

Two sections independently declare that they need grid.css, and the browser ends up with exactly one <link href="/css/grid.css"> in <head> and one network request. React keys de-duplication on href alone, which is what makes it safe for a shared component to declare its own stylesheet: callers never have to know whether some other subtree already asked for it. Remove precedence from DataGridStyles and you get two link nodes rendered inline, one inside each <section>.

Example · jsx
function DataGridStyles() {
  return <link rel="stylesheet" href="/css/grid.css" precedence="default" />;
}

function UserGrid() {
  return (
    <section>
      <DataGridStyles />
      <h2>Users</h2>
      <div className="grid">...</div>
    </section>
  );
}

function InvoiceGrid() {
  return (
    <section>
      <DataGridStyles />
      <h2>Invoices</h2>
      <div className="grid">...</div>
    </section>
  );
}

export default function Dashboard() {
  return (
    <main>
      <UserGrid />
      <InvoiceGrid />
    </main>
  );
}

Opting out to manage loading yourself

Because onLoad and onError are present, React treats the resource as yours and skips every special behaviour: the node is not moved to <head>, not de-duplicated against other links to the same href, and the component does not suspend. The paragraph reports idle, then loaded or failed. This is the escape hatch for an optional stylesheet whose failure should degrade gracefully rather than break a Suspense boundary.

Example · jsx
import { useState } from "react";

export default function OptionalChartTheme() {
  const [status, setStatus] = useState("idle");

  return (
    <div>
      {/* onLoad/onError disable hoisting, de-duplication and suspense.
          This link stays right here in the DOM. */}
      <link
        rel="stylesheet"
        href="/css/chart-theme.css"
        onLoad={() => setStatus("loaded")}
        onError={() => setStatus("failed")}
      />
      <p>Chart theme: {status}</p>
      {status === "failed" && <p>Falling back to the default palette.</p>}
    </div>
  );
}

Preloading what the parser will not find in time

Both links are hoisted into <head>, where the browser's preload scanner sees them before it has parsed down to the <img>. imageSrcSet and imageSizes must mirror the img exactly, or the preload fetches a different candidate and you pay for two downloads instead of one. Note the crossOrigin on the preconnect: for fonts and any as="fetch" preload, a mismatched CORS mode also produces a duplicate request.

Example · jsx
export default function ProductHero() {
  const hero = {
    src: "/img/product-1200.avif",
    srcSet: "/img/product-600.avif 600w, /img/product-1200.avif 1200w",
    sizes: "(max-width: 700px) 100vw, 1200px"
  };

  return (
    <section>
      <link
        rel="preload"
        as="image"
        href={hero.src}
        imageSrcSet={hero.srcSet}
        imageSizes={hero.sizes}
        fetchPriority="high"
      />
      <link rel="preconnect" href="https://cdn.example.com" crossOrigin="anonymous" />
      <img src={hero.src} srcSet={hero.srcSet} sizes={hero.sizes} alt="Product" />
    </section>
  );
}

Discussion

  • Be the first to comment on this lesson.