How to build <link rel=preload> tags for your web fonts
- Step 1Find the real URL of your critical font — Open the page in DevTools → Network → filter Font, reload, and note the **WOFF2** the LCP text uses. That exact path (e.g. `/fonts/inter-latin-400.woff2` or a CDN URL) is what you paste. Preloading a path the browser never requests does nothing — the hint must match the eventual `@font-face` fetch byte-for-byte.
- Step 2Paste one URL per line into the textarea — The single input is a textarea labelled **Font URLs (one per line)**. Each non-empty line becomes one tag; leading/trailing spaces are trimmed and blank lines are skipped. Typical input is one or two URLs — the WOFF2 for your hero/brand font and, if it differs, the body font on the LCP path.
- Step 3Generate — Click **Generate**. There is no file upload step for this tool — it is generative, so the drop zone is hidden and only the textarea shows. The result panel appears with the HTML and a `Tags emitted` count equal to the number of non-empty URLs.
- Step 4Copy the output — Use **Copy to clipboard** or **Download HTML** (`preload-tags.html`). The fragment begins with a comment: `Add inside <head>, BEFORE the stylesheet that uses these fonts. crossorigin is required for browsers to reuse the preloaded font.`
- Step 5Paste into <head> before your stylesheet — Place the tags high in `<head>`, **above** the `<link rel="stylesheet">` that declares the `@font-face`. Order matters for discovery: the preload should be encountered by the preload scanner as early as possible, before render-blocking CSS.
- Step 6Verify in DevTools and keep the list short — Reload with DevTools open. The preloaded font should show in the Network tab early, and you should NOT see Chrome's console warning `The resource ... was preloaded ... but not used`. If you do, the URL didn't match the `@font-face` fetch (often a `crossorigin` or path mismatch). Keep the list to 1–2 critical fonts — the tool emits exactly what you paste, with no cap and no warning, so restraint is on you.
Extension → emitted type= value
The generator reads only the file extension after the last dot in the URL and lower-cases it. Anything that is not woff2/woff/otf maps to font/ttf — including .ttf itself, but also no-extension URLs and any path where the extension is masked by a query string.
| URL you paste | Detected extension | Emitted `type=` | Correct? |
|---|---|---|---|
/fonts/inter.woff2 | woff2 | font/woff2 | Yes — the format you should be preloading |
/fonts/legacy.woff | woff | font/woff | Yes |
/fonts/brand.otf | otf | font/otf | Yes (but ship WOFF2 — OTF is ~2× the bytes) |
/fonts/brand.ttf | ttf | font/ttf | Yes (TTF is the fallback branch; prefer WOFF2) |
/fonts/inter.woff2?v=9d38c18 | woff2?v=9d38c18 | font/ttf | Wrong — the query string masks the extension; strip the ?v= or use a clean URL |
https://cdn.example.com/f/abc123 | (none) | font/ttf | Wrong if the bytes are WOFF2 — extensionless CDN URLs default to font/ttf; the hint mismatch wastes the preload |
What the tool emits vs. what it does NOT
The generator produces rel="preload" tags only. It does not add prefetch, preconnect, media, fetchpriority, or integrity attributes, and it has no URL-count cap or warning. Where you need those, use the matching tool or hand-edit.
| Capability | In this tool? | Where it lives |
|---|---|---|
<link rel="preload" as="font" type type href crossorigin> | Yes — the only output | This generator, one tag per URL |
rel="prefetch" variant for next-page fonts | No | Hand-edit preload → prefetch; see preload vs prefetch vs preconnect |
rel="preconnect" to a font CDN host | No | Hand-write per host; rationale in the comparison guide |
media, fetchpriority, integrity attributes | No | Add by hand; reference in the attributes guide |
| Warning when you paste many URLs | No — no cap, no warning | Discipline is on you; preload 1–2 critical fonts |
Generate the matching @font-face | No | font-face-generator |
Cookbook
Real input → real output from the generator. The leading HTML comment is part of every result; it is shown once here and elided in later examples for brevity.
One critical WOFF2
ExampleThe 90% case: preload the single WOFF2 your above-the-fold text renders in. One line in, one tag out, with the full comment header.
Input (textarea):
/fonts/inter-latin-400.woff2
Output:
<!-- Add inside <head>, BEFORE the stylesheet that uses these fonts.
crossorigin is required for browsers to reuse the preloaded font. -->
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter-latin-400.woff2" crossorigin>Two weights, mixed formats
ExampleEach line is handled independently; the type= value tracks each URL's own extension. Here a WOFF2 and a legacy WOFF produce two correctly-typed tags.
Input: /fonts/inter-400.woff2 /fonts/inter-700.woff Output (comment elided): <link rel="preload" as="font" type="font/woff2" href="/fonts/inter-400.woff2" crossorigin> <link rel="preload" as="font" type="font/woff" href="/fonts/inter-700.woff" crossorigin>
Blank lines and stray spaces are cleaned
ExampleLines are trimmed and empties dropped, so a path list pasted out of a spreadsheet or a multi-line clipboard works without manual cleanup. The Tags emitted metric reflects the cleaned count.
Input (note leading spaces + blank line): /fonts/brand.woff2 /fonts/brand-italic.woff2 Output: <link rel="preload" as="font" type="font/woff2" href="/fonts/brand.woff2" crossorigin> <link rel="preload" as="font" type="font/woff2" href="/fonts/brand-italic.woff2" crossorigin> Tags emitted: 2
Cache-busting query string defaults to font/ttf
ExampleThe detector splits on the last dot and lower-cases the tail, so a ?v= suffix becomes part of the 'extension' and falls through to the font/ttf branch. The fix is to preload a clean URL.
Input: /fonts/inter.woff2?v=9d38c18 Output (note WRONG type): <link rel="preload" as="font" type="font/ttf" href="/fonts/inter.woff2?v=9d38c18" crossorigin> Fix — paste the path without the query string: /fonts/inter.woff2 → type="font/woff2"
Absolute CDN URL with a real extension
ExampleCross-origin URLs work the same way — the extension drives the type, and crossorigin is still emitted (it is mandatory for cross-origin font fetches). The host itself is not preconnected; that is a separate hint.
Input: https://cdn.example.com/fonts/space-grotesk.woff2 Output: <link rel="preload" as="font" type="font/woff2" href="https://cdn.example.com/fonts/space-grotesk.woff2" crossorigin> For the handshake to that host, hand-add (not emitted by this tool): <link rel="preconnect" href="https://cdn.example.com" crossorigin>
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
All lines empty / only whitespace
Error: Enter at least one font URL.If every line is blank after trimming, the generator throws Enter at least one font URL. and produces no output. This is the only hard validation — once at least one non-empty line exists, it emits a tag for it regardless of whether the URL is reachable or even well-formed.
Query string or fragment after the filename
By design — falls back to font/ttfExtension detection is url.split(".").pop() lower-cased — it does not strip ?query or #fragment first. So /inter.woff2?v=1 is detected as the extension woff2?v=1, which matches none of woff2/woff/otf, and the tag gets type="font/ttf". The href still contains your full URL (so the fetch works), but the wrong type can cause the browser to skip the preload it can't parse as that type. Strip the query string from the line you paste.
Extensionless CDN URL (hash-named asset)
By design — defaults to font/ttfContent-addressed CDN URLs like /f/abc123 have no extension, so pop() returns the whole last path segment, which is not a known font extension → type="font/ttf". If the bytes are actually WOFF2, the type mismatch wastes the hint. Either preload a URL that keeps its .woff2 extension, or paste it here and then hand-edit the emitted type=.
Uppercase extension (`/Brand.WOFF2`)
SupportedThe detector lower-cases the extension before matching, so /Brand.WOFF2, /x.Woff, and /y.OTF all resolve to the correct font/woff2, font/woff, font/otf. Casing of the rest of the path is preserved verbatim in href.
You paste 10 URLs expecting a warning
By design — no cap, no warningUnlike some advice you may have read, this tool does not warn at 3+ URLs and has no maximum. It emits exactly one tag per non-empty line. Preloading many fonts competes for the first bytes and can regress LCP, so the restraint is yours to apply — preload only the 1–2 fonts on the LCP path.
Missing crossorigin would break reuse — but the tool always adds it
Preserved — crossorigin is unconditionalEvery emitted tag includes the bare crossorigin keyword (equivalent to crossorigin="anonymous"). Fonts are always fetched in CORS mode by @font-face, so a preload without crossorigin is treated as a different request and the browser fetches the font twice. Because the tool hard-codes it, you can't accidentally omit it from generated tags — only by hand-editing them out.
Output is HTML and renders in the result preview
ExpectedThe result type is html, so the preview panel injects the fragment via the HTML render path rather than showing escaped source. The Copy and Download buttons still give you the raw text (preload-tags.html). If you want to read the literal tags, use Copy — the inline preview is the rendered form.
Preloaded but not used (Chrome warning)
Warning — URL mismatch, not a tool bugIf Chrome logs The resource <url> was preloaded using link preload but not used within a few seconds, the preloaded URL did not match an actual font fetch. Common causes: the path differs from the @font-face src (trailing slash, casing, query string), or the font isn't requested above the fold after all. The generator emits exactly what you paste — re-check the URL against the DevTools Network entry for the real font request.
Frequently asked questions
Does this tool upload my font?
No. It is generative — you paste URLs, not files. There is no font upload at all; the file drop zone is hidden for this tool, and the result badge reads 0 bytes uploaded. The only thing it processes is the text in the URL textarea, in your browser.
How does it know the type= value?
From the file extension after the last dot in each URL, lower-cased: .woff2 → font/woff2, .woff → font/woff, .otf → font/otf. Everything else — including .ttf, extensionless URLs, and URLs whose extension is hidden behind a ?query — falls back to font/ttf. It does not fetch or sniff the bytes; detection is purely string-based on the URL.
Why is `crossorigin` on every tag, even for same-origin fonts?
Because @font-face always fetches fonts in CORS mode, even same-origin. A preload must use the same mode to be reused — <link rel="preload" as="font"> without crossorigin is treated as a separate request, and the browser downloads the font twice. The tool adds the bare crossorigin keyword (= crossorigin="anonymous") unconditionally, which is correct for both same-origin and cross-origin fonts.
Does it emit a prefetch tag too?
No. The earlier description of this tool mentioned a prefetch variant, but the implementation emits only rel="preload" tags. For next-page fonts you'd hand-edit preload to prefetch; see preload vs prefetch vs preconnect for when that's worth it.
Will it warn me if I paste too many URLs?
No. There is no URL cap and no warning — it emits one tag per non-empty line. Preloading more than 1–2 fonts can hurt LCP by competing for early bytes, so keeping the list short is up to you. The Core Web Vitals strategy guide explains how to pick which font(s) to preload.
Where exactly do I paste the output?
Inside <head>, above the <link rel="stylesheet"> that declares the @font-face. The emitted comment says exactly this. Placing it early lets the preload scanner discover the font before render-blocking CSS, which is the whole point — the fetch starts in parallel with HTML parsing.
What about `media`, `fetchpriority`, or `integrity`?
The tool doesn't add them — it emits rel, as, type, href, and crossorigin only. If you need conditional preloading (media), an explicit priority boost (fetchpriority), or Subresource Integrity (integrity), add them by hand. The attributes reference documents each.
Should I preload my OTF or TTF?
Preferably neither — preload the WOFF2. OTF/TTF are roughly twice the bytes of the equivalent WOFF2, so you'd be spending early bandwidth on a larger file. Convert with ttf-to-woff2 first, then preload the WOFF2 URL. The tool will happily emit font/otf / font/ttf tags, but that's rarely what you want for performance.
My font URL has a cache-busting hash like `?v=abc`. Is that a problem?
For the type= value, yes. The detector treats the ?v=abc suffix as part of the extension, so the tag gets font/ttf even for a WOFF2. The href is still correct so the file loads, but the wrong type can make the browser skip the preload. Paste the URL without the query string, then add the cache-buster back to the href by hand if your build needs it.
Does preload help returning visitors?
Marginally. The large win is the first, cold-cache visit, where preloading starts the font fetch alongside the HTML parse and removes the FOIT/swap delay — typically 100–500 ms off LCP on slow connections. Returning visitors usually hit the disk cache regardless, so the hint mostly reuses the cached file a touch earlier.
Can I run this from a build script instead of the UI?
Yes — the engine is the same one behind the UI. With the JAD runner paired, POST http://127.0.0.1:9789/v1/tools/preload-tag-builder/run with { "preloadUrls": "/fonts/a.woff2\n/fonts/b.woff2" } returns the same HTML. For a full CI pattern that keeps the tags in sync with content-hashed filenames, see auto-generate preload tags in CI.
What's the next step after generating the tags?
Make sure the matching @font-face exists and uses the same URL — generate it with font-face-generator. If you're unsure which format a URL points at, check it with font-format-identifier. And pick a sensible font-display so the fallback shows immediately while the preloaded font arrives — font-display-strategy recommends a value per use case.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.