🐕 ทำไม Frontend Performance สำคัญ?
ในโลกที่ทุกคนใช้มือถือเข้าเว็บ ความเร็วของเว็บไซต์ไม่ใช่แค่ "nice to have" แต่เป็นสิ่งที่ส่งผลโดยตรงต่อรายได้ อัตราการ bounce และ SEO ranking ของคุณ Google ใช้ Core Web Vitals เป็นปัจจัยในการจัดอันดับ ซึ่งหมายความว่าเว็บที่ช้าจะถูกลดอันดับในผลการค้นหาโดยอัตโนมัติ
ลองดูตัวเลขจริงที่ Google และบริษัทชั้นนำรายงาน — จะเห็นว่าแม้แค่ 100ms ก็ส่งผลกระทบมหาศาล:
| Load Time | Bounce Rate | Revenue 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 |
- 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
🖥️ Rendering Strategies — CSR vs SSR vs SSG vs ISR
The Big Picture
Server → Empty HTML + JS bundle → Browser downloads JS → Executes → Renders
Server → Renders HTML → Sends full page → Browser shows → Downloads JS → Hydrates
Build time → Pre-render ALL pages → Deploy → Serve static HTML from CDN
Like SSG but pages regenerate in background — serve stale → rebuild → serve fresh
Comparison Table
เปรียบเทียบทั้ง 4 strategies แบบ side-by-side — ดูง่ายๆ ว่าแต่ละตัวเด่นตรงไหน และเหมาะกับ use case แบบไหน:
| CSR | SSR | SSG | ISR | |
|---|---|---|---|---|
| First Paint | 🐢 Slow | ⚡ Fast | ⚡ Fast | ⚡ Fast |
| SEO | ❌ | ✅ | ✅ | ✅ |
| Interactivity | ⚡ Fast | ⚠️ Wait | ⚠️ Wait | ⚠️ Wait |
| Server Load | None | High | None | Low |
| Freshness | ✅ Live | ✅ Live | ❌ Build | ⚡ Near |
| Hosting Cost | 💰 | 💰💰💰 | 💰 | 💰💰 |
| Build Time | Fast | N/A | 🐢 Slow | Fast |
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 | |
|---|---|---|---|
| Released | 2013 (Meta) | 2014 (Evan) | 2016 (Rich) |
| Approach | Library | Framework | Compiler |
| Rendering | Virtual DOM | Virtual DOM | No VDOM! |
| Bundle Size | ~40 KB | ~33 KB | ~2 KB |
| Learning | ⭐⭐ Medium | ⭐ Easy | ⭐ Easy |
| State Mgmt | Redux/Zustand | Pinia/Vuex | Built-in |
| Meta-framework | Next.js | Nuxt | SvelteKit |
| Ecosystem | ⭐⭐⭐ Huge | ⭐⭐ Large | ⭐ Growing |
| Jobs | ⭐⭐⭐ Most | ⭐⭐ Many | ⭐ Growing |
| Performance | ⭐⭐ Good | ⭐⭐ Good | ⭐⭐⭐ Best |
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 เปล่าๆ:
- 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
- Auto-imports (no import statements!)
- File-based routing
- SSR / SSG / SPA / Hybrid
- Server API routes (Nitro)
- 50+ official modules
- Deploy: Vercel, Netlify, Cloudflare
- Smallest bundle sizes
- File-based routing
- SSR / SSG / SPA
- Form actions (progressive enhancement)
- Built-in adapters (Node, CF, Vercel)
- Best DX (Developer Experience)
- 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
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
<img src="hero.jpg">
- 5 MB JPEG
- 4000×3000 px (displayed at 800×600)
- No lazy loading
- No responsive sizes
- LCP: 8 seconds 💀
- 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
| Format | Size | Quality | Support | Use Case |
|---|---|---|---|---|
| JPEG | ⭐⭐ | ⭐⭐ | ✅ All | Photos (legacy) |
| PNG | ⭐ | ⭐⭐⭐ | ✅ All | Transparency |
| WebP | ⭐⭐⭐ | ⭐⭐⭐ | ✅ 97%+ | Photos (modern) |
| AVIF | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⚠️ 93% | Best ratio |
| SVG | Tiny | ∞ | ✅ All | Icons, logos |
Next.js Image Component
Everything loaded upfront — user visits /home → downloads entire app
Visit /home → loads layout + home only (150 KB)
📦 Bundle Optimization — ส่ง JS น้อยลง
Code Splitting & Lazy Loading
- 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
- 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
// ═══════════════════════════════
// 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):
Cache-Control: public, max-age=31536000, immutable→ Cache 1 year, use content hash: main.a1b2c3d4.js
Cache-Control: no-cache → Always check with serverCache-Control: private, max-age=60 → Cache 60s, user-specificProviders: CloudFlare, CloudFront, Fastly — Reduce latency from 200ms to 20ms
🚀 Caching Strategies
Browser Caching
Web App ที่ทำงานเหมือน Native App
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 แล้วเห็นผลลัพธ์ที่น่าทึ่ง:
- 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
- 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
- Inline critical CSS
- Async load non-critical CSS
- Remove unused CSS (PurgeCSS/Tailwind)
- Minimize CSS-in-JS
- Self-host fonts
- font-display: swap/optional
- Preload critical fonts
- Subset fonts
- Max 2 font families
- CDN for static assets
- HTTP/2 or HTTP/3
- Gzip/Brotli compression
- Cache headers (immutable)
- Preconnect & DNS prefetch
- 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 ควรจำ:
- SSR สำหรับ SEO + LCP, CSR สำหรับ interactivity → ใช้ Next.js/Nuxt/SvelteKit ที่รวมทุกอย่าง
- Core Web Vitals = Google ranking factor → LCP < 2.5s, INP < 200ms, CLS < 0.1
- Images = ปัญหา #1 → WebP/AVIF, responsive sizes, lazy load, width/height attributes
- Code splitting = ลด initial load → Lazy import components, route-based splitting
- Tree shaking = ลบ dead code →
import { x } from 'lib'แทนimport lib - Bundle budget < 100 KB → วัดทุก library ก่อน install (bundlephobia.com)
- CDN + Cache = เร็วขึ้น 10x → Static assets cache 1 year, HTML no-cache
- Font optimization = ป้องกัน CLS → Self-host, font-display: swap, max 2 fonts
- PWA = native-like experience → Offline, installable, push notifications
- วัด ก่อน optimize → ใช้ Lighthouse, web-vitals, RUM — อย่าเดา!
*📚 DevOps Series ทั้งหมด:*
- *Git Branching* | 2. *CI/CD Pipeline* | 3. *Docker vs VMs*
- *API Lifecycle* | 5. *Kubernetes* | 6. *Networking*
- *Infrastructure as Code* | 8. *Monitoring & Observability*
- *Linux & Shell* | 10. *DevSecOps & Security*
- *Testing Strategies* | 12. *GitOps & ArgoCD*
- *Cloud Architecture* | 14. *SRE*
- *GitHub Actions & GitHub Flow* | 16. *Automated Testing with GitHub*
- *Database Design & SQL* | 18. *Authentication & Authorization*
- *Web Architecture & Design Patterns*
- *Frontend Performance & Modern Frameworks* ← 📍 อยู่ตรงนี้