How HTTP Works

Response header

< Link: </style.css>; rel=preload; as=style

Link Header

Learn how the Link header provides resource hints and enables preloading of CSS, fonts, and scripts to improve page load performance and user experience.

Reviewed 6 min readintermediate2 sourcesTry itMarkdown
Direction
Response
Category
Linking
Spec
RFC 8288 §3
On this page

TL;DR: Link is the HTTP-header version of HTML’s <link> element. Send Link: </app.css>; rel=preload; as=style to start a stylesheet download early (you still need the stylesheet link in your HTML to apply it), or rel="next" to point API clients at the next page.

RFC 8288 defines how to write typed links as HTTP fields. Each link is a target URL in angle brackets followed by parameters such as rel. What a browser does with it depends on the relation type; a URL by itself does nothing.

For example, rel="next" tells an API client that the target is the next page of the collection. Note the brackets: Link: /items?page=2 without them isn’t valid syntax.

Headers and URLs on this page are examples.

Link: </app.css>; rel=preload; as=style

The browser can start fetching /app.css before it finds the reference in the document. The HTML still has to use it, for example with <link rel="stylesheet" href="/app.css">. MDN explains preload consumption.

Syntax

Link: </app.css>; rel=preload; as=style, </items?page=2>; rel="next"

Angle brackets wrap each target and commas separate links. Relative targets resolve against the response URL. Use a proper parser rather than splitting on commas, since quoted parameter values can contain commas too.

Resource Hints

preload fetches a resource this document needs; preconnect opens a connection to an origin ahead of time. Both are hints. The browser decides whether to act and at what priority, and you’ll only know it helped by measuring.

Not every HTML rel works in a header. MDN’s Link reference lists preload and preconnect as the relations browsers reliably support there. Test anything else in the browsers you care about.

A connection hint targets an origin rather than a particular CSS file:

Link: <https://static.example.com>; rel=preconnect

Point it at an origin the page really contacts; a warm connection nobody uses is wasted work. as belongs on preloads, which name a specific resource, not on connection hints.

Resource Types (as parameter)

Use as=style, as=script, as=image, or as=font to match the consuming request’s destination. For a font, match the CORS mode too:

Link: </fonts/site.woff2>; rel=preload; as=font; type="font/woff2"; crossorigin

Font preloads need crossorigin even when the font is same-origin, as MDN’s preload guide explains.

Pagination

An API can advertise another page:

Link: </items?page=2>; rel="next", </items?page=10>; rel="last"

The API client decides whether to follow these. A browser won’t change pages because of them.

Build the links from the data you actually have. This route goes in an existing Express app:

const items = ['alpha', 'beta', 'gamma']
app.get('/items', (req, res) => {
  const page = Number(req.query.page ?? 1)
  if (!Number.isInteger(page) || page < 1 || page > items.length) {
    return res.status(400).json({ error: 'Invalid page' })
  }
  const links = []
  if (page > 1) links.push(`</items?page=${page - 1}>; rel="prev"`)
  if (page < items.length) links.push(`</items?page=${page + 1}>; rel="next"`)
  if (links.length) res.set('Link', links.join(', '))
  res.json({ items: [items[page - 1]] })
})

The fixture serves one item per page, so page three has no next link. Clients should follow whatever links the server sends instead of assuming every collection has a fixed last page.

Send several Link fields or one comma-separated list; they mean the same thing. Check the final response, because the app server and the reverse proxy may each be adding values.

In Express, use append if another handler may already have set links:

res.append('Link', '</app.css>; rel=preload; as=style')
res.append('Link', '</fonts/site.woff2>; rel=preload; as=font; crossorigin')

A later res.set('Link', ...) replaces those values. Express documents that distinction; check middleware order when only one of your hints reaches the client.

Real-World Scenarios

HTML responses preload stylesheets. API responses carry pagination. Non-HTML documents such as PDFs can name their canonical URL with rel="canonical"; Google documents canonical HTTP links.

For a PDF that duplicates an HTML publication, the response can advertise the preferred URL:

Link: <https://example.com/publications/report>; rel="canonical"

Point it at the URL of the equivalent publication, not a generic landing page. Canonical is a signal for search engines; the person reading the PDF never gets redirected.

Server Implementation

Add this inside the nginx location that serves the HTML needing /app.css:

add_header Link '</app.css>; rel=preload; as=style';

add_header adds it only to common success and redirect statuses (200, 201, 204, 206, 301–304, 307, 308); add always to include other status codes. Keep the hint scoped to documents that actually use the resource.

With Apache mod_headers loaded, scope a preload to HTML documents that need it:

<FilesMatch "\.html$">
    Header add Link "</app.css>; rel=preload; as=style"
</FilesMatch>

Header add adds a field rather than replacing an existing one. If only some pages use this stylesheet, narrow the match.

For Caddy serving a static site where the root page needs that CSS:

example.com {
    root * /srv/www
    header / +Link "</app.css>; rel=preload; as=style"
    file_server
}

Caddy’s + prefix appends a header value. The / matcher limits the hint to the root request, so asset responses don’t get it.

HTML Equivalent

<link rel="preload" href="/app.css" as="style" />
<link rel="stylesheet" href="/app.css" />

They do different jobs: preload fetches, stylesheet applies. A preload header can replace the first line, but you always need the second.

Match the preload’s URL, as destination, and credentials mode to the request that will use it. Get any of them wrong and you get an unused preload, a second fetch, or both. Preload only what the current page needs, not every route’s assets.

A common slip: preloading /app.css while the page links /app.css?v=2. Those are different URLs, so the preload goes to waste. Use exactly the same URL, version query included, and recheck whenever asset versioning changes while a server-level header still points at the old file.

Performance Impact

Measure the waterfall before and after adding a hint. A 103 Early Hints response can deliver Link before the final response headers are ready; see MDN’s 103 reference. How much time you save depends entirely on your page.

In DevTools, compare when the hinted request starts against the document request and the element that later uses it. Keep the cache state the same between runs. A preload that starts earlier but goes unused is a cost, not a win. Also check whether it’s stealing bandwidth from other resources the page needs before you keep it.

Common Issues

If a hinted resource is fetched twice, compare URL and CORS mode with the consuming request. If a stylesheet is fetched but never applied, check that a stylesheet link still exists in the HTML.

Use a GET to inspect the field actually sent for a document:

curl -sS -D - -o /dev/null https://example.com/

If a font is requested twice, compare the preload’s crossorigin setting with the font request. If the header is missing, check the response status and where the server directive applies. For a cross-origin API, JavaScript can read Link only if the response exposes it, for example with Access-Control-Expose-Headers: Link.

Browser Support

A browser that supports the Link header still might not support every relation in it. In Network → document → Response Headers, first confirm the server sent the hint, then find the request for its target and check the initiator. Compatibility tables for HTML <link> don’t tell you how the header behaves, so test the exact header in the browsers you support.

Frequently asked questions

What is the Link header?

Link provides metadata about related resources, similar to HTML link elements. Common uses include preloading resources, pagination links, and canonical URLs.

How do I preload resources with Link?

Send Link: </style.css>; rel=preload; as=style so the browser starts fetching early. Keep the normal stylesheet link in your HTML, because preload only fetches, and measure the waterfall to confirm the hint actually helps.

Can I use multiple Link headers?

Yes. Send repeated Link fields or combine links with commas. For preloads, specify the matching destination, such as as=style for CSS or as=script for JavaScript.

What is the difference between preload and prefetch?

preload is for resources the current page needs; prefetch is for resources a later navigation will probably need. In an HTTP Link header, browsers reliably act on preload and preconnect, so test any other relation before relying on it.

Sources

  1. MDN Web Docs: Linkdeveloper.mozilla.org
  2. RFC 8288: Web Linkingrfc-editor.org

Keep going

Browse /search