Why Optimization Matters
SCSS is powerful, but with great power comes great responsibility. Loops and mixins can generate HUGE CSS files if you're not careful. A full 12-column responsive grid system can create 10,000+ lines of CSS!
π‘ Performance Impact:
Every KB of CSS delays page load. A 500KB CSS file takes ~3 seconds to download on 3G. Optimization isn't optionalβit's essential.
Strategy 1: Generate Only What You Need
Don't generate utilities you won't use:
// β BAD: Generates 12 classes you might not need
@for $i from 1 through 12 {
.grid-cols-#{$i} {
grid-template-columns: repeat($i, 1fr);
}
}
// β
GOOD: Only generate what you use
$grid-variations: 1, 2, 3, 4, 6, 12;
@each $cols in $grid-variations {
.grid-cols-#{$cols} {
grid-template-columns: repeat($cols, 1fr);
}
}
Strategy 2: Use PurgeCSS
PurgeCSS removes unused CSS from your compiled files:
// package.json
{
"scripts": {
"build:css": "sass styles.scss styles.css",
"purge:css": "purgecss --css styles.css --content index.html --output styles.min.css"
}
}
// purgecss.config.js
module.exports = {
content: [
'./src/**/*.html',
'./src/**/*.js'
],
css: ['./dist/styles.css'],
safelist: [
// Classes to never remove
'grid',
'grid-cols-1',
'gap-4'
]
}
Strategy 3: Conditional Compilation
// _config.scss
$enable-responsive: true !default;
$enable-gaps: true !default;
$enable-row-utilities: false !default;
// _grid.scss
@if $enable-responsive {
@each $bp-name, $bp-value in $breakpoints {
@media (min-width: $bp-value) {
// Generate responsive classes
}
}
}
@if $enable-gaps {
@each $name, $size in $spacing {
.gap-#{$name} {
gap: $size;
}
}
}
@if $enable-row-utilities {
// Only compile if needed
@for $i from 1 through 6 {
.row-span-#{$i} {
grid-row: span $i;
}
}
}
Strategy 4: Extend Instead of Duplicate
// β BAD: Duplicates styles
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
// β
GOOD: Share styles with @extend
%grid-base {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.card-grid {
@extend %grid-base;
}
.product-grid {
@extend %grid-base;
}
// Compiles to:
// .card-grid, .product-grid {
// display: grid;
// grid-template-columns: repeat(3, 1fr);
// gap: 20px;
// }
β οΈ @extend Warning:
Use @extend sparingly! It can create unexpected selector combinations. For utility classes, duplication might be better than complex extends.
Strategy 5: Minification
// Install sass with compression
npm install sass --save-dev
// package.json
{
"scripts": {
"build": "sass --style=compressed styles.scss styles.min.css"
}
}
// Or use PostCSS
{
"scripts": {
"build": "sass styles.scss | postcss -u cssnano > styles.min.css"
}
}
Strategy 6: Smart Breakpoint Generation
// β BAD: Every utility at every breakpoint
@each $bp in (sm, md, lg, xl) {
@for $i from 1 through 12 {
.#{$bp}\:col-#{$i} { /* ... */ }
.#{$bp}\:grid-cols-#{$i} { /* ... */ }
.#{$bp}\:gap-#{$i} { /* ... */ }
}
}
// Result: 144+ classes per breakpoint = 576+ classes!
// β
GOOD: Only common breakpoint utilities
$responsive-columns: 1, 2, 3, 4;
$responsive-gaps: 2, 4, 6, 8;
@each $bp-name, $bp-value in $breakpoints {
@media (min-width: $bp-value) {
@each $col in $responsive-columns {
.#{$bp-name}\:grid-cols-#{$col} {
grid-template-columns: repeat($col, 1fr);
}
}
@each $gap in $responsive-gaps {
.#{$bp-name}\:gap-#{$gap} {
gap: #{$gap * 4}px;
}
}
}
}
// Result: 32 classes total!
Strategy 7: Use CSS Custom Properties
// Instead of generating classes for every value
// Use CSS variables for dynamic values
:root {
--grid-gap: 20px;
--grid-columns: 3;
}
.grid {
display: grid;
grid-template-columns: repeat(var(--grid-columns), 1fr);
gap: var(--grid-gap);
}
// Change via inline styles or JavaScript
//
// No need for .grid-cols-1, .grid-cols-2, etc!
β
Best Practice:
Use SCSS variables for compile-time values (breakpoints, spacing scale).
Use CSS custom properties for runtime values (theme colors, dynamic spacing).
Strategy 8: Code Splitting
// Split your grid system into modules
// Import only what each page needs
// critical.scss - Inline in
@import 'config';
@import 'grid-base'; // .grid, basic utilities
@import 'grid-responsive'; // Mobile-first responsive
// non-critical.scss - Load async
@import 'grid-advanced'; // Complex patterns
@import 'grid-animations'; // Transitions
@import 'grid-print'; // Print styles
Optimization Checklist
- β
Generate only used column counts (not all 1-12)
- β
Limit spacing scale (not every pixel value)
- β
Use responsive utilities only where needed
- β
Enable compression in production builds
- β
Use PurgeCSS or similar tool
- β
Split critical vs non-critical CSS
- β
Consider CSS custom properties for dynamic values
- β
Monitor compiled CSS file size
Measuring Performance
// Check compiled size
sass styles.scss styles.css
ls -lh styles.css
// Before compression: 245KB
// After compression: 45KB
// After PurgeCSS: 12KB
// Analyze what's taking space
npm install -g css-analyzer
css-analyzer styles.css
Real-World Example
// Optimized grid system configuration
// _config.scss
// Only enable what you need
$enable-responsive-grids: true;
$enable-responsive-gaps: true;
$enable-row-utilities: false; // Disabled - not used
$enable-grid-areas: false; // Disabled - not used
$enable-auto-fit: true;
// Limited column variations
$grid-columns: (1, 2, 3, 4, 6, 12); // Not 1-12!
// Limited spacing
$spacing-values: (0, 4, 8, 16, 24, 32, 48); // Common values only
// Limit responsive utilities
$responsive-breakpoints: (md, lg); // Skip sm, xl if not needed
// Result: 80% smaller CSS file!
Production Build Script
// package.json
{
"scripts": {
"dev": "sass --watch styles.scss:styles.css",
"build": "sass --style=compressed styles.scss:dist/styles.css && purgecss --css dist/styles.css --content src/**/*.html --output dist/",
"analyze": "ls -lh dist/styles.css && css-analyzer dist/styles.css"
}
}
π Quick Check (3 Questions)