Frontend Performance Frameworks

Frontend Performance & Modern Frameworks — เพิ่มความเร็วและเลือก Framework ที่ใช่ ⚡

DevOps & Vibe Coding 2026 ~25 min read
Frontend Performance & Modern Frameworks

🐕 ทำไม Frontend Performance สำคัญ?

ในโลกที่ทุกคนใช้มือถือเข้าเว็บ ความเร็วของเว็บไซต์ไม่ใช่แค่ "nice to have" แต่เป็นสิ่งที่ส่งผลโดยตรงต่อรายได้ อัตราการ bounce และ SEO ranking ของคุณ Google ใช้ Core Web Vitals เป็นปัจจัยในการจัดอันดับ ซึ่งหมายความว่าเว็บที่ช้าจะถูกลดอันดับในผลการค้นหาโดยอัตโนมัติ

ลองดูตัวเลขจริงที่ Google และบริษัทชั้นนำรายงาน — จะเห็นว่าแม้แค่ 100ms ก็ส่งผลกระทบมหาศาล:

⏱️ Every Second Counts
Load TimeBounce RateRevenue Impact
0 – 1 sec~10%✅ Best experience
1 – 3 sec~30%⚠️ Acceptable
3 – 5 sec~50%❌ Losing customers
5 – 10 sec~70%💀 Disaster
10+ sec~90%☠️ Everyone left
📊 Real stats (Google):
  • 53% of mobile users leave if load > 3 seconds
  • 1 sec slower = 7% drop in conversions
  • Amazon: 100ms slower = 1% less revenue
  • Google: 500ms slower = 20% less traffic
⚡ Performance IS a feature — not an afterthought

🖥️ Rendering Strategies — CSR vs SSR vs SSG vs ISR

The Big Picture

Rendering Strategies
CSR (Client-Side Rendering)

Server → Empty HTML + JS bundle → Browser downloads JS → Executes → Renders

blank
JS download
✅ visible
❌ Slow first paint ❌ Bad SEO ✅ Rich interactivity ✅ Simple hosting
SSR (Server-Side Rendering)

Server → Renders HTML → Sends full page → Browser shows → Downloads JS → Hydrates

✅ visible immediately
JS
✅ interactive
✅ Fast first paint ✅ Great SEO ⚠️ Server load per request ⚠️ TTFB can be slow
SSG (Static Site Generation)

Build time → Pre-render ALL pages → Deploy → Serve static HTML from CDN

✅ instant from CDN!
JS
✅ interactive
✅ Fastest possible ✅ Great SEO ✅ Cheapest hosting ❌ Stale until rebuild
ISR (Incremental Static Regeneration)

Like SSG but pages regenerate in background — serve stale → rebuild → serve fresh

✅ Fast like SSG ✅ Fresh content ✅ No full rebuild ⚠️ Slightly stale for first visitor

Comparison Table

เปรียบเทียบทั้ง 4 strategies แบบ side-by-side — ดูง่ายๆ ว่าแต่ละตัวเด่นตรงไหน และเหมาะกับ use case แบบไหน:

CSRSSRSSGISR
First Paint🐢 Slow⚡ Fast⚡ Fast⚡ Fast
SEO
Interactivity⚡ Fast⚠️ Wait⚠️ Wait⚠️ Wait
Server LoadNoneHighNoneLow
Freshness✅ Live✅ Live❌ Build⚡ Near
Hosting Cost💰💰💰💰💰💰💰
Build TimeFastN/A🐢 SlowFast
Best For:
CSR → Dashboards, admin panels, internal tools
SSR → E-commerce, social media, dynamic content
SSG → Blogs, docs, marketing sites, portfolios
ISR → E-commerce product pages, news sites

Next.js — All Strategies in One Framework

Next.js เป็น framework ที่รวมทุก rendering strategy ไว้ในที่เดียว — คุณสามารถใช้ SSG, ISR, SSR และ CSR ในโปรเจคเดียวกัน โดยเลือกแต่ละหน้าว่าจะใช้ strategy ไหน ดูตัวอย่างโค้ดจริง:

// ═══════════════════════════════
// SSG — Static at build time
// ═══════════════════════════════
// app/blog/[slug]/page.tsx

export async function generateStaticParams() {
  const posts = await fetchAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

export default async function BlogPost({ params }) {
  const post = await fetchPost(params.slug);  // Runs at BUILD time
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

// ═══════════════════════════════
// ISR — Static + Revalidate
// ═══════════════════════════════
// app/products/[id]/page.tsx

export const revalidate = 60;  // Regenerate every 60 seconds

export default async function ProductPage({ params }) {
  const product = await fetchProduct(params.id);  // Cached, revalidated
  return (
    <div>
      <h1>{product.name}</h1>
      <p>฿{product.price}</p>
    </div>
  );
}

// ═══════════════════════════════
// SSR — Fresh on every request
// ═══════════════════════════════
// app/dashboard/page.tsx

export const dynamic = 'force-dynamic';  // No caching

export default async function Dashboard() {
  const stats = await fetchStats();  // Runs on EVERY request
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Active users: {stats.activeUsers}</p>
    </div>
  );
}

// ═══════════════════════════════
// CSR — Client component
// ═══════════════════════════════
// app/chat/ChatWindow.tsx
'use client';  // ← This makes it CSR

import { useState, useEffect } from 'react';

export default function ChatWindow() {
  const [messages, setMessages] = useState([]);
  
  useEffect(() => {
    const ws = new WebSocket('wss://api.myapp.com/chat');
    ws.onmessage = (e) => setMessages(prev => [...prev, JSON.parse(e.data)]);
    return () => ws.close();
  }, []);

  return <div>{messages.map(m => <p key={m.id}>{m.text}</p>)}</div>;
}

⚛️ Modern Frameworks — React vs Vue vs Svelte

Framework Comparison

⚛️ React💚 Vue🔥 Svelte
Released2013 (Meta)2014 (Evan)2016 (Rich)
ApproachLibraryFrameworkCompiler
RenderingVirtual DOMVirtual DOMNo VDOM!
Bundle Size~40 KB~33 KB~2 KB
Learning⭐⭐ Medium⭐ Easy⭐ Easy
State MgmtRedux/ZustandPinia/VuexBuilt-in
Meta-frameworkNext.jsNuxtSvelteKit
Ecosystem⭐⭐⭐ Huge⭐⭐ Large⭐ Growing
Jobs⭐⭐⭐ Most⭐⭐ Many⭐ Growing
Performance⭐⭐ Good⭐⭐ Good⭐⭐⭐ Best
💡 2026 Recommendation:
Need jobs/ecosystem → React (Next.js)
Beginner friendly → Vue (Nuxt)
Performance-first → Svelte (SvelteKit)
All three are great — pick one and master it!

Same Component in Each Framework

วิธีที่ดีที่สุดในการเปรียบเทียบ framework คือดู component เดียวกันเขียนในแต่ละตัว — Counter component ง่ายๆ ที่มีปุ่มเพิ่ม-ลด จะเห็นชัดว่า Svelte เขียนสั้นที่สุด ไม่ต้อง import อะไรเลย:

// ═══════════════════════════════
// React — Counter Component
// ═══════════════════════════════
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
    </div>
  );
}
<!-- ═══════════════════════════════ -->
<!-- Vue — Counter Component        -->
<!-- ═══════════════════════════════ -->
<script setup>
import { ref } from 'vue';

const count = ref(0);
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="count++">+1</button>
    <button @click="count--">-1</button>
  </div>
</template>
<!-- ═══════════════════════════════ -->
<!-- Svelte — Counter Component     -->
<!-- ═══════════════════════════════ -->
<script>
  let count = 0;
</script>

<div>
  <p>Count: {count}</p>
  <button on:click={() => count++}>+1</button>
  <button on:click={() => count--}>-1</button>
</div>

<!-- ✨ ง่ายที่สุด! No imports, no hooks, no ref() -->

Meta-Frameworks — Full-Stack Capabilities

Meta-framework คือ framework ที่สร้างอยู่บน UI framework อีกที — ให้ความสามารถ full-stack เช่น routing, SSR, API routes, deployment และ optimization ต่างๆ แบบ out of the box ถ้าจะเริ่มโปรเจคจริงในปี 2026 ควรใช้ meta-framework มากกว่า UI framework เปล่าๆ:

⚛️
Next.js (React)
  • App Router (React Server Components)
  • SSR / SSG / ISR / CSR — all in one
  • API Routes (full-stack)
  • Image & font optimization
  • Middleware (edge functions)
  • Deploy: Vercel, self-hosted, Docker
💚
Nuxt (Vue)
  • Auto-imports (no import statements!)
  • File-based routing
  • SSR / SSG / SPA / Hybrid
  • Server API routes (Nitro)
  • 50+ official modules
  • Deploy: Vercel, Netlify, Cloudflare
🔥
SvelteKit (Svelte)
  • Smallest bundle sizes
  • File-based routing
  • SSR / SSG / SPA
  • Form actions (progressive enhancement)
  • Built-in adapters (Node, CF, Vercel)
  • Best DX (Developer Experience)
🚀
Astro (Multi-framework)
  • Zero JS by default (Islands Architecture)
  • Use React + Vue + Svelte together!
  • Content collections (MDX, Markdown)
  • Perfect for content-heavy sites
  • Best performance for static sites

⚡ Core Web Vitals — Google's Performance Metrics

The 3 Metrics That Matter

LCP
Largest Contentful Paint
"เวลาที่ content หลักโหลดเสร็จ"
✅ < 2.5s ⚠️ 2.5–4.0s ❌ > 4.0s
Triggers: <img> hero, <video> poster, CSS bg-image, large text
INP
Interaction to Next Paint
"เวลาตอบสนองเมื่อ user คลิก/พิมพ์"
✅ < 200ms ⚠️ 200–500ms ❌ > 500ms
Worst: Click → long JS, Type → heavy handlers, Scroll → re-renders
CLS
Cumulative Layout Shift
"หน้าจอกระโดดไปมาไหม?"
✅ < 0.1 ⚠️ 0.1–0.25 ❌ > 0.25
Causes: No width/height, late ads, font swap (FOUT), dynamic content
📊 CWV affects Google search ranking! 📏 Measure: PageSpeed Insights, Lighthouse, CrUX

Measuring Performance

ก่อนจะ optimize ต้องวัดก่อน! มีเครื่องมือหลายตัวที่ช่วยวัด Core Web Vitals ทั้งแบบ lab testing (Lighthouse, PageSpeed Insights) และ field data จาก user จริง (CrUX, web-vitals library) ควรใช้ทั้ง 2 แบบ เพราะ lab testing อาจไม่ตรงกับ real-world performance:

# ═══════════════════════════════
# Tools for Measuring
# ═══════════════════════════════

# 1. Google PageSpeed Insights (online)
# → https://pagespeed.web.dev/

# 2. Lighthouse (Chrome DevTools)
# → DevTools → Lighthouse tab → Analyze

# 3. Web Vitals JavaScript library
npm install web-vitals

# 4. Chrome DevTools → Performance tab
# → Record → Interact → Stop → Analyze flamechart
// ═══════════════════════════════
// Track Core Web Vitals in your app
// ═══════════════════════════════
import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics(metric) {
  const body = {
    name: metric.name,      // "LCP", "INP", "CLS"
    value: metric.value,    // 2500, 150, 0.05
    rating: metric.rating,  // "good", "needs-improvement", "poor"
    delta: metric.delta,
    id: metric.id,
    page: window.location.pathname,
  };

  // Send to your analytics
  navigator.sendBeacon('/api/analytics/vitals', JSON.stringify(body));
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

🖼️ Image Optimization — ปัญหา #1 ของ Performance

Before vs After

❌ Before
<img src="hero.jpg">
  • 5 MB JPEG
  • 4000×3000 px (displayed at 800×600)
  • No lazy loading
  • No responsive sizes
  • LCP: 8 seconds 💀
✅ After
  • 50 KB WebP (100× smaller!)
  • Right size for each screen
  • Lazy loaded (below fold)
  • Width/height prevents CLS
  • fetchpriority for LCP image
  • LCP: 0.8 seconds ⚡

Image Formats Comparison

FormatSizeQualitySupportUse Case
JPEG⭐⭐⭐⭐✅ AllPhotos (legacy)
PNG⭐⭐⭐✅ AllTransparency
WebP⭐⭐⭐⭐⭐⭐✅ 97%+Photos (modern)
AVIF⭐⭐⭐⭐⭐⭐⭐⚠️ 93%Best ratio
SVGTiny✅ AllIcons, logos
💡 Priority: AVIF > WebP > JPEG — Use <picture> for fallbacks

Next.js Image Component

❌ Without Code Splitting
bundle.js (2.5 MB)

Everything loaded upfront — user visits /home → downloads entire app

✅ With Code Splitting
layout (100 KB) home (50 KB) about (30 KB) dashboard (80 KB) admin (120 KB) chart (200 KB)

Visit /home → loads layout + home only (150 KB)


📦 Bundle Optimization — ส่ง JS น้อยลง

Code Splitting & Lazy Loading

📦 Bundle Size Budget
🎯 Targets (gzipped):
  • Initial JS: < 100 KB (ideal: < 50 KB)
  • Total JS: < 300 KB
  • CSS: < 50 KB
  • Images: < 200 KB per page
  • Fonts: < 100 KB (1-2 fonts max)
  • Total page weight: < 1 MB
📏 Common Sizes (min+gz):
  • React + ReactDOM: ~42 KB
  • Vue 3: ~33 KB
  • Svelte: ~2 KB ✅
  • Lodash (full): ~25 KB ❌ use lodash-es
  • Moment.js: ~72 KB ❌ use dayjs (~2 KB)
  • Axios: ~5 KB — or use native fetch
💡 Every KB matters on mobile (3G: 1 KB ≈ 10ms)
// ═══════════════════════════════
// React — Lazy Loading Components
// ═══════════════════════════════
import { lazy, Suspense } from 'react';

// ❌ Static import (included in main bundle)
// import Dashboard from './Dashboard';
// import AdminPanel from './AdminPanel';
// import ChartLibrary from './ChartLibrary';

// ✅ Dynamic import (separate chunks)
const Dashboard = lazy(() => import('./Dashboard'));
const AdminPanel = lazy(() => import('./AdminPanel'));
const ChartLibrary = lazy(() => import('./ChartLibrary'));

export default function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />  {/* Always loaded */}
      
      <Route
        path="/dashboard"
        element={
          <Suspense fallback={<LoadingSpinner />}>
            <Dashboard />    {/* Loaded on navigate */}
          </Suspense>
        }
      />
      
      <Route
        path="/admin"
        element={
          <Suspense fallback={<LoadingSpinner />}>
            <AdminPanel />   {/* Loaded on navigate */}
          </Suspense>
        }
      />
    </Routes>
  );
}

// ═══════════════════════════════
// Next.js — Dynamic Import
// ═══════════════════════════════
import dynamic from 'next/dynamic';

// Heavy component loaded only when needed
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false,  // Don't render on server (client-only)
});

// Conditional loading
const AdminTools = dynamic(() => import('@/components/AdminTools'), {
  loading: () => null,
});

export default function Dashboard({ user }) {
  return (
    <div>
      <h1>Dashboard</h1>
      <HeavyChart data={chartData} />
      {user.role === 'admin' && <AdminTools />}
    </div>
  );
}

ใน React ใช้ lazy() + Suspense สำหรับ component-level code splitting ส่วน Next.js ใช้ dynamic() import ที่รองรับ SSR control ด้วย ดูตัวอย่าง:

Tree Shaking — ลบ code ที่ไม่ใช้

Tree shaking คือการที่ bundler (Webpack, Rollup, Vite) วิเคราะห์ว่า code ส่วนไหนที่ import มาแต่ไม่ได้ใช้จริง แล้วลบออกจาก bundle อัตโนมัติ แต่มันจะทำงานได้ก็ต่อเมื่อคุณ import แบบ named import (ไม่ใช่ import ทั้ง library):

// ═══════════════════════════════
// Tree Shaking — Dead code elimination
// ═══════════════════════════════

// ❌ Import ทั้ง library
import _ from 'lodash';  // 71 KB ทั้งหมด!
_.debounce(fn, 300);      // ใช้แค่ 1 function

// ✅ Import เฉพาะที่ใช้
import debounce from 'lodash/debounce';  // 1.5 KB!
// หรือ
import { debounce } from 'lodash-es';   // ESM tree-shakeable

// ❌ Import entire icon library
import { icons } from 'lucide-react';   // 500+ icons loaded!

// ✅ Import specific icons
import { Search, Menu, X } from 'lucide-react';  // Only 3 icons

// ═══════════════════════════════
// Check bundle size
// ═══════════════════════════════

// 1. bundlephobia.com — check BEFORE installing
// "lodash" = 71.5 KB (minified + gzipped: 25.2 KB)
// "lodash-es" = tree-shakeable!
// "date-fns" (2 KB per function) vs "moment" (72 KB total)

// 2. Webpack Bundle Analyzer
// npm install --save-dev webpack-bundle-analyzer
// → Visual treemap of your bundle

// 3. Next.js built-in
// next build → shows route sizes automatically

Bundle Size Budget

ทุกโปรเจคควรมี performance budget — กำหนดขนาดสูงสุดที่ยอมรับได้ และ fail CI/CD ถ้าเกิน ช่วยป้องกันไม่ให้ bundle size ค่อยๆ โตจนควบคุมไม่ได้ (performance regression):

1. Browser Cache (HTTP Headers)
Static assets (JS, CSS, images):
Cache-Control: public, max-age=31536000, immutable
→ Cache 1 year, use content hash: main.a1b2c3d4.js
HTML pages: Cache-Control: no-cache → Always check with server
API responses: Cache-Control: private, max-age=60 → Cache 60s, user-specific
2. Service Worker (Offline-first)
Request Service Worker Cache? ✅/❌ Return / Fetch
Cache First — cache, fallback network Network First — network, fallback cache Stale While Revalidate — return cache, update bg Cache Only — offline content
3. CDN Cache
🇹🇭 Bangkok 🇸🇬 CDN Edge (cached!) 🌐 Origin

Providers: CloudFlare, CloudFront, Fastly — Reduce latency from 200ms to 20ms


🚀 Caching Strategies

Browser Caching

📱 PWA — Progressive Web App

Web App ที่ทำงานเหมือน Native App

📥
Installable
เพิ่มลง home screen
📡
Offline
ทำงานได้แม้ไม่มี internet
🔔
Push Notifications
แจ้งเตือนได้
🔄
Background Sync
sync เมื่อกลับมาออนไลน์
Fast
cached assets, instant load
📐
Responsive
ทุก screen size
Requirements: HTTPS · Service Worker · Web App Manifest · Responsive design
Success stories: Twitter Lite (+65% pages/session) · Pinterest (+60% engagement) · Starbucks (2× DAU) · Uber (works on 2G!)

Next.js Caching

Next.js มีระบบ caching built-in ที่ทรงพลังมาก — fetch() ใน Server Components จะ cache โดย default คุณสามารถกำหนด revalidation time หรือ cache tag สำหรับ on-demand revalidation ผ่าน webhook:

// ═══════════════════════════════
// Next.js — Built-in Caching
// ═══════════════════════════════

// 1. fetch() caching (Server Components)
async function getProducts() {
  // Cached by default (static)
  const res = await fetch('https://api.example.com/products');
  return res.json();
}

async function getUser(id: string) {
  // Revalidate every 60 seconds
  const res = await fetch(`https://api.example.com/users/${id}`, {
    next: { revalidate: 60 },
  });
  return res.json();
}

async function getNotifications() {
  // No cache (always fresh)
  const res = await fetch('https://api.example.com/notifications', {
    cache: 'no-store',
  });
  return res.json();
}

// 2. Route segment caching
// app/products/page.tsx
export const revalidate = 3600;  // Revalidate every hour
export const dynamic = 'force-static';  // Force SSG

// 3. On-demand revalidation (webhook)
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';

export async function POST(request: Request) {
  const { path, tag } = await request.json();
  
  if (path) revalidatePath(path);         // Revalidate specific page
  if (tag) revalidateTag(tag);            // Revalidate by tag
  
  return Response.json({ revalidated: true });
}

📱 Progressive Web App (PWA)

PWA คือเทคโนโลยีที่ทำให้เว็บทำงานได้เหมือน native app — install ลง home screen ได้, ทำงาน offline ได้, ส่ง push notification ได้ และ sync data เมื่อกลับมาออนไลน์ ทั้งหมดนี้โดยไม่ต้องผ่าน App Store เลย บริษัทอย่าง Twitter, Pinterest, Starbucks ใช้ PWA แล้วเห็นผลลัพธ์ที่น่าทึ่ง:

📋 Frontend Performance Checklist
🖼️ Images
  • Use WebP/AVIF format
  • Responsive sizes (srcset)
  • Lazy load below-fold images
  • Set width/height (prevent CLS)
  • Priority hint for LCP image
  • Use next/image or auto-optimization
📦 JavaScript
  • Code splitting (route-based)
  • Lazy load heavy components
  • Tree shake unused imports
  • Avoid large libraries (lodash → lodash-es)
  • Bundle budget < 100 KB initial
  • Analyze with bundle-analyzer
🎨 CSS
  • Inline critical CSS
  • Async load non-critical CSS
  • Remove unused CSS (PurgeCSS/Tailwind)
  • Minimize CSS-in-JS
🔤 Fonts
  • Self-host fonts
  • font-display: swap/optional
  • Preload critical fonts
  • Subset fonts
  • Max 2 font families
🌐 Network
  • CDN for static assets
  • HTTP/2 or HTTP/3
  • Gzip/Brotli compression
  • Cache headers (immutable)
  • Preconnect & DNS prefetch
⚡ Rendering & 📊 Monitoring
  • SSR/SSG for content pages
  • CSR for interactive features
  • React Server Components
  • Track Core Web Vitals
  • Lighthouse CI
  • Performance budget alerts

การสร้าง PWA ต้องมี 3 องค์ประกอบหลัก: HTTPS, Web App Manifest (metadata สำหรับ install) และ Service Worker (caching + offline) มาดู manifest.json ก่อน:

// manifest.json — PWA metadata
{
  "name": "My Awesome App",
  "short_name": "MyApp",
  "description": "A progressive web application",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0D1229",
  "theme_color": "#2196F3",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}
// ═══════════════════════════════
// Service Worker — Caching Strategy
// ═══════════════════════════════
// sw.js

const CACHE_NAME = 'myapp-v1';
const STATIC_ASSETS = [
  '/',
  '/offline.html',
  '/css/app.css',
  '/js/app.js',
  '/icons/icon-192.png',
];

// Install — cache static assets
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
  );
});

// Fetch — Stale While Revalidate
self.addEventListener('fetch', event => {
  // Skip non-GET requests
  if (event.request.method !== 'GET') return;

  event.respondWith(
    caches.match(event.request).then(cached => {
      // Return cached + fetch fresh in background
      const fetchPromise = fetch(event.request).then(response => {
        const cache = caches.open(CACHE_NAME);
        cache.then(c => c.put(event.request, response.clone()));
        return response;
      }).catch(() => {
        // Offline — return offline page
        if (event.request.destination === 'document') {
          return caches.match('/offline.html');
        }
      });

      return cached || fetchPromise;
    })
  );
});

// Activate — clean old caches
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(
        keys
          .filter(key => key !== CACHE_NAME)
          .map(key => caches.delete(key))
      )
    )
  );
});

🎨 CSS & Font Optimization

Critical CSS

<!-- ═══════════════════════════════ -->
<!-- Critical CSS — Inline above-fold styles -->
<!-- ═══════════════════════════════ -->

<head>
  <!-- ✅ Critical CSS inlined (above-fold styles) -->
  <style>
    /* Only styles needed for first viewport */
    body { margin: 0; font-family: system-ui; }
    .hero { height: 100vh; display: flex; align-items: center; }
    .nav { position: fixed; top: 0; width: 100%; }
  </style>

  <!-- ✅ Non-critical CSS loaded async -->
  <link rel="preload" href="/css/app.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/css/app.css"></noscript>
</head>

Font Optimization

Custom fonts เป็นสาเหตุหลักของ CLS (layout shift) — เมื่อ font โหลดเสร็จ text จะ re-render ด้วยขนาดที่ต่างจาก fallback font ทำให้ layout กระโดด ใช้ font-display property เพื่อควบคุมพฤติกรรม:

/* ═══════════════════════════════ */
/* Font Loading — Prevent CLS     */
/* ═══════════════════════════════ */

/* ❌ Bad: Flash of Unstyled Text (FOUT) / Flash of Invisible Text (FOIT) */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  /* No font-display → browser default (usually FOIT = invisible text!) */
}

/* ✅ Good: font-display: swap */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  font-display: swap;     /* Show fallback → swap when loaded */
}

/* ✅ Better: font-display: optional */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  font-display: optional;  /* Use if cached, skip if not */
}
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/inter-var.woff2" 
      as="font" type="font/woff2" crossorigin>
// ═══════════════════════════════
// Next.js — Automatic font optimization
// ═══════════════════════════════
import { Inter, Noto_Sans_Thai } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
});

const notoThai = Noto_Sans_Thai({
  subsets: ['thai'],
  display: 'swap',
  variable: '--font-thai',
});

// Next.js automatically:
// ├── Self-hosts fonts (no Google Fonts request!)
// ├── Preloads critical fonts
// ├── Generates optimal @font-face
// └── Prevents layout shift

ถ้าใช้ Next.js, next/font จะจัดการ font optimization ให้ทั้งหมด — self-host fonts, preload, optimal @font-face, prevent layout shift โดยอัตโนมัติ:

Tailwind CSS — Performance Tips

Tailwind CSS v3+ มี PurgeCSS built-in ซึ่งจะวิเคราะห์ว่าคุณใช้ class ไหนจริงๆ แล้วลบ class ที่ไม่ได้ใช้ออก ทำให้ CSS จาก 3 MB เหลือเพียง 3-10 KB:

// tailwind.config.js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx}',  // Only scan used files
    './components/**/*.{js,ts,jsx,tsx}',
  ],
  // Tailwind v3+ automatically removes unused CSS (PurgeCSS built-in)
  // Result: 3-10 KB CSS instead of 3 MB!
};

📋 Performance Optimization Checklist

┌──────────────────────────────────────────────────────┐
│        Frontend Performance Checklist                 │
│                                                        │
│  🖼️ Images:                                           │
│  ├── [x] Use WebP/AVIF format                         │
│  ├── [x] Responsive sizes (srcset)                    │
│  ├── [x] Lazy load below-fold images                  │
│  ├── [x] Set width/height (prevent CLS)               │
│  ├── [x] Priority hint for LCP image                  │
│  └── [x] Use next/image or similar auto-optimization  │
│                                                        │
│  📦 JavaScript:                                       │
│  ├── [x] Code splitting (route-based)                 │
│  ├── [x] Lazy load heavy components                   │
│  ├── [x] Tree shake unused imports                    │
│  ├── [x] Avoid large libraries (lodash → lodash-es)   │
│  ├── [x] Bundle budget < 100 KB initial               │
│  └── [x] Analyze bundle with webpack-bundle-analyzer  │
│                                                        │
│  🎨 CSS:                                              │
│  ├── [x] Inline critical CSS                          │
│  ├── [x] Async load non-critical CSS                  │
│  ├── [x] Remove unused CSS (PurgeCSS/Tailwind)        │
│  └── [x] Minimize CSS-in-JS (prefer Tailwind/CSS)     │
│                                                        │
│  🔤 Fonts:                                            │
│  ├── [x] Self-host fonts (no external requests)        │
│  ├── [x] Use font-display: swap/optional               │
│  ├── [x] Preload critical fonts                        │
│  ├── [x] Subset fonts (latin only if no Thai)          │
│  └── [x] Max 2 font families                          │
│                                                        │
│  🌐 Network:                                          │
│  ├── [x] CDN for static assets                        │
│  ├── [x] HTTP/2 or HTTP/3                             │
│  ├── [x] Gzip/Brotli compression                      │
│  ├── [x] Cache headers (immutable for hashed assets)   │
│  ├── [x] Preconnect to external origins               │
│  └── [x] DNS prefetch for third-party domains         │
│                                                        │
│  ⚡ Rendering:                                        │
│  ├── [x] SSR/SSG for content pages (SEO + LCP)        │
│  ├── [x] CSR only for interactive-heavy features      │
│  ├── [x] ISR for dynamic-but-cacheable content        │
│  ├── [x] Streaming SSR for large pages                │
│  └── [x] React Server Components (reduce JS sent)     │
│                                                        │
│  📊 Monitoring:                                       │
│  ├── [x] Track Core Web Vitals (web-vitals lib)       │
│  ├── [x] Lighthouse CI in GitHub Actions               │
│  ├── [x] Real User Monitoring (RUM)                   │
│  └── [x] Performance budget alerts                    │
│                                                        │
└──────────────────────────────────────────────────────┘

🔑 Key Takeaways

สรุปสิ่งที่สำคัญที่สุดจากทั้งบทความ — 10 ข้อที่ทุก frontend developer ควรจำ:

  1. SSR สำหรับ SEO + LCP, CSR สำหรับ interactivity → ใช้ Next.js/Nuxt/SvelteKit ที่รวมทุกอย่าง
  2. Core Web Vitals = Google ranking factor → LCP < 2.5s, INP < 200ms, CLS < 0.1
  3. Images = ปัญหา #1 → WebP/AVIF, responsive sizes, lazy load, width/height attributes
  4. Code splitting = ลด initial load → Lazy import components, route-based splitting
  5. Tree shaking = ลบ dead codeimport { x } from 'lib' แทน import lib
  6. Bundle budget < 100 KB → วัดทุก library ก่อน install (bundlephobia.com)
  7. CDN + Cache = เร็วขึ้น 10x → Static assets cache 1 year, HTML no-cache
  8. Font optimization = ป้องกัน CLS → Self-host, font-display: swap, max 2 fonts
  9. PWA = native-like experience → Offline, installable, push notifications
  10. วัด ก่อน optimize → ใช้ Lighthouse, web-vitals, RUM — อย่าเดา!

*📚 DevOps Series ทั้งหมด:*

  1. *Git Branching* | 2. *CI/CD Pipeline* | 3. *Docker vs VMs*
  2. *API Lifecycle* | 5. *Kubernetes* | 6. *Networking*
  3. *Infrastructure as Code* | 8. *Monitoring & Observability*
  4. *Linux & Shell* | 10. *DevSecOps & Security*
  5. *Testing Strategies* | 12. *GitOps & ArgoCD*
  6. *Cloud Architecture* | 14. *SRE*
  7. *GitHub Actions & GitHub Flow* | 16. *Automated Testing with GitHub*
  8. *Database Design & SQL* | 18. *Authentication & Authorization*
  9. *Web Architecture & Design Patterns*
  10. *Frontend Performance & Modern Frameworks* ← 📍 อยู่ตรงนี้
บทความจากซีรีส์ DevOps & Vibe Coding 2026
← Previous
Web Architecture & Design Patterns — ออกแบบระบบให้ Scale ได้และดูแลง่าย
Next →
Deployment & Hosting Strategies — จาก Code สู่ Production