Performance Optimization

Optimize Turbo Themes for production with best practices for loading, caching, and runtime performance.

Overview

Turbo Themes is designed with performance in mind, but there are additional optimizations you can apply for production deployments. This guide covers loading strategies, caching, bundle optimization, and runtime performance.

Loading Strategies

Critical CSS Inlining

Inline critical theme variables to prevent Flash of Unstyled Content (FOUC):

<head>
  <!-- Inline critical theme variables -->
  <style>
    :root {
      --turbo-bg-base: #1e1e2e;
      --turbo-text-primary: #cdd6f4;
      --turbo-brand-primary: #89b4fa;
    }
  </style>

  <!-- Load full theme asynchronously -->
  <link rel="preload" href="/css/themes/catppuccin-mocha.css" as="style" />
  <link rel="stylesheet" href="/css/themes/catppuccin-mocha.css" />
</head>

Preloading Theme Assets

Preload the user’s preferred theme:

// In your head or early in the document
const savedTheme = localStorage.getItem('turbo-theme') || 'catppuccin-mocha';
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = `/css/themes/${savedTheme}.css`;
document.head.appendChild(link);

Lazy Loading Secondary Themes

Only load themes when needed:

async function loadTheme(themeName) {
  // Check if already loaded
  if (document.querySelector(`link[data-theme="${themeName}"]`)) {
    return;
  }

  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = `/css/themes/${themeName}.css`;
  link.dataset.theme = themeName;

  return new Promise((resolve, reject) => {
    link.onload = resolve;
    link.onerror = reject;
    document.head.appendChild(link);
  });
}

// Usage
document.querySelector('#theme-selector').addEventListener('change', async (e) => {
  await loadTheme(e.target.value);
  document.documentElement.setAttribute('data-theme', e.target.value);
});

Bundle Optimization

Tree Shaking

Import only what you need:

// Instead of importing everything
// import { themes, tokens, utils } from 'turbo-themes';

// Import specific modules
import { getTheme } from 'turbo-themes/themes';
import { applyTheme } from 'turbo-themes/utils';

CSS Purging

Configure your build tool to remove unused CSS:

// postcss.config.js with PurgeCSS
module.exports = {
  plugins: [
    require('@fullhuman/postcss-purgecss')({
      content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
      safelist: [
        /^turbo-/, // Keep all Turbo Themes classes
        /^\[data-theme/, // Keep theme attribute selectors
        /^--turbo-/, // Keep CSS custom properties
      ],
    }),
  ],
};

Minification

Ensure CSS is properly minified:

// vite.config.js
export default {
  css: {
    postcss: {
      plugins: [
        require('cssnano')({
          preset: [
            'default',
            {
              // Preserve custom properties
              cssDeclarationSorter: false,
              reduceIdents: false,
            },
          ],
        }),
      ],
    },
  },
};

Caching Strategies

Browser Caching

Set appropriate cache headers for theme files:

# nginx.conf
location /css/themes/ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Service Worker Caching

Cache themes for offline access:

// sw.js
const THEME_CACHE = 'turbo-themes-v1';
const THEME_FILES = [
  '/css/turbo-core.css',
  '/css/themes/catppuccin-mocha.css',
  '/css/themes/catppuccin-latte.css',
  '/css/themes/dracula.css',
  '/css/themes/github-dark.css',
  '/css/themes/github-light.css',
];

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(THEME_CACHE).then((cache) => cache.addAll(THEME_FILES)));
});

self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/css/themes/')) {
    event.respondWith(
      caches.match(event.request).then((response) => {
        return response || fetch(event.request);
      })
    );
  }
});

Local Storage Optimization

Efficiently store theme preferences:

// Store only the theme name, not the entire theme object
const ThemeStorage = {
  key: 'turbo-theme',

  get() {
    try {
      return localStorage.getItem(this.key);
    } catch {
      return null;
    }
  },

  set(theme) {
    try {
      localStorage.setItem(this.key, theme);
    } catch {
      // localStorage might be full or disabled
      console.warn('Could not save theme preference');
    }
  },
};

Runtime Performance

Avoiding Layout Thrashing

Batch DOM updates when switching themes:

function switchTheme(newTheme) {
  // Read phase - gather all measurements first
  const scrollPosition = window.scrollY;

  // Write phase - make all changes together
  requestAnimationFrame(() => {
    document.documentElement.setAttribute('data-theme', newTheme);
    updateThemeLink(newTheme);

    // Restore scroll position if layout shifted
    window.scrollTo(0, scrollPosition);
  });
}

Debouncing Theme Changes

Prevent rapid theme switching from impacting performance:

let themeTimeout;

function debouncedThemeSwitch(theme) {
  clearTimeout(themeTimeout);
  themeTimeout = setTimeout(() => {
    applyTheme(theme);
  }, 150);
}

CSS Containment

Use CSS containment for themed sections:

.themed-section {
  contain: layout style paint;
}

.theme-preview {
  contain: strict;
}

Reducing Paint Operations

Minimize repaints during theme transitions:

/* Use transform instead of changing colors directly for previews */
.theme-preview-card {
  will-change: transform;
  transition: transform 0.2s ease;
}

.theme-preview-card:hover {
  transform: scale(1.02);
}

/* Only transition necessary properties */
.themed-element {
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
  /* Avoid transitioning all properties */
}

Measuring Performance

Core Web Vitals

Monitor theme impact on Core Web Vitals:

// Measure Largest Contentful Paint
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('LCP:', entry.startTime);
  }
}).observe({ entryTypes: ['largest-contentful-paint'] });

// Measure Cumulative Layout Shift during theme switch
let clsScore = 0;
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      clsScore += entry.value;
    }
  }
}).observe({ entryTypes: ['layout-shift'] });

Theme Load Time

Track how long themes take to load:

async function measureThemeLoad(themeName) {
  const start = performance.now();

  await loadTheme(themeName);

  const duration = performance.now() - start;
  console.log(`Theme ${themeName} loaded in ${duration.toFixed(2)}ms`);

  // Send to analytics
  analytics.track('theme_load', {
    theme: themeName,
    duration,
  });
}

Production Checklist

Before deploying to production, verify:

  • CSS is minified - Check file sizes are optimized
  • Cache headers configured - Theme files have long cache times
  • Critical CSS inlined - No FOUC on initial load
  • Unused CSS removed - PurgeCSS or similar is configured
  • Service worker caching - Themes available offline
  • Theme preloading - User’s preferred theme loads first
  • Lazy loading - Secondary themes load on demand
  • No layout shift - CLS score is acceptable during theme switch
  • Performance monitoring - Core Web Vitals are tracked

File Size Reference

Turbo Themes is optimized for minimal file size:

FileSize (minified)Size (gzipped)
turbo-core.css~2 KB~0.8 KB
Individual theme~2-3 KB~0.7-1 KB
All themes combined~25 KB~5 KB

Next Steps