Skip to main content
CodeRocket
Performancehighassets

Keep page weight under 1500KB

rule · page-weight

Page weight is the total size of all resources needed to render a page.

Code Examples

TypeScript
// next.config.js - automatic image optimization
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920],
    minimumCacheTTL: 60 * 60 * 24 * 365, // 1 year
  }
}
TSX
// Component with optimized images
import Image from 'next/image'
 
function ProductCard({ image, name }: { image: string; name: string }) {
  return (
    <Image
      src={image}
      alt={name}
      width={300}
      height={200}
      loading="lazy"
      sizes="(max-width: 640px) 100vw, 300px"
    />
  )
}

Why It Matters

Page weight directly correlates with load time—a 1.5MB page takes 3-5 seconds on 4G mobile. Every 100KB reduction improves user experience, especially on slower networks.

Weight Benchmarks

CategoryTargetAcceptablePoor
Total page< 500KB< 1500KB> 1500KB
HTML< 50KB< 100KB> 100KB
CSS< 50KB< 100KB> 100KB
JavaScript< 200KB< 400KB> 400KB
Images< 200KB< 500KB> 500KB
Fonts< 100KB< 200KB> 200KB

Typical Page Weight Breakdown

Resource TypeAverage %Optimization Priority
Images50-70%High
JavaScript15-25%High
CSS5-10%Medium
Fonts5-10%Medium
HTML2-5%Low

JavaScript Bundle Optimization

JavaScript
// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // Split vendor code
          vendor: ['react', 'react-dom'],
          // Split by feature
          charts: ['recharts', 'd3'],
        }
      }
    },
    // Warn if chunk exceeds size
    chunkSizeWarningLimit: 200
  }
}
TSX
// Dynamic imports for code splitting
import { lazy, Suspense } from 'react'
 
const HeavyChart = lazy(() => import('./HeavyChart'))
const AdminPanel = lazy(() => import('./AdminPanel'))
 
function Dashboard() {
  return (
    <Suspense fallback={<Skeleton />}>
      {showChart && <HeavyChart />}
      {isAdmin && <AdminPanel />}
    </Suspense>
  )
}

CSS Optimization

CSS
/* Remove unused CSS with PurgeCSS */
/* After: Only critical styles remain */
 
/* Use CSS custom properties to reduce repetition */
:root {
  --color-primary: #3b82f6;
  --spacing-md: 1rem;
}
 
/* Avoid large frameworks entirely if possible */
/* Tailwind CSS with purging typically produces < 10KB */

Font Optimization

CSS
/* Subset fonts to only needed characters */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom-latin.woff2') format('woff2');
  font-display: swap;
  unicode-range: U+0000-007F, U+0080-00FF; /* Latin only */
}
 
/* Or use system fonts */
body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

Performance Budget in CI/CD

JavaScript
// lighthouse.config.js
module.exports = {
  extends: 'lighthouse:default',
  settings: {
    budgets: [
      {
        resourceSizes: [
          { resourceType: 'total', budget: 500 },
          { resourceType: 'script', budget: 200 },
          { resourceType: 'image', budget: 200 },
          { resourceType: 'stylesheet', budget: 50 },
          { resourceType: 'font', budget: 100 },
        ]
      }
    ]
  }
}
YAML
# GitHub Actions performance check
- name: Run Lighthouse
  uses: treosh/lighthouse-ci-action@v10
  with:
    budgetPath: ./lighthouse-budget.json
    uploadArtifacts: true

Measuring Page Weight

JavaScript
// Check total page weight
function measurePageWeight() {
  const resources = performance.getEntriesByType('resource')
  const navigation = performance.getEntriesByType('navigation')[0]
 
  const breakdown = {
    html: navigation?.transferSize || 0,
    css: 0,
    js: 0,
    images: 0,
    fonts: 0,
    other: 0
  }
 
  resources.forEach(r => {
    const size = r.transferSize || 0
    if (r.initiatorType === 'css' || r.name.includes('.css')) {
      breakdown.css += size
    } else if (r.initiatorType === 'script' || r.name.includes('.js')) {
      breakdown.js += size
    } else if (r.initiatorType === 'img' || /\.(jpg|png|webp|avif|gif|svg)/.test(r.name)) {
      breakdown.images += size
    } else if (/\.(woff2?|ttf|otf|eot)/.test(r.name)) {
      breakdown.fonts += size
    } else {
      breakdown.other += size
    }
  })
 
  const total = Object.values(breakdown).reduce((a, b) => a + b, 0)
 
  console.table({
    ...Object.fromEntries(
      Object.entries(breakdown).map(([k, v]) => [k, `${(v / 1024).toFixed(1)} KB`])
    ),
    total: `${(total / 1024).toFixed(1)} KB`
  })
 
  return { total, breakdown }
}

Verification

Automated Checks

  • Check Network tab—sort by Size column
  • Run Lighthouse performance audit
  • Use WebPageTest for detailed breakdown
  • Set up performance budgets in CI

Manual Checks

  • Monitor with Real User Monitoring (RUM)