Skip to main content

Self-host web fonts on Shopify CDN

When a typeface isn't available in the Shopify font library, self-host it on the Shopify CDN instead of loading it from an external service such as Google Fonts. This eliminates connection overhead and multiple requests.


Self-hosting fonts eliminates external domain connections, uses automatic Shopify CDN delivery, provides automatic version cache busting through asset_url, enables faster font discovery and download, works with Early Hints through Liquid filters, and removes one external dependency.

  • Self-hosted fonts: eliminate external connections. Fastest approach.
  • Optimized Google Fonts: reduces connection overhead with preconnect and async loading.
  • Unoptimized Google Fonts: multiple separate connections. Slowest approach.

If the typeface you need is in the Shopify font library, then reach for a font_picker setting with the font_face and font_url filters first. Those filters serve the font from the Shopify CDN, normally under /cdn/fonts/ on the domain that served the page, so they avoid the external connection for the same reason self-hosting does. They also let merchants change the font in the theme editor without a code change, and they cover the bold and italic variants through font_modify. Shopify's own themes, including Dawn, use this approach.

Self-host when the font filters can't do the job:

  • The typeface isn't in the Shopify font library, such as a licensed brand font.
  • You need a subset, variable axis, or Unicode range that the library's files don't cover.
  • The typeface is fixed by brand guidelines, so it shouldn't be a merchant-editable setting.

If none of those apply, then the font filters are the lower-maintenance option and reach the same result.


Anchor to Step 1: Download font filesStep 1: Download font files

Use Google Webfonts Helper to download Google Fonts. This tool provides WOFF2 and WOFF files for modern browsers, and CSS snippets ready to use. You need WOFF2 as the primary format for best compression, and WOFF as the fallback for older browsers. Skip EOT, TTF, and SVG, because modern browsers don't need them.

Anchor to Step 2: Add fonts to theme assetsStep 2: Add fonts to theme assets

Place downloaded font files in the /assets directory:

theme/
assets/
montserrat-v31-latin-regular.woff2
montserrat-v31-latin-regular.woff
montserrat-v31-latin-700.woff2
montserrat-v31-latin-700.woff

Create inline styles in your layout file, such as theme.liquid, or in a .css.liquid file:

layout/theme.liquid

<style>
/* montserrat-regular - latin */
@font-face {
font-display: swap;
font-family: 'Montserrat';
font-style: normal;
font-weight: 400;
src: url('{{ "montserrat-v31-latin-regular.woff2" | asset_url }}') format('woff2'),
url('{{ "montserrat-v31-latin-regular.woff" | asset_url }}') format('woff');
}

/* montserrat-700 - latin */
@font-face {
font-display: swap;
font-family: 'Montserrat';
font-style: normal;
font-weight: 700;
src: url('{{ "montserrat-v31-latin-700.woff2" | asset_url }}') format('woff2'),
url('{{ "montserrat-v31-latin-700.woff" | asset_url }}') format('woff');
}
</style>

Defining fonts inline in the <head> ensures that they're discovered immediately, which is faster than loading an external CSS file first. The asset_url filter provides automatic cache busting through version numbers.

Anchor to Step 4: Use the fontStep 4: Use the font

Reference the font family in your CSS:

body {
font-family: "Montserrat", system-ui, -apple-system, sans-serif;
}

Always provide fallback fonts in case the web font fails to load.

Anchor to Step 5: Preload critical fontsStep 5: Preload critical fonts

For fonts used for content visible in the initial viewport, preload them for faster rendering:

{%- comment -%} Preload primary font {%- endcomment -%}
{{ 'montserrat-v31-latin-regular.woff2' | asset_url | preload_tag: as: 'font', type: 'font/woff2' }}

<style>
@font-face {
font-display: swap;
font-family: 'Montserrat';
src: url('{{ "montserrat-v31-latin-regular.woff2" | asset_url }}') format('woff2');
}
</style>
Note

Font preloads must be CORS-enabled, even for same-origin fonts. Don't pass crossorigin to preload_tag: it emits crossorigin="anonymous" on its own when as is font.

If you prefer to keep @font-face declarations in a CSS file, then create assets/fonts.css.liquid:

assets/fonts.css.liquid

/* montserrat-regular - latin */
@font-face {
font-display: swap;
font-family: 'Montserrat';
font-style: normal;
font-weight: 400;
src: url('{{ "montserrat-v31-latin-regular.woff2" | asset_url }}') format('woff2'),
url('{{ "montserrat-v31-latin-regular.woff" | asset_url }}') format('woff');
}

Include in theme.liquid:

{{ 'fonts.css' | asset_url | stylesheet_tag }}

The source file must have a .css.liquid extension for Shopify to process the Liquid code inside it. Reference it as fonts.css, without the .liquid extension: Shopify serves the compiled asset under that name.

Anchor to Self-hosting best practicesSelf-hosting best practices

Always use font-display: swap to avoid invisible text while fonts load:

@font-face {
font-display: swap; /* Show the fallback font immediately */
font-family: "Montserrat";
src: url("...") format("woff2");
}

This prevents FOIT (Flash of Invisible Text) and lets customers see content immediately with a fallback font.

Limit the number of fonts. Each font family and weight requires a separate download. 1 to 2 font families is good, 2 to 4 weights per family is reasonable, and more than 6 total font files impacts performance.

Don't forget fallback fonts. See Reduce CLS from font swapping for information on reducing CLS from font swapping by customizing fallback fonts to match web font dimensions.


Anchor to When self-hosting isn't possibleWhen self-hosting isn't possible

Some font services don't allow self-hosting because of licensing restrictions. If you can't self-host, then add preconnect hints to warm up the connection early and minimize latency.

Typekit fonts can't be self-hosted. Add preconnect for both Typekit domains:

<link rel="preconnect" href="https://p.typekit.net" />
<link rel="preconnect" href="https://use.typekit.net" crossorigin />

Sometimes you must use the Google Fonts API directly because of dynamic font pickers or client requirements. If you can't self-host, then minimize the performance impact:

  1. Make the stylesheet async:
{%- comment -%} Load the Google Fonts CSS asynchronously {%- endcomment -%}
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
media="print"
onload="this.media='all'"
/>
<noscript>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap">
</noscript>
  1. Add preconnect:
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
  1. Limit font weights.

Load only the weights you actually use. Good: ?family=Roboto:wght@400;700. Anti-pattern: ?family=Roboto:wght@100;200;300;400;500;600;700;800;900.


  • Chrome DevTools Network panel: count connections to font domains. The count should be zero for self-hosted fonts.
  • Network tab: verify that fonts load from cdn.shopify.com or your store domain.
  • Compare timings: self-hosting eliminates external domain connections. Check the waterfall for fewer DNS and connection bars.


Was this page helpful?