Rooks
HooksExperimental Hooks

useSuspenseFavicon

Loads a favicon and suspends the component until the resource is ready.

About

A Suspense-enabled hook for reading and updating the current document favicon. It resolves the current explicit favicon from the DOM, classifies it as same-origin or external, and returns controls for switching to a new favicon using an explicit config object.

If the document does not declare a favicon link, the current value is null.

Examples

Basic Usage

import React, { Suspense } from "react";
import { useSuspenseFavicon } from "rooks/experimental";

function FaviconPreview() {
  const [favicon, { updateFaviconURL }] = useSuspenseFavicon();

  return (
    <div>
      <pre>{JSON.stringify(favicon, null, 2)}</pre>

      <button
        onClick={() =>
          updateFaviconURL({
            kind: "same-origin",
            relativeHref: "/favicon-light.svg?v=2",
          })
        }
      >
        Use same-origin favicon
      </button>

      <button
        onClick={() =>
          updateFaviconURL({
            kind: "external",
            url: "https://cdn.example.com/brand/favicon-dark.ico",
          })
        }
      >
        Use external favicon
      </button>
    </div>
  );
}

export default function App() {
  return (
    <Suspense fallback={<div>Reading favicon...</div>}>
      <FaviconPreview />
    </Suspense>
  );
}

Same-origin Updates With Cache-busting Query Strings

import React, { Suspense } from "react";
import { useSuspenseFavicon } from "rooks/experimental";

function BrandSwitcher() {
  const [favicon, { updateFaviconURL }] = useSuspenseFavicon();

  return (
    <div>
      <p>Current kind: {favicon?.kind ?? "none"}</p>

      <button
        onClick={() =>
          updateFaviconURL({
            kind: "same-origin",
            relativeHref: "/favicons/workspace-a.svg?v=12",
          })
        }
      >
        Workspace A
      </button>

      <button
        onClick={() =>
          updateFaviconURL({
            kind: "same-origin",
            relativeHref: "../favicons/workspace-b.svg?v=3#active",
          })
        }
      >
        Workspace B
      </button>
    </div>
  );
}

export default function App() {
  return (
    <Suspense fallback={<div>Loading favicon state...</div>}>
      <BrandSwitcher />
    </Suspense>
  );
}

Persisting The Managed Favicon After Unmount

import React, { Suspense, useState } from "react";
import { useSuspenseFavicon } from "rooks/experimental";

function ManagedFavicon() {
  const [, { updateFaviconURL }] = useSuspenseFavicon({
    unmountStrategy: "leave-as-is",
  });

  return (
    <button
      onClick={() =>
        updateFaviconURL({
          kind: "external",
          url: "https://cdn.example.com/persistent/favicon.ico",
        })
      }
    >
      Apply persistent favicon
    </button>
  );
}

export default function App() {
  const [showManager, setShowManager] = useState(true);

  return (
    <Suspense fallback={<div>Preparing favicon manager...</div>}>
      <button onClick={() => setShowManager((current) => !current)}>
        Toggle hook
      </button>
      {showManager ? <ManagedFavicon /> : null}
    </Suspense>
  );
}

Parameters

options

PropertyTypeDefaultDescription
unmountStrategy"restore-originals" | "leave-as-is""restore-originals"Controls what happens when the last mounted useSuspenseFavicon instance unmounts.

Return value

The hook returns a tuple with two items:

[
  currentFavicon,
  {
    updateFaviconURL,
  },
]

currentFavicon

ShapeDescription
nullNo explicit favicon link was found during initialization.
{ kind: "same-origin", relativeHref, href }The current favicon resolves to the same origin as the current page. relativeHref is the same-origin value you can reuse with updateFaviconURL.
{ kind: "external", url, href }The current favicon resolves to a different origin.

controls.updateFaviconURL

updateFaviconURL(
  | { kind: "same-origin"; relativeHref: string }
  | { kind: "external"; url: string }
): void

Config Rules

  • same-origin updates must use a relative href string such as /favicon.svg?v=2 or ../icons/favicon.svg.
  • external updates must resolve to a different origin than the current page.
  • Invalid configs throw synchronously with a descriptive error.

Notes

  • The hook suspends only while it discovers the current explicit favicon from the DOM.
  • When multiple favicon links are present, the last explicit rel~="icon" link in DOM order is treated as current.
  • The hook does not observe arbitrary third-party favicon mutations after mount. It tracks the initial DOM state plus updates made through updateFaviconURL.

Behavior and lifecycle

Initial discovery suspends once while the shared resource finds the last valid <link rel="icon">. The first update snapshots and removes the original icon links, then all mounted instances share one managed link and receive subsequent updates. After the last instance unmounts, "restore-originals" removes the managed link and restores the snapshot; "leave-as-is" keeps the current DOM link and discards the snapshot.

Compatibility and accessibility

The hook throws outside a browser or when document.head is unavailable, so place it behind a client boundary as well as Suspense and an error boundary. Same-origin updates reject empty, absolute, protocol-relative, or cross-origin values; external updates reject empty or same-origin URLs. A favicon is decorative and must not be the only indication of unread, error, or status state.

On this page