Skip to main content

links

Defines <link> tags to be inserted into the document <head> for a route. Commonly used for stylesheets, favicons, and other resource hints.

Signature

export function links(): LinkDescriptor[]
return
LinkDescriptor[]
An array of link descriptor objects. Each descriptor becomes a <link> element in the document head.

Basic Example

import type { LinksFunction } from "react-router";
import stylesUrl from "./styles.css?url";

export const links: LinksFunction = () => {
  return [
    { rel: "stylesheet", href: stylesUrl },
  ];
};

export default function Component() {
  // Route rendering
}

Multiple Stylesheets

import appStyles from "./app.css?url";
import componentStyles from "./component.css?url";

export const links: LinksFunction = () => {
  return [
    { rel: "stylesheet", href: appStyles },
    { rel: "stylesheet", href: componentStyles },
  ];
};

External Resources

export const links: LinksFunction = () => {
  return [
    {
      rel: "stylesheet",
      href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap",
    },
  ];
};

Prefetch and Preload

export const links: LinksFunction = () => {
  return [
    // Preload critical resources
    {
      rel: "preload",
      href: "/fonts/inter-var.woff2",
      as: "font",
      type: "font/woff2",
      crossOrigin: "anonymous",
    },
    // Prefetch likely next page
    {
      rel: "prefetch",
      href: "/api/products",
      as: "fetch",
    },
    // DNS prefetch for external domains
    {
      rel: "dns-prefetch",
      href: "https://api.example.com",
    },
    // Preconnect to external origins
    {
      rel: "preconnect",
      href: "https://fonts.googleapis.com",
    },
  ];
};

Favicons and Icons

export const links: LinksFunction = () => {
  return [
    {
      rel: "icon",
      href: "/favicon.ico",
      sizes: "any",
    },
    {
      rel: "icon",
      href: "/icon.svg",
      type: "image/svg+xml",
    },
    {
      rel: "apple-touch-icon",
      href: "/apple-touch-icon.png",
    },
  ];
};

Manifest and Theme

export const links: LinksFunction = () => {
  return [
    {
      rel: "manifest",
      href: "/site.webmanifest",
    },
    {
      rel: "mask-icon",
      href: "/safari-pinned-tab.svg",
      color: "#5bbad5",
    },
  ];
};
export const links: LinksFunction = () => {
  return [
    {
      rel: "alternate",
      type: "application/rss+xml",
      href: "/feed.xml",
      title: "Blog RSS Feed",
    },
    {
      rel: "alternate",
      hrefLang: "es",
      href: "https://example.com/es",
    },
  ];
};

Canonical URLs

export const links: LinksFunction = () => {
  return [
    {
      rel: "canonical",
      href: "https://example.com/products/best-product",
    },
  ];
};

Media Queries

import lightStyles from "./light.css?url";
import darkStyles from "./dark.css?url";
import printStyles from "./print.css?url";

export const links: LinksFunction = () => {
  return [
    {
      rel: "stylesheet",
      href: lightStyles,
      media: "(prefers-color-scheme: light)",
    },
    {
      rel: "stylesheet",
      href: darkStyles,
      media: "(prefers-color-scheme: dark)",
    },
    {
      rel: "stylesheet",
      href: printStyles,
      media: "print",
    },
  ];
};

TypeScript Type

import type { LinkDescriptor } from "react-router";

// LinkDescriptor has these properties:
type LinkDescriptor = {
  rel: string;
  href: string;
  as?: string;
  type?: string;
  media?: string;
  integrity?: string;
  crossOrigin?: "anonymous" | "use-credentials";
  referrerPolicy?: ReferrerPolicy;
  sizes?: string;
  title?: string;
  color?: string;
  hrefLang?: string;
  imageSrcSet?: string;
  imageSizes?: string;
  imagesrcset?: string;
  imagesizes?: string;
};

Combining with Parent Routes

// Links are automatically merged from all matching routes:

// app/root.tsx
export const links: LinksFunction = () => {
  return [
    { rel: "stylesheet", href: globalStyles },
  ];
};

// app/routes/dashboard.tsx
export const links: LinksFunction = () => {
  return [
    { rel: "stylesheet", href: dashboardStyles },
  ];
};

// Result for /dashboard: both globalStyles and dashboardStyles are included
// Links cannot access loader data, but you can conditionally include links:

const isDevelopment = process.env.NODE_ENV === "development";

export const links: LinksFunction = () => {
  const links: LinkDescriptor[] = [
    { rel: "stylesheet", href: appStyles },
  ];

  if (isDevelopment) {
    links.push({
      rel: "stylesheet",
      href: debugStyles,
    });
  }

  return links;
};

Best Practices

Import CSS files with the ?url suffix to get the URL:
// ✅ Correct
import styles from "./styles.css?url";
export const links = () => [{ rel: "stylesheet", href: styles }];

// ❌ Wrong - imports CSS module
import styles from "./styles.css";
Use preload for resources needed immediately:
export const links: LinksFunction = () => {
  return [
    // Preload hero image
    {
      rel: "preload",
      href: "/images/hero.webp",
      as: "image",
      type: "image/webp",
    },
    // Preload critical font
    {
      rel: "preload",
      href: "/fonts/heading.woff2",
      as: "font",
      type: "font/woff2",
      crossOrigin: "anonymous",
    },
  ];
};
Only include styles needed for the current route:
// ✅ Good - each route has its own styles
// app/routes/products.tsx
export const links = () => [{ rel: "stylesheet", href: productStyles }];

// app/routes/checkout.tsx  
export const links = () => [{ rel: "stylesheet", href: checkoutStyles }];

// ❌ Bad - loading all styles everywhere
// app/root.tsx
export const links = () => [
  { rel: "stylesheet", href: productStyles },
  { rel: "stylesheet", href: checkoutStyles },
  // ... many more
];

See Also

Build docs developers (and LLMs) love