---
title: Load first-paint resources before content_for_header
description: >-
  Shopify can start sending the page to the browser when the layout reaches `{{
  content_for_header }}`. Place the stylesheets, fonts, and scripts the first
  paint needs above it so the browser fetches them while Shopify renders your
  sections.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/load-critical-resources-before-content-for-header
  md: >-
    https://shopify.dev/docs/storefronts/themes/best-practices/performance/load-critical-resources-before-content-for-header.md
api_name: liquid
---

# Load first-paint resources before content\_​for\_​header

Place the stylesheets, font preloads, and scripts that render anything the user sees before they scroll above `{{ content_for_header }}` in your layout. Shopify can send everything above that tag to the browser as soon as it's rendered. The rest of the page follows when your sections finish. What sits above the tag reaches the browser first.

***

## Why

Shopify can stream storefront HTML in two parts, and on most pages it does. The first part is your layout from `<!doctype html>` to `{{ content_for_header }}`. The browser receives it while Shopify is still rendering the sections in your template, and it starts fetching every stylesheet, font, and script it finds there right away. The second part, starting with the output of `content_for_header` and continuing through `</body>`, arrives when rendering completes.

If your critical stylesheet link sits below `{{ content_for_header }}`, then on a streamed page the browser can't request it until your sections have finished rendering. On a product or collection page with a heavy theme that can be hundreds of milliseconds of Liquid work. Move the link above the tag and the download overlaps with that work instead of waiting for it, so [First Contentful Paint (FCP)](https://web.dev/fcp/) and [Largest Contentful Paint (LCP)](https://web.dev/lcp/) come earlier.

Shopify streams eligible pages automatically, and it's expanding the set of pages that qualify. A layout that follows this practice loses nothing when a page isn't streamed, because the browser still receives the resources in the order it needs them. Streaming doesn't change how long your Liquid takes to render, so the [Liquid best practices](https://shopify.dev/docs/storefronts/themes/best-practices/performance#liquid) still apply. It changes when the browser can start its own work.

[Horizon](https://github.com/Shopify/horizon/blob/main/layout/theme.liquid) renders its stylesheets, font preloads, import map, module preloads, and CSS variables above `{{ content_for_header }}`, so all of them reach the browser in the first part of a streamed response. [Dawn](https://github.com/Shopify/dawn/blob/main/layout/theme.liquid) puts `{{ content_for_header }}` early in the head and loads `base.css`, its `font_face` declarations, and its component stylesheets below it, so those wait for the sections.

***

## How

### Move first-paint resources up

Everything the browser needs to paint the initial viewport belongs above `{{ content_for_header }}`:

* `<meta charset>`, `<meta name="viewport">`, and `<title>`.
* Stylesheet links for your base styles and for components visible in the initial viewport, loaded through [`stylesheet_tag`](https://shopify.dev/docs/api/liquid/filters/stylesheet_tag).
* `@font-face` declarations through [`font_face`](https://shopify.dev/docs/api/liquid/filters/font_face), and, if you [preload a font](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-preload-resource-hints-sparingly) for text the user sees before scrolling, that [`preload_tag`](https://shopify.dev/docs/api/liquid/filters/preload_tag) too.
* Inline `{% style %}` blocks that define CSS custom properties from theme settings.
* Your import map and any `modulepreload` links, followed by the module scripts that use them.

Keep base styles in a CSS file in `assets/` rather than in `{% stylesheet %}` tags. Shopify compiles `{% stylesheet %}` output from sections, blocks, and snippets into one file and links it from `content_for_header`. On themes such as Horizon that file is subset to the sections and blocks on the current page, which Shopify only knows once they've rendered, so the link always arrives with the second part of the response. That's the right place for section-scoped styles, and the wrong place for the styles your first paint depends on.

### Keep `content_for_header` as it is

Streaming depends on Shopify finding `{{ content_for_header }}` as a plain output tag inside `<head>`. Don't apply filters to it, wrap it in `{% if %}`, `{% capture %}`, or `{% liquid %}`, assign it to a variable, or move it into a snippet. The [`ContentForHeaderModification`](https://shopify.dev/docs/storefronts/themes/tools/theme-check/checks/content-for-header-modification) theme check flags some of these.

The same applies to layouts that apps provide. A landing page builder or a similar app that ships its own layout and captures or rewrites `content_for_header` turns streaming off for every page that uses that layout. Check the layouts under `layout/` that you didn't write.

For the full list of what makes a page eligible, including template type, see [Streamed HTML responses](https://shopify.dev/docs/storefronts/themes/best-practices/performance/platform#streamed-html-responses) on the platform page. Preview themes, the preview bar, and the theme editor aren't streamed. Test on your live theme.

### Check what depends on `content_for_header`

`content_for_header` defines the `window.Shopify` object and outputs stylesheets and deferred scripts of its own, so moving your code above it changes what's available when that code runs. Check three things before you move anything:

1. **Scripts that read `Shopify.*` while the page is parsing.** An inline `<script>`, or an external `<script src>` without `defer`, `async`, or `type="module"` (the form [`script_tag`](https://shopify.dev/docs/api/liquid/filters/script_tag) produces), runs as soon as the parser reaches it. Above `{{ content_for_header }}`, the `Shopify` object doesn't exist yet, so `Shopify.shop`, `Shopify.locale`, `Shopify.currency`, `Shopify.routes.root`, and the rest throw a `ReferenceError`. Leave such scripts where they are. They gain little from moving up, and making them wait for `Shopify` adds complexity for no benefit. If the value is available in Liquid, read it there instead: [`request.design_mode`](https://shopify.dev/docs/api/liquid/objects/request#request-design_mode), [`routes.root_url`](https://shopify.dev/docs/api/liquid/objects/routes#routes-root_url), [`request.locale`](https://shopify.dev/docs/api/liquid/objects/request#request-locale), and [`cart.currency`](https://shopify.dev/docs/api/liquid/objects/cart#cart-currency) cover the common cases.

   ## layout/theme.liquid

   ```liquid
   {%- comment -%} Breaks above the tag: Shopify isn't defined yet {%- endcomment -%}
   <script>
     window.themeRoot = Shopify.routes.root;
   </script>


   {%- comment -%} Works anywhere: the value comes from Liquid {%- endcomment -%}
   <script>
     window.themeRoot = {{ routes.root_url | json }};
   </script>


   {{ content_for_header }}
   ```

2. **Stylesheets that compete with Shopify's.** `content_for_header` links the stylesheet compiled from `{% stylesheet %}` tags and, on pages that render dynamic checkout buttons, the styles for those buttons. When two rules have equal specificity, the later stylesheet wins. A theme stylesheet moved from below the tag to above it now loses those conflicts. After you move a stylesheet, check components styled through `{% stylesheet %}` and the `.shopify-payment-button` area, and raise specificity where a rule stopped applying.

   ## layout/theme.liquid

   ```liquid
   {%- comment -%}
     Before: base.css comes after content_for_header, so its rules win
     ties against the compiled stylesheet file.
   {%- endcomment -%}
   {{ content_for_header }}
   {{ 'base.css' | asset_url | stylesheet_tag }}


   {%- comment -%}
     After: base.css comes first, so the compiled file wins the same ties.
     A rule such as `.card { padding: 0 }` in base.css that used to override
     `.card { padding: 1rem }` from a section's stylesheet tag no longer does.
   {%- endcomment -%}
   {{ 'base.css' | asset_url | stylesheet_tag }}
   {{ content_for_header }}
   ```

3. **`defer` scripts that depend on `{% javascript %}` code.** `content_for_header` also outputs the deferred scripts compiled from `{% javascript %}` tags. Deferred scripts run in document order, so a theme script moved above the tag now runs before them. This only matters if your theme script reads something a `{% javascript %}` block defines. The reverse dependency, section code that reads globals from your theme script, becomes safer.

   ## layout/theme.liquid

   ```liquid
   {%- comment -%}
     theme.js runs after the compiled javascript bundle when it's below the tag,
     and before it when it's above. Only code in theme.js that reads
     something the bundle defines notices the difference.
   {%- endcomment -%}
   <script src="{{ 'theme.js' | asset_url }}" defer></script>
   {{ content_for_header }}
   ```

Import maps need no extra care. Keep yours ahead of the module scripts that use it, as you would anyway. Its position relative to `{{ content_for_header }}` doesn't matter.

Apps need nothing from you. App embed blocks, theme app extension assets, and scripts that apps inject land after `content_for_header` regardless of how you arrange your layout.

### Keep the layout head cheap

Shopify renders everything above `{{ content_for_header }}` before it can send the first part of the response. Expensive Liquid in the layout head, such as a `meta-tags` snippet that loops over `collections` or `product.variants`, delays the whole benefit. Keep catalog loops in sections, where they belong anyway, and keep the head to metadata, resource links, and settings-driven CSS.

***

## Examples

A layout head arranged for streaming. This is the structure Horizon uses, with the snippets inlined:

## layout/theme.liquid

```liquid
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ page_title }}</title>
    <link rel="canonical" href="{{ canonical_url }}">


    {%- unless settings.type_body_font.system? -%}
      {{ settings.type_body_font | font_url | preload_tag: as: 'font', type: 'font/woff2' }}
    {%- endunless -%}


    {{ 'base.css' | asset_url | stylesheet_tag: preload: true }}


    {% style %}
      {{ settings.type_body_font | font_face: font_display: 'swap' }}
      :root {
        --color-background: {{ settings.color_background }};
        --font-body-family: {{ settings.type_body_font.family }}, {{ settings.type_body_font.fallback_families }};
      }
    {% endstyle %}


    <script type="importmap">
      {
        "imports": {
          "@theme/utilities": "{{ 'utilities.js' | asset_url }}"
        }
      }
    </script>
    <link rel="modulepreload" href="{{ 'utilities.js' | asset_url }}">
    <script src="{{ 'global.js' | asset_url }}" type="module"></script>


    {% if request.design_mode %}
      <script src="{{ 'theme-editor.js' | asset_url }}" defer></script>
    {% endif %}


    {{ content_for_header }}
  </head>
  <body>
    {% sections 'header-group' %}
    <main id="MainContent">
      {{ content_for_layout }}
    </main>
    {% sections 'footer-group' %}
  </body>
</html>
```

An inline script that shouldn't move above the tag. Dawn's head ends with this, well below `{{ content_for_header }}`:

## layout/theme.liquid

```liquid
{{ content_for_header }}


{%- comment -%} ... stylesheets, fonts, and settings-driven styles ... {%- endcomment -%}


<script>
  if (Shopify.designMode) {
    document.documentElement.classList.add('shopify-design-mode');
  }
</script>
```

Moved above `{{ content_for_header }}`, it throws `ReferenceError: Shopify is not defined` on the live storefront. Leave it in place, or read the value in Liquid, which removes the script altogether:

## layout/theme.liquid

```liquid
<html
  lang="{{ request.locale.iso_code }}"
  {% if request.design_mode %}class="shopify-design-mode"{% endif %}
>
```

***

## Testing

* Open the **Network** panel in Chrome DevTools, reload a product or collection page on your live theme, and select the document request. Preview themes and the theme editor aren't streamed, so the split doesn't show there. On a streamed page, **Waiting for server response** in the **Timing** tab ends when the first part of the response arrives and **Content download** covers the rest, so your stylesheet and font requests should start during **Content download** rather than after it. If the page wasn't streamed, they start right after the document arrives either way, and the comparison in the next step is the one to rely on.
* Compare two versions of the layout: one with your stylesheet link below `{{ content_for_header }}` and one with it above. In the waterfall, the stylesheet request starts earlier in the second version. Run each several times and compare medians, because network noise is larger than a single run's difference.
* Run a [Lighthouse](https://developer.chrome.com/docs/lighthouse) audit before and after. FCP and LCP should improve. The render-blocking resources audit still lists your stylesheets, which is expected. The goal is to start them earlier, not to remove them.
* Load the page with the theme editor and with a normal storefront visit, and check the console for `ReferenceError: Shopify is not defined`. The editor defines the `Shopify` object at the top of the head, so a script that only breaks outside the editor is easy to miss.
* Run [Theme Check](https://shopify.dev/docs/storefronts/themes/tools/theme-check) to confirm `ContentForHeaderModification` passes.

***

## References

* [The Shopify platform: Streamed HTML responses](https://shopify.dev/docs/storefronts/themes/best-practices/performance/platform#streamed-html-responses)
* [Layouts](https://shopify.dev/docs/storefronts/themes/architecture/layouts)
* [`stylesheet_tag`](https://shopify.dev/docs/api/liquid/filters/stylesheet_tag) filter
* [`preload_tag`](https://shopify.dev/docs/api/liquid/filters/preload_tag) filter
* [`font_face`](https://shopify.dev/docs/api/liquid/filters/font_face) filter
* [`stylesheet`](https://shopify.dev/docs/api/liquid/tags/stylesheet) and [`javascript`](https://shopify.dev/docs/api/liquid/tags/javascript) tags
* [`request.design_mode`](https://shopify.dev/docs/api/liquid/objects/request#request-design_mode)
* [`ContentForHeaderModification`](https://shopify.dev/docs/storefronts/themes/tools/theme-check/checks/content-for-header-modification) theme check
* [Load critical CSS synchronously](https://shopify.dev/docs/storefronts/themes/best-practices/performance/load-critical-css-synchronously)
* [Self-host web fonts on Shopify CDN](https://shopify.dev/docs/storefronts/themes/best-practices/performance/self-host-web-fonts)
* [Load JavaScript modules with import maps](https://shopify.dev/docs/storefronts/themes/best-practices/performance/use-import-maps-for-modules)
* [Measuring Liquid rendering time](https://shopify.dev/docs/storefronts/themes/best-practices/performance/testing-for-performance#measuring-liquid-rendering-time)

***
