Skip to content

Performance

Performance directly impacts user experience and search rankings. A fast theme keeps visitors engaged and improves conversion rates. This guide covers key optimization techniques for Basker themes.

Images are typically the largest assets on a page. Proper optimization is critical.

Always use the image_url filter to serve properly sized images:

{# Specify exact dimensions needed #}
<img
src="{{ event.image | image_url: width: 800, height: 500 }}"
alt="{{ event.image.alt | default: event.title }}"
width="800"
height="500"
>

Use <picture> or srcset for responsive images:

{# Picture element for art direction #}
<picture>
<source
media="(min-width: 1024px)"
srcset="{{ event.image | image_url: width: 1200, height: 600 }}"
>
<source
media="(min-width: 768px)"
srcset="{{ event.image | image_url: width: 800, height: 400 }}"
>
<img
src="{{ event.image | image_url: width: 400, height: 200 }}"
alt="{{ event.image.alt }}"
width="400"
height="200"
>
</picture>

Use native lazy loading for off-screen images:

{# Hero image - load immediately #}
<img
src="{{ page.image | image_url: width: 1920 }}"
alt="{{ page.title }}"
fetchpriority="high"
>
{# Below-the-fold images - lazy load #}
{% for event in events %}
<img
src="{{ event.image | image_url: width: 400 }}"
alt="{{ event.title }}"
loading="lazy"
>
{% endfor %}

Prevent layout shift by including width and height:

<img
src="{{ image | image_url: width: 600, height: 400 }}"
alt="{{ image.alt }}"
width="600"
height="400"
loading="lazy"
>

The stylesheet tag generates inline CSS. Use it only for:

  • Dynamic values from settings
  • Critical above-the-fold styles
  • Component-specific styles that vary per instance
{# Good: Dynamic values that can't be in external CSS #}
{% stylesheet %}
:root {
--brand-primary: {{ settings.global.primaryColor | default: '#0066cc' }};
--brand-secondary: {{ settings.global.secondaryColor | default: '#004499' }};
}
{% endstylesheet %}

Keep static CSS in external files (in your theme’s assets/ folder):

{# In layouts/default.liquid #}
<link rel="stylesheet" href="{{ 'styles/main.css' | asset_url }}">

Inline critical above-the-fold CSS to prevent render blocking:

<head>
{# Critical CSS inline #}
<style>
/* Minimal styles for above-the-fold content */
.header { /* ... */ }
.hero { /* ... */ }
</style>
{# Full stylesheet loaded async #}
<link rel="preload" href="{{ 'styles/main.css' | asset_url }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ 'styles/main.css' | asset_url }}"></noscript>
</head>

Use defer or place scripts at the end of <body>:

{# In layouts/default.liquid #}
<body>
{{ content_for_layout }}
{# Scripts at end of body #}
<script src="{{ 'scripts/main.js' | asset_url }}" defer></script>
</body>

The javascript tag creates inline scripts. Use for:

  • Small, page-specific functionality
  • Dynamic data that JavaScript needs
{# Good: Pass dynamic data to JS #}
{% javascript %}
window.siteConfig = {
apiEndpoint: '{{ settings.global.apiUrl }}',
tenantId: '{{ tenant.id }}'
};
{% endjavascript %}
{# Bad: Blocks rendering #}
<head>
<script src="heavy-library.js"></script>
</head>
{# Good: Non-blocking #}
<head>
<script src="heavy-library.js" defer></script>
</head>

Only fetch what you need:

{# Good: Limited results #}
{% for event in all_events limit: 6 %}
{% render 'components/event-card' event: event %}
{% endfor %}
{# Avoid: Processing all items when only showing few #}
{% for event in all_events %}
{% if forloop.index <= 6 %}
{% render 'components/event-card' event: event %}
{% endif %}
{% endfor %}

Assign complex computations to variables:

{# Good: Compute once, use multiple times #}
{% assign hero_image = page.theme.hero_image | default: page.image | default: settings.global.fallbackImage %}
{% if hero_image %}
<img src="{{ hero_image | image_url: width: 1920 }}">
{% endif %}
{# Avoid: Repeating the same logic #}
{% if page.theme.hero_image %}
<img src="{{ page.theme.hero_image | image_url }}">
{% elsif page.image %}
<img src="{{ page.image | image_url }}">
{% elsif settings.global.fallbackImage %}
<img src="{{ settings.global.fallbackImage | image_url }}">
{% endif %}

Components are cached and reused efficiently:

{# Good: Render reusable component #}
{% render 'components/event-card' event: event %}
{# Avoid: Repeating markup inline #}
<article class="event-card">
<h3>{{ event.title }}</h3>
{# ... lots of markup ... #}
</article>
<head>
{# Preload the most important font #}
<link
rel="preload"
href="{{ 'fonts/brand-font.woff2' | asset_url }}"
as="font"
type="font/woff2"
crossorigin
>
</head>

Prevent invisible text while fonts load:

@font-face {
font-family: 'Brand Font';
src: url('fonts/brand-font.woff2') format('woff2');
font-display: swap;
}

Only include the characters you need. Use tools like glyphhanger to create subsets.

Focus on these key metrics:

MetricTargetWhat it Measures
LCP (Largest Contentful Paint)< 2.5sLoading performance
FID (First Input Delay)< 100msInteractivity
CLS (Cumulative Layout Shift)< 0.1Visual stability
  • All images use image_url filter with dimensions
  • Off-screen images have loading="lazy"
  • Hero/above-fold images have fetchpriority="high"
  • Images include width and height attributes
  • Responsive images use srcset or <picture>
  • Static CSS in external files
  • stylesheet tag only for dynamic values
  • No unused CSS
  • Critical CSS inlined (optional but recommended)
  • Scripts use defer or are at end of body
  • No render-blocking scripts in <head>
  • javascript tag only for dynamic data
  • Loops use limit where appropriate
  • Complex computations assigned to variables
  • Reusable markup in components
  • Critical fonts preloaded
  • font-display: swap used
  • Fonts are subsetted if possible

Symptom: Slow LCP, high bandwidth usage

Solution:

{# Always specify appropriate dimensions #}
{{ image | image_url: width: 800, height: 600 }}

Symptom: High CLS score, jumpy page load

Solution:

{# Include width and height #}
<img
src="{{ image | image_url: width: 400, height: 300 }}"
width="400"
height="300"
alt="..."
>

Symptom: Slow FCP/LCP, white screen on load

Solution:

{# Defer non-critical scripts #}
<script src="{{ 'main.js' | asset_url }}" defer></script>
{# Async load non-critical CSS #}
<link rel="preload" href="{{ 'styles.css' | asset_url }}" as="style" onload="this.rel='stylesheet'">

Symptom: Slow overall load time

Solution:

  • Combine CSS files where possible
  • Use SVG sprites instead of individual icon files
  • Lazy load below-the-fold content