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.
Image Optimization
Section titled “Image Optimization”Images are typically the largest assets on a page. Proper optimization is critical.
Use the image_url Filter
Section titled “Use the image_url Filter”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">Responsive Images
Section titled “Responsive Images”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>Lazy Loading
Section titled “Lazy Loading”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 %}Always Specify Dimensions
Section titled “Always Specify Dimensions”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">CSS Optimization
Section titled “CSS Optimization”Use the stylesheet Tag Sparingly
Section titled “Use the stylesheet Tag Sparingly”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 %}External Stylesheets for Static CSS
Section titled “External Stylesheets for Static CSS”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 }}">Critical CSS
Section titled “Critical CSS”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>JavaScript Optimization
Section titled “JavaScript Optimization”Defer Non-Critical Scripts
Section titled “Defer Non-Critical Scripts”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>Use the javascript Tag Wisely
Section titled “Use the javascript Tag Wisely”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 %}Avoid Blocking Scripts in Head
Section titled “Avoid Blocking Scripts in Head”{# Bad: Blocks rendering #}<head> <script src="heavy-library.js"></script></head>
{# Good: Non-blocking #}<head> <script src="heavy-library.js" defer></script></head>Efficient Liquid Patterns
Section titled “Efficient Liquid Patterns”Limit Loop Iterations
Section titled “Limit Loop Iterations”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 %}Cache Computed Values
Section titled “Cache Computed Values”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 %}Use Components for Reusable Markup
Section titled “Use Components for Reusable Markup”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>Font Optimization
Section titled “Font Optimization”Preload Critical Fonts
Section titled “Preload Critical Fonts”<head> {# Preload the most important font #} <link rel="preload" href="{{ 'fonts/brand-font.woff2' | asset_url }}" as="font" type="font/woff2" crossorigin ></head>Use font-display: swap
Section titled “Use font-display: swap”Prevent invisible text while fonts load:
@font-face { font-family: 'Brand Font'; src: url('fonts/brand-font.woff2') format('woff2'); font-display: swap;}Subset Fonts
Section titled “Subset Fonts”Only include the characters you need. Use tools like glyphhanger to create subsets.
Measuring Performance
Section titled “Measuring Performance”Core Web Vitals
Section titled “Core Web Vitals”Focus on these key metrics:
| Metric | Target | What it Measures |
|---|---|---|
| LCP (Largest Contentful Paint) | < 2.5s | Loading performance |
| FID (First Input Delay) | < 100ms | Interactivity |
| CLS (Cumulative Layout Shift) | < 0.1 | Visual stability |
Testing Tools
Section titled “Testing Tools”- Lighthouse - Built into Chrome DevTools
- PageSpeed Insights - pagespeed.web.dev
- WebPageTest - webpagetest.org
Performance Checklist
Section titled “Performance Checklist”Images
Section titled “Images”- All images use
image_urlfilter with dimensions - Off-screen images have
loading="lazy" - Hero/above-fold images have
fetchpriority="high" - Images include
widthandheightattributes - Responsive images use
srcsetor<picture>
- Static CSS in external files
-
stylesheettag only for dynamic values - No unused CSS
- Critical CSS inlined (optional but recommended)
JavaScript
Section titled “JavaScript”- Scripts use
deferor are at end of body - No render-blocking scripts in
<head> -
javascripttag only for dynamic data
Liquid
Section titled “Liquid”- Loops use
limitwhere appropriate - Complex computations assigned to variables
- Reusable markup in components
- Critical fonts preloaded
-
font-display: swapused - Fonts are subsetted if possible
Common Performance Issues
Section titled “Common Performance Issues”Issue: Large Unoptimized Images
Section titled “Issue: Large Unoptimized Images”Symptom: Slow LCP, high bandwidth usage
Solution:
{# Always specify appropriate dimensions #}{{ image | image_url: width: 800, height: 600 }}Issue: Layout Shift from Images
Section titled “Issue: Layout Shift from Images”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="...">Issue: Render-Blocking Resources
Section titled “Issue: Render-Blocking Resources”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'">Issue: Too Many HTTP Requests
Section titled “Issue: Too Many HTTP Requests”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