CSS Performance & Workflow: The Complete Guide to Shipping Production-Ready Sites
You can write beautiful CSS. But can you ship it? Can you hand your codebase to another developer without a three-hour explanation? Can a user on a 3G connection in rural India load your site in under two seconds? Will your styles break when a third-party widget injects its own CSS?
This module bridges the gap between "I know CSS" and "I ship CSS professionally." We'll cover architecture patterns that scale, build tools that automate the boring parts, performance budgets that keep you honest, and a Git workflow that won't make your teammates hate you.
By the end, you'll build and deploy a SaaS landing page with a 90+ Lighthouse score, optimized images, and a maintainable SCSS architecture.
Part 1: CSS Architecture — Writing Code That Doesn't Rot
CSS is the only language where adding two lines in one file can break a layout ten files away. Architecture prevents this.
The BEM Naming Convention
Block Element Modifier keeps selectors flat, specific, and self-documenting.
1/* Block: standalone component */ 2.card { } 3 4/* Element: part of a block */ 5.card__title { } 6.card__image { } 7.card__content { } 8.card__footer { } 9 10/* Modifier: variant of a block or element */ 11.card--featured { } 12.card__title--large { } 13.card--dark { }
The Rules:
- Blocks can nest, but classes don't mirror the DOM
- Elements are prefixed with the block name:
.card__title, never.card .title - Modifiers are prefixed with the block or element:
.card--dark, never.dark-card
1<!-- Correct BEM --> 2<article class="card card--featured"> 3 <img class="card__image" src="..." alt="..."> 4 <div class="card__content"> 5 <h3 class="card__title card__title--large">Featured Post</h3> 6 <p class="card__excerpt">...</p> 7 </div> 8 <footer class="card__footer"> 9 <time class="card__date">Aug 12, 2026</time> 10 <a class="card__link" href="#">Read more</a> 11 </footer> 12</article>
1// SCSS nesting makes BEM readable without specificity bloat 2.card { 3 background: var(--color-surface); 4 border-radius: 12px; 5 padding: 1.5rem; 6 7 &__image { 8 width: 100%; 9 height: 200px; 10 object-fit: cover; 11 border-radius: 8px; 12 } 13 14 &__title { 15 font-size: 1.25rem; 16 margin: 1rem 0 0.5rem; 17 18 &--large { 19 font-size: 1.5rem; 20 } 21 } 22 23 &--featured { 24 border: 2px solid var(--color-primary); 25 } 26 27 &--dark { 28 background: var(--color-surface-dark); 29 color: var(--color-text-light); 30 } 31}
Utility-First Thinking
Not everything needs a component. Some styles are one-offs.
1// utilities/_spacing.scss 2.m-0 { margin: 0; } 3.mt-1 { margin-top: 0.5rem; } 4.mt-2 { margin-top: 1rem; } 5.p-2 { padding: 1rem; } 6.px-3 { padding-inline: 1.5rem; } 7 8// utilities/_text.scss 9.text-center { text-align: center; } 10.text-sm { font-size: 0.875rem; } 11.font-bold { font-weight: 700; } 12.truncate { 13 overflow: hidden; 14 text-overflow: ellipsis; 15 white-space: nowrap; 16}
When to use BEM vs. Utility:
- BEM for complex, reusable components (cards, modals, navigation)
- Utility for simple, one-off adjustments (margin tweaks, text alignment)
- Both in practice: BEM for structure, utilities for fine-tuning
File Organization at Scale
styles/
├── main.scss # Entry point: only imports
├── 1-settings/
│ ├── _variables.scss # Colors, typography, spacing tokens
│ ├── _breakpoints.scss # Media query values
│ └── _z-index.scss # Z-index scale
├── 2-tools/
│ ├── _mixins.scss # Reusable mixins
│ └── _functions.scss # Sass functions
├── 3-generic/
│ ├── _reset.scss # Box-sizing, margin reset
│ └── _normalize.scss # Cross-browser consistency
├── 4-elements/
│ ├── _headings.scss # h1-h6 base styles
│ ├── _links.scss # Anchor defaults
│ └── _forms.scss # Input defaults
├── 5-objects/
│ ├── _layout.scss # Grid systems, containers
│ └── _media.scss # Responsive image patterns
├── 6-components/
│ ├── _button.scss
│ ├── _card.scss
│ ├── _nav.scss
│ └── _modal.scss
├── 7-utilities/
│ ├── _spacing.scss
│ ├── _text.scss
│ └── _visibility.scss
└── 8-overrides/
└── _shame.scss # Temporary hacks with TODOs
The ITCSS Inverted Triangle: Start with the least specific (settings, tools) and end with the most specific (utilities, overrides). This prevents specificity wars because later files naturally override earlier ones.
Part 2: CSS Preprocessors in 2026 — Do You Still Need Sass?
Native CSS now has variables, nesting, color-mix(), @layer, and @scope. So why use Sass?
What Sass Still Does Better
| Feature | Native CSS | Sass |
|---|---|---|
| Variables | ✅ CSS Custom Properties | ✅ Compiled to static values |
| Nesting | ✅ Native since 2023 | ✅ More mature, & combinator |
| Color functions | color-mix(), oklch() | darken(), lighten(), mix() |
| Partials/Imports | @import (deprecated) | @use and @forward |
| Loops/Conditionals | ❌ Not possible | @for, @each, @if |
| Mathematical logic | calc() only | Full math, string interpolation |
The 2026 Verdict
Use Sass for architecture (file organization, loops generating utility classes) and native CSS for runtime (theming, dark mode, component overrides).
1// _utilities.scss — Sass generates 50 classes from 5 lines 2$spacers: ( 3 0: 0, 4 1: 0.25rem, 5 2: 0.5rem, 6 3: 1rem, 7 4: 1.5rem, 8 5: 3rem, 9); 10 11@each $name, $value in $spacers { 12 .m-#{$name} { margin: $value; } 13 .mt-#{$name} { margin-top: $value; } 14 .mb-#{$name} { margin-bottom: $value; } 15 .p-#{$name} { padding: $value; } 16 .px-#{$name} { padding-inline: $value; } 17}
1// _button.scss — Sass for structure, CSS vars for theming 2.button { 3 // Sass compiles these to static values 4 padding: 0.75rem 1.5rem; 5 border-radius: 0.5rem; 6 font-weight: 600; 7 8 // CSS Custom Properties for runtime theming 9 background: var(--button-bg, var(--color-primary)); 10 color: var(--button-color, white); 11 border: 1px solid var(--button-border, transparent); 12 13 &:hover { 14 background: var(--button-bg-hover, var(--color-primary-dark)); 15 } 16 17 &--outline { 18 --button-bg: transparent; 19 --button-color: var(--color-primary); 20 --button-border: var(--color-primary); 21 --button-bg-hover: var(--color-primary); 22 --button-color-hover: white; 23 } 24}
Part 3: Build Tools — Vite, PostCSS, and CSS Modules
Writing SCSS is step one. Shipping optimized CSS is step two. Build tools handle transpilation, minification, autoprefixing, and tree-shaking.
Project Setup with Vite
1npm create vite@latest saas-landing -- --template vanilla 2cd saas-landing 3npm install 4npm install -D sass postcss autoprefixer
Vite Configuration (vite.config.js)
1import { defineConfig } from 'vite'; 2 3export default defineConfig({ 4 css: { 5 devSourcemap: true, 6 postcss: { 7 plugins: [ 8 require('autoprefixer') 9 ] 10 } 11 }, 12 build: { 13 cssCodeSplit: true, // Extract CSS into separate files 14 cssMinify: 'lightningcss', // Faster than esbuild for CSS 15 rollupOptions: { 16 output: { 17 assetFileNames: (assetInfo) => { 18 // Add content hash for cache busting 19 if (assetInfo.name.endsWith('.css')) { 20 return 'assets/styles/[name]-[hash][extname]'; 21 } 22 return 'assets/[name]-[hash][extname]'; 23 } 24 } 25 } 26 } 27});
PostCSS Pipeline
PostCSS processes your CSS after Sass compiles it. Essential plugins:
1// postcss.config.js 2module.exports = { 3 plugins: { 4 'postcss-preset-env': { 5 stage: 2, // Stable future CSS features 6 features: { 7 'nesting-rules': true, 8 'custom-media-queries': true, 9 'container-queries': true 10 } 11 }, 12 autoprefixer: {}, // Adds vendor prefixes (-webkit-, -moz-) 13 cssnano: process.env.NODE_ENV === 'production' ? {} : false 14 } 15};
What this gives you:
- Write
color-mix(),oklch(), and container queries — PostCSS transpiles for older browsers - Autoprefixer adds
-webkit-prefixes where needed (you never write them by hand) - CSSNano minifies production CSS (removes whitespace, merges rules, optimizes values)
CSS Modules for Component Scoping
When building with frameworks (React, Vue, Svelte), CSS Modules scope styles to the component automatically.
1/* Button.module.css */ 2.button { 3 padding: 0.75rem 1.5rem; 4 background: var(--color-primary); 5} 6 7.primary { 8 composes: button; /* Inherits .button styles */ 9 background: var(--color-primary); 10} 11 12.large { 13 composes: button; 14 padding: 1rem 2rem; 15 font-size: 1.125rem; 16}
1// Button.jsx 2import styles from './Button.module.css'; 3 4export default function Button({ variant, size, children }) { 5 const className = [ 6 styles.button, 7 styles[variant], 8 styles[size] 9 ].join(' '); 10 11 return <button className={className}>{children}</button>; 12}
Compiled output: .Button_button__3x7a9 — unique, scoped, zero specificity conflicts.
Part 4: Critical CSS — Speeding Up First Paint
When a browser loads your page, it blocks rendering until all CSS is downloaded and parsed. If your CSS file is 150KB, the user stares at a blank screen until every byte arrives.
Critical CSS extracts the styles needed for above-the-fold content, inlines them in the <head>, and loads the rest asynchronously.
The Concept
1<head> 2 <!-- Critical CSS: inlined, render-blocking but tiny --> 3 <style> 4 /* Above-the-fold styles only */ 5 body{margin:0;font-family:system-ui} 6 .hero{min-height:100dvh;display:grid;place-items:center} 7 .hero h1{font-size:clamp(2rem,5vw,4rem)} 8 /* ... ~10-14KB total ... */ 9 </style> 10 11 <!-- Non-critical CSS: loaded asynchronously --> 12 <link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> 13 <noscript><link rel="stylesheet" href="/styles/main.css"></noscript> 14</head>
Generating Critical CSS with Critters
1npm install -D critters
1// vite.config.js 2import { defineConfig } from 'vite'; 3import critters from 'critters'; 4 5export default defineConfig({ 6 plugins: [ 7 critters({ 8 preload: 'swap', // Use font-display: swap 9 pruneSource: true, // Remove critical styles from main CSS 10 inlineFonts: true, // Inline critical font declarations 11 }) 12 ] 13});
Critters scans your built HTML, determines which CSS rules apply to above-the-fold elements, inlines them, and removes duplicates from the external stylesheet.
The Impact:
- Before: 150KB CSS blocks rendering for 800ms
- After: 12KB critical CSS renders in 80ms, rest loads in background
Part 5: Core Web Vitals — The Metrics That Matter
Google uses three metrics to measure user experience. Your CSS choices directly impact all three.
LCP (Largest Contentful Paint)
Target: Under 2.5 seconds
What it measures: How long until the largest visible element renders
CSS Impact:
1/* BAD: Background images for hero sections */ 2.hero { 3 background: url('hero-4k.jpg'); /* Blocks LCP if not preloaded */ 4} 5 6/* GOOD: Use <img> with fetchpriority */
1<img src="hero-800.jpg" 2 srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1600.jpg 1600w" 3 sizes="100vw" 4 alt="SaaS dashboard interface" 5 width="1600" 6 height="900" 7 fetchpriority="high" 8 decoding="async">
1/* BAD: FOUC from web fonts */ 2body { 3 font-family: 'Inter'; /* Invisible text while font loads */ 4} 5 6/* GOOD: Font display strategy */ 7@font-face { 8 font-family: 'Inter'; 9 src: url('Inter.woff2') format('woff2'); 10 font-display: swap; /* Show fallback font immediately, swap when loaded */ 11}
INP (Interaction to Next Paint)
Target: Under 200ms
What it measures: How quickly the page responds to clicks, taps, and keystrokes
CSS Impact:
1/* BAD: Animating layout properties */ 2.modal { 3 transition: width 0.3s, height 0.3s, top 0.3s; /* Triggers layout */ 4} 5 6/* GOOD: Animate only transform and opacity */ 7.modal { 8 transition: transform 0.3s, opacity 0.3s; /* GPU composited */ 9 transform: translateY(20px); 10 opacity: 0; 11} 12 13.modal--open { 14 transform: translateY(0); 15 opacity: 1; 16}
1/* BAD: Expensive selectors on large DOM */ 2body :nth-child(3n+1) { } /* Browser recalculates on every element */ 3 4/* GOOD: Scoped, specific selectors */ 5.card:nth-child(3n+1) { } /* Limited scope */
CLS (Cumulative Layout Shift)
Target: Under 0.1
What it measures: How much content shifts around after initial render
CSS Impact:
1/* BAD: Images without dimensions */ 2img { max-width: 100%; } /* Height collapses to 0 until image loads */ 3 4/* GOOD: Always specify width and height */ 5img { 6 width: 100%; 7 height: auto; 8 aspect-ratio: 16 / 9; /* Reserves space before image loads */ 9} 10 11/* BAD: Injecting content above existing content */ 12.banner { 13 position: absolute; 14 top: 0; /* Pushes everything down when injected */ 15} 16 17/* GOOD: Reserve space for dynamic content */ 18.banner { 19 min-height: 60px; /* Space reserved even when empty */ 20}
Part 6: Image Optimization — The Biggest Performance Win
Images are typically 60-80% of page weight. Optimizing them is non-negotiable.
Modern Image Formats
| Format | Use Case | Size vs JPEG |
|---|---|---|
| JPEG | Photography | Baseline |
| PNG | Transparency, screenshots | 3-5× larger |
| WebP | Modern replacement for JPEG/PNG | 25-35% smaller |
| AVIF | Next-gen replacement | 50-60% smaller |
| SVG | Icons, logos, illustrations | Vector, infinite scale |
The <picture> Element with Format Fallbacks
1<picture> 2 <!-- AVIF: smallest, best quality --> 3 <source 4 srcset="hero-400.avif 400w, 5 hero-800.avif 800w, 6 hero-1600.avif 1600w" 7 sizes="100vw" 8 type="image/avif"> 9 10 <!-- WebP: widely supported, still small --> 11 <source 12 srcset="hero-400.webp 400w, 13 hero-800.webp 800w, 14 hero-1600.webp 1600w" 15 sizes="100vw" 16 type="image/webp"> 17 18 <!-- JPEG: universal fallback --> 19 <img 20 src="hero-800.jpg" 21 srcset="hero-400.jpg 400w, 22 hero-800.jpg 800w, 23 hero-1600.jpg 1600w" 24 sizes="100vw" 25 alt="SaaS analytics dashboard showing revenue charts" 26 width="1600" 27 height="900" 28 loading="eager" 29 decoding="async" 30 fetchpriority="high"> 31</picture>
Image Optimization Pipeline
1# Install sharp for batch conversion 2npm install -D sharp 3 4# Create optimize-images.js 5const sharp = require('sharp'); 6const glob = require('glob'); 7const path = require('path'); 8 9const images = glob.sync('src/images/**/*.{jpg,png}'); 10 11images.forEach(img => { 12 const base = path.parse(img); 13 14 // Generate WebP 15 sharp(img) 16 .webp({ quality: 85 }) 17 .toFile(`public/images/${base.name}.webp`); 18 19 // Generate AVIF 20 sharp(img) 21 .avif({ quality: 80 }) 22 .toFile(`public/images/${base.name}.avif`); 23 24 // Generate responsive sizes 25 [400, 800, 1200].forEach(width => { 26 sharp(img) 27 .resize(width) 28 .webp({ quality: 85 }) 29 .toFile(`public/images/${base.name}-${width}.webp`); 30 }); 31});
Lazy Loading Strategy
1<!-- Above the fold: eager load --> 2<img src="hero.jpg" alt="..." loading="eager" fetchpriority="high" width="800" height="400"> 3 4<!-- Below the fold: lazy load --> 5<img src="feature-1.jpg" alt="..." loading="lazy" decoding="async" width="600" height="400"> 6<img src="feature-2.jpg" alt="..." loading="lazy" decoding="async" width="600" height="400">
| Attribute | Purpose |
|---|---|
loading="lazy" | Defers loading until near viewport |
loading="eager" | Load immediately (for above-fold) |
decoding="async" | Decode off main thread, prevents jank |
fetchpriority="high" | Hint to browser: load this first |
fetchpriority="low" | Hint to browser: deprioritize this |
Part 7: Version Control — Git Without the Mess
Your CSS is only as good as your ability to track, review, and revert changes.
Meaningful Commit Messages
type(scope): subject
body (optional)
footer (optional)
| Type | Use When |
|---|---|
feat | New feature or component |
fix | Bug fix |
style | Formatting, missing semicolons, no code change |
refactor | Code change that neither fixes nor adds feature |
perf | Performance improvement |
chore | Build process, dependencies |
1git commit -m "feat(nav): add responsive mobile menu with slide animation" 2 3git commit -m "perf(images): convert hero assets to AVIF, reduce LCP by 1.2s" 4 5git commit -m "fix(button): ensure focus-visible ring meets WCAG contrast"
Branching Strategy for Solo Projects
1# Main branch is always deployable 2git checkout main 3git pull origin main 4 5# Create feature branch 6git checkout -b feat/hero-section 7 8# Work, commit, push 9git add . 10git commit -m "feat(hero): build responsive hero with CTA" 11git push -u origin feat/hero-section 12 13# Open Pull Request on GitHub, review, merge 14git checkout main 15git pull origin main 16git branch -d feat/hero-section
.gitignore for CSS Projects
1# Dependencies 2node_modules/ 3 4# Build output 5dist/ 6build/ 7 8# Environment 9.env 10.env.local 11 12# OS files 13.DS_Store 14Thumbs.db 15 16# Editor 17.vscode/settings.json 18.idea/ 19 20# Logs 21npm-debug.log*
Part 8: The Mini-Project — SaaS Landing Page
Build a fictional SaaS tool landing page. The goal: 90+ Lighthouse across all categories.
Project Structure
saas-landing/
├── src/
│ ├── index.html
│ ├── main.js # Minimal JS for mobile menu toggle
│ ├── styles/
│ │ ├── main.scss
│ │ ├── 1-settings/
│ │ ├── 2-tools/
│ │ ├── 3-generic/
│ │ ├── 4-elements/
│ │ ├── 5-objects/
│ │ ├── 6-components/
│ │ └── 7-utilities/
│ └── images/
│ ├── logo.svg
│ ├── hero-dashboard.png # Source, will convert to avif/webp
│ └── icons/
├── public/
│ └── images/ # Optimized images
├── package.json
├── vite.config.js
└── postcss.config.js
package.json
1{ 2 "name": "saas-landing", 3 "private": true, 4 "version": "0.0.0", 5 "type": "module", 6 "scripts": { 7 "dev": "vite", 8 "build": "vite build", 9 "preview": "vite preview", 10 "optimize-images": "node scripts/optimize-images.js" 11 }, 12 "devDependencies": { 13 "autoprefixer": "^10.4.20", 14 "critters": "^0.0.23", 15 "postcss": "^8.4.41", 16 "postcss-preset-env": "^9.6.0", 17 "sass": "^1.77.8", 18 "sharp": "^0.33.4", 19 "vite": "^5.3.0" 20 } 21}
src/styles/main.scss (Entry Point)
1// 1. Settings 2@use '1-settings/variables'; 3@use '1-settings/breakpoints'; 4@use '1-settings/z-index'; 5 6// 2. Tools 7@use '2-tools/mixins'; 8@use '2-tools/functions'; 9 10// 3. Generic 11@use '3-generic/reset'; 12@use '3-generic/normalize'; 13 14// 4. Elements 15@use '4-elements/headings'; 16@use '4-elements/links'; 17@use '4-elements/forms'; 18 19// 5. Objects 20@use '5-objects/layout'; 21@use '5-objects/media'; 22 23// 6. Components 24@use '6-components/button'; 25@use '6-components/card'; 26@use '6-components/nav'; 27@use '6-components/hero'; 28@use '6-components/pricing'; 29@use '6-components/footer'; 30 31// 7. Utilities 32@use '7-utilities/spacing'; 33@use '7-utilities/text'; 34@use '7-utilities/visibility';
Key Component: src/styles/6-components/_hero.scss
1@use '../1-settings/variables' as *; 2@use '../2-tools/mixins' as *; 3 4.hero { 5 position: relative; 6 min-height: 100dvh; 7 display: grid; 8 place-items: center; 9 overflow: hidden; 10 background: linear-gradient(135deg, var(--color-bg) 0%, var(--color-surface) 100%); 11 12 &__container { 13 width: min(1100px, 100% - 2rem); 14 margin-inline: auto; 15 display: grid; 16 gap: 3rem; 17 padding-block: 6rem 3rem; 18 19 @include respond-to('tablet') { 20 grid-template-columns: 1fr 1fr; 21 align-items: center; 22 padding-block: 0; 23 } 24 } 25 26 &__content { 27 text-align: center; 28 29 @include respond-to('tablet') { 30 text-align: start; 31 } 32 } 33 34 &__title { 35 font-size: clamp(2rem, 5vw, 3.5rem); 36 font-weight: 800; 37 line-height: 1.1; 38 letter-spacing: -0.02em; 39 margin-bottom: 1.5rem; 40 41 // Gradient text 42 background: linear-gradient(135deg, var(--color-text) 0%, var(--color-primary) 100%); 43 -webkit-background-clip: text; 44 -webkit-text-fill-color: transparent; 45 } 46 47 &__description { 48 font-size: clamp(1rem, 1.5vw, 1.25rem); 49 color: var(--color-text-muted); 50 margin-bottom: 2rem; 51 max-width: 50ch; 52 } 53 54 &__actions { 55 display: flex; 56 flex-wrap: wrap; 57 gap: 1rem; 58 justify-content: center; 59 60 @include respond-to('tablet') { 61 justify-content: flex-start; 62 } 63 } 64 65 &__image { 66 position: relative; 67 border-radius: 1rem; 68 overflow: hidden; 69 box-shadow: 0 25px 50px -12px var(--shadow-color); 70 transform: perspective(1000px) rotateY(-5deg) rotateX(5deg); 71 transition: transform 0.5s ease; 72 73 &:hover { 74 transform: perspective(1000px) rotateY(0) rotateX(0); 75 } 76 77 img { 78 width: 100%; 79 height: auto; 80 display: block; 81 } 82 } 83}
vite.config.js with Critters
1import { defineConfig } from 'vite'; 2import { resolve } from 'path'; 3 4export default defineConfig({ 5 root: 'src', 6 build: { 7 outDir: '../dist', 8 emptyOutDir: true, 9 cssCodeSplit: true, 10 cssMinify: 'lightningcss', 11 rollupOptions: { 12 input: { 13 main: resolve(__dirname, 'src/index.html') 14 }, 15 output: { 16 entryFileNames: 'assets/js/[name]-[hash].js', 17 chunkFileNames: 'assets/js/[name]-[hash].js', 18 assetFileNames: (info) => { 19 if (info.name.endsWith('.css')) { 20 return 'assets/css/[name]-[hash][extname]'; 21 } 22 if (/\.(png|jpe?g|gif|svg|webp|avif)$/.test(info.name)) { 23 return 'assets/images/[name]-[hash][extname]'; 24 } 25 return 'assets/[name]-[hash][extname]'; 26 } 27 } 28 } 29 }, 30 css: { 31 devSourcemap: true, 32 preprocessorOptions: { 33 scss: { 34 additionalData: `@use "sass:math";` 35 } 36 } 37 } 38});
Performance Checklist for the Landing Page
Before deploying, verify:
Performance (Target: 90+)
- All images converted to AVIF/WebP with
<picture>fallbacks - Images have explicit
widthandheight - Hero image uses
fetchpriority="high"andloading="eager" - Below-fold images use
loading="lazy" - CSS is minified and split
- Fonts use
font-display: swap - No render-blocking resources above the fold
Accessibility (Target: 100)
- Skip link is first focusable element
- All images have descriptive
alttext - Color contrast ≥ 4.5:1 for all text
- Focus indicators visible on all interactive elements
- Form labels properly associated
- Heading hierarchy is logical (
h1→h2→h3) - ARIA landmarks present (
<header>,<nav>,<main>,<footer>)
Best Practices (Target: 100)
- HTTPS enabled
- No console errors
- Uses passive event listeners for scroll/touch
- Avoids deprecated APIs
SEO (Target: 100)
- Meta title and description present
- Canonical URL set
- Open Graph tags for social sharing
- Semantic HTML structure
- XML sitemap submitted
Part 9: Deployment — From GitHub to Live URL
Step 1: Push to GitHub
1git init 2git add . 3git commit -m "feat: initial SaaS landing page with full optimization" 4git branch -M main 5git remote add origin https://github.com/yourusername/saas-landing.git 6git push -u origin main
Step 2: Deploy to Netlify
- Go to netlify.com → Add new site → Import an existing project
- Connect your GitHub account, select
saas-landing - Build settings:
- Build command:
npm run build - Publish directory:
dist
- Build command:
- Click Deploy Site
Netlify automatically builds on every git push to main.
Step 3: Deploy to Vercel (Alternative)
- Go to vercel.com → Add New Project
- Import from GitHub
- Framework preset: Vite
- Build and output settings auto-detected
- Deploy
Step 4: Verify with Lighthouse
- Open your live URL in Chrome
- DevTools → Lighthouse → Mobile → Check all categories
- Run audit
- If score is under 90, fix the flagged issues and push again
Key Takeaways
| Concept | Why It Matters |
|---|---|
| BEM | Flat specificity, self-documenting, no naming collisions |
| ITCSS | Organized specificity escalation, prevents cascade wars |
| Sass in 2026 | Use for architecture (loops, partials), not runtime theming |
| Vite | Fast dev server, optimized builds, built-in CSS handling |
| PostCSS | Future CSS today, autoprefixing, minification |
| Critical CSS | Inline above-fold styles, defer rest, faster First Contentful Paint |
| LCP | Optimize images, preload heroes, use fetchpriority |
| INP | Animate only transform and opacity |
| CLS | Always set image dimensions, reserve space for dynamic content |
| AVIF/WebP | 50-60% smaller than JPEG, served with <picture> fallback |
| Git workflow | Feature branches, meaningful commits, clean history |
What's Next?
You now have a production-grade workflow. Your code is organized, your assets are optimized, your metrics are measured, and your site is live.
Module 7 covers The Portfolio Build & Interview Prep: taking everything from Modules 1-6 and building a personal portfolio that gets callbacks. We'll cover case study structure, deployment strategy, and the CSS interview questions that actually get asked in 2026.
Push your SaaS landing to GitHub. Your future employer will look at your commit history. Make it clean.
Quick Reference Cheat Sheet
1// BEM 2.block { } 3.block__element { } 4.block--modifier { } 5 6// ITCSS order 7// 1-settings → 2-tools → 3-generic → 4-elements → 5-objects → 6-components → 7-utilities 8 9// Critical CSS 10<style>/* above-fold styles */</style> 11<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'"> 12 13// Image optimization 14<picture> 15 <source srcset="img.avif" type="image/avif"> 16 <source srcset="img.webp" type="image/webp"> 17 <img src="img.jpg" alt="..." width="800" height="400" loading="lazy"> 18</picture> 19 20// Core Web Vitals fixes 21img { width: 100%; height: auto; aspect-ratio: 16/9; } // CLS 22.element { transition: transform, opacity; } // INP 23<img fetchpriority="high" decoding="async"> // LCP 24 25// Git workflow 26git checkout -b feat/name 27git commit -m "type(scope): description" 28git push origin feat/name 29# Open PR, merge, delete branch
Your landing page is live and scoring 90+. In Module 7, we turn this workflow into a portfolio that gets you hired.