ข้ามไปยังเนื้อหา

Performance & Deploy

เพราะ Svelte เป็น compiler (บทเรียนแรกสุด) performance baseline จึงดีอยู่แล้ว — ไม่มี virtual DOM runtime ให้ ship และ component compile เป็น code update ที่พุ่งตรงจุด คุณเสียเวลาสู้กับ bundle size น้อยกว่าแอปที่ใช้ runtime framework มาก — overhead ของ framework ที่ติดมากับ code คุณมีน้อย

แต่นั่นไม่ได้ทำให้ performance เกิดขึ้นเอง code ของคุณเอง, image, library ภายนอก และการเลือก data-loading ยังสำคัญ แต่จุดเริ่มต้นนั้นเบา และเครื่องมือด้านล่างช่วยให้คุณรักษาข้อได้เปรียบนั้นไว้

ก่อน optimize ต้องวัด vite build รายงานขนาด output และ ecosystem ของ Vite มี bundle visualizer (เช่น rollup-plugin-visualizer) ที่โชว์ว่าอะไรกินพื้นที่ — ปกติคือ dependency ภายนอกที่หนัก ไม่ใช่ตัว Svelte เอง

Terminal window
npm run build # vite build — prints chunk sizes

ระวัง: date/utility library ตัวใหญ่ที่ import มาทั้งก้อน, icon set ที่ดึงมาทั้งชุด หรือ component ที่ load แบบ eager ทั้งที่ทำ lazy ได้ SvelteKit code-split ต่อ route ให้อัตโนมัติ หน้าที่หนักจึงไม่ถ่วงส่วนที่เหลือของแอป

แอป SvelteKit deploy ผ่าน adapter ที่แปลง build ให้แพลตฟอร์มเฉพาะ คุณตั้งใน svelte.config.js:

import adapter from '@sveltejs/adapter-auto';
export default {
kit: {
adapter: adapter(),
},
};
adapterdeploy ไปที่
adapter-autoตรวจจับแพลตฟอร์มยอดนิยม (Vercel, Netlify, Cloudflare…) อัตโนมัติ
adapter-nodeNode server ที่รันยาว ๆ ที่คุณ host เอง
adapter-cloudflareCloudflare Pages / Workers (edge)
adapter-staticเว็บ static ล้วน — HTML/CSS/JS บน CDN ใดก็ได้

adapter-auto คือ default แบบ zero-config คุณเปลี่ยนไปใช้ adapter เฉพาะเมื่อรู้ target ของคุณ (และต้องใช้ option ของตัวเอง)

flowchart LR
  build["vite build (SvelteKit)"] --> adapter["adapter"]
  adapter --> node["Node server"]
  adapter --> edge["Cloudflare / edge"]
  adapter --> static["static CDN"]
build เดียว หลาย deploy target ผ่าน adapter

การตัดสินใจเรื่อง deployment สะท้อนการเลือก rendering:

  • static — ด้วย adapter-static (หรือ export const prerender = true บน route ที่ prerender ได้) หน้าถูก build เป็น HTML ตอน build time และ serve จาก CDN ใดก็ได้ เหมาะกับ content ที่เหมือนกันสำหรับทุกคน ไม่ต้องมี server
  • server — ด้วย runtime adapter (node/cloudflare/…) หน้า render on demand สำหรับ data ตอน request-time (ผู้ใช้ที่ login อยู่, data สด) ผสมได้: prerender หน้า marketing, render dashboard on demand

ตั้ง export const prerender = true บน route ที่เป็น static ได้ แล้วปล่อยที่เหลือให้ render on demand — วิธี hybrid เดียวกันที่ทำให้เว็บส่วนใหญ่ cache ได้ ส่วนอีกไม่กี่ route ยังคง dynamic

theme “เล็กโดย default” ของบทนี้ไม่ได้มีแค่เรื่อง compiler — มันขยายไปถึง standard library ที่ Svelte ship มาคู่กันด้วย svelte/reactivity (SvelteMap, SvelteSet, SvelteDate และเพื่อน ๆ) ให้ reactive collection แบบ rune-native โดยไม่ต้องหยิบ writable store มาห่อ ซึ่งสำคัญตรงนี้เพราะมันคือของที่มี shape เป็น dependency น้อยลงหนึ่งชิ้นที่ติดมาใน bundle สำหรับงานที่ runtime ทำให้แบบ native อยู่แล้ว บท Built-in Stores ของโมดูล state & stores พิสูจน์ความต่างของ reactivity เทียบกับ Map ธรรมดาแบบละเอียด takeaway ของบทนี้ง่ายกว่านั้น: การหยิบเวอร์ชัน rune-native แทนเวอร์ชันที่ห่อด้วย store เองก็เป็นทางเลือกด้าน performance เล็ก ๆ ที่ได้มาฟรี ไม่ใช่ trade-off

ทุกอย่างในบทนี้สืบเนื่องมาจากข้อเท็จจริงหนึ่งที่วางไว้ตั้งแต่บทเรียนแรกสุดของคอร์สนี้: compiler ของ Svelte ทำการวิเคราะห์ที่แพงไปแล้วตอน build time ดังนั้น “งาน performance” ที่นี่ส่วนใหญ่คือการ ไม่ทำลาย ข้อได้เปรียบนั้น — ไม่ import dependency ที่หนักมาทั้งก้อน, ไม่เลือก server adapter สำหรับ content ที่ static ได้ (และดังนั้น serve ฟรีจาก CDN โดยไม่ต้องใช้ compute เลย), ไม่หยิบ collection ที่ห่อด้วย store ในที่ที่ rune-native ตัวหนึ่งทำงานเดียวกันได้โดยมีของรอบ ๆ น้อยกว่า ไม่มีเครื่องมือไหนในบทนี้ (bundle visualizer, adapter, svelte/reactivity) สร้างความเร็วขึ้นมาเอง ส่วนใหญ่มันแค่ป้องกันไม่ให้คุณใช้ budget ที่ compiler มอบให้ฟรีไปโดยไม่ตั้งใจ

ได้อะไรประโยชน์ต้นทุน
adapter-autozero-config ทำงานได้เลยบนแพลตฟอร์มยอดนิยมไม่เปิด option เฉพาะแพลตฟอร์ม (การจัดการ env, edge binding) ที่ adapter เฉพาะให้ได้
adapter-static / prerender = truehost ฟรีบน CDN ใดก็ได้ ไม่มีต้นทุน compute ต่อ request เลย (พิสูจน์ด้วยการตรวจ build จริงในโมดูล SvelteKit)serve data ตอน request-time ไม่ได้ — route ที่ต้องการผู้ใช้ที่ login อยู่หรือ data สด prerender ไม่ได้เลย
adapter-nodeควบคุมเต็มที่ รันได้ทุกที่ที่ Node รัน (พิสูจน์ด้วย build จริงในโมดูล SvelteKit)คุณเป็นเจ้าของ process เอง — env var อย่าง ORIGIN พิสูจน์แล้วในบทนั้น แทนที่จะมี platform จัดการให้
svelte/reactivity แทน store wrapperrune-native ไม่มี code รูปร่าง dependency เพิ่มสำหรับ behavior เดียวกันครอบคลุมแค่ type ของ collection ที่มัน ship มา (Map, Set, Date, URL, …) — shape reactive ที่ custom เต็มรูปแบบยังต้องใช้ $state/store
  • “prerendering (adapter-static/prerender = true) เป็นแค่ toggle ด้าน performance ที่เปิดบน route ไหนก็ได้” มันคือข้อจำกัดเชิงความถูกต้อง ไม่ใช่แค่เรื่องความเร็ว — route ที่ prerender ถูก render ครั้งเดียวตอน build time ดังนั้น route ที่ต้องการ data ตอน request-time (cookie, ผู้ใช้ที่ login อยู่, data สด) prerender ไม่ได้เลย ไม่ว่าจะเร็วขึ้นแค่ไหนก็ตาม
  • “$env/static/private กับ $env/dynamic/private ใช้แทนกันได้ — เลือกอันไหนก็ได้” มันถูก resolve คนละเวลากันเลย (build time เทียบ request time) — พิสูจน์ด้วย output จริงของ build ที่ตรวจสอบในโมดูล SvelteKit; การสลับตัวหนึ่งเป็นอีกตัวเปลี่ยนแบบเงียบ ๆ ว่าค่านั้นเปลี่ยนได้เมื่อไรโดยไม่ต้อง rebuild
  • “ใช้ SvelteMap จาก svelte/reactivity แทน Map ธรรมดาที่ห่อด้วย store เป็นแค่ความชอบสไตล์” มันคือความต่างจริงด้าน bundle และ reactivity-model — ไม่มีกลไก subscription ของ store, reactivity ระดับ method แทนการแทนที่ทั้ง object — ครอบคลุมเต็ม ๆ ในบท built-in-stores ที่ลิงก์ไว้ด้านบน

เวลารีวิว code ที่เกี่ยวกับ deployment และไวต่อ performance ให้เช็ค:

  • export const prerender = true ถูกตั้งบน route ที่อ่าน data ตอน request-time จริง ๆ หรือเปล่า (cookies(), session, url.searchParams ที่ใช้เพื่ออะไรอื่นนอกจาก static generation) — เป็น correctness bug ไม่ใช่แค่ optimization ที่พลาด?
  • server code อ่าน secret ผ่าน $env/dynamic/private ทั้งที่ $env/static/private พอแล้ว (และจะถูก inline เร็วกว่าเล็กน้อย) — หรือกลับกัน อ่าน $env/static/private สำหรับค่าที่ตั้งใจให้เปลี่ยนได้ต่อ deployment โดยไม่ต้อง rebuild จริง ๆ หรือเปล่า?
  • Map/Set ถูกห่อด้วย writable store ที่เขียนเอง ทั้งที่ SvelteMap/SvelteSet ของ svelte/reactivity ให้ reactivity เดียวกันด้วย code น้อยกว่าและไม่มี overhead ของ store subscription หรือเปล่า?
  • adapter เฉพาะ (adapter-node, adapter-cloudflare) ถูกสมมติว่า “ทำงานได้เลย” เหมือน adapter-auto — พลาด setup เฉพาะแพลตฟอร์ม (env var, binding) ที่ adapter เฉพาะนั้นต้องการจริง ๆ พิสูจน์แล้วในโมดูล SvelteKit สำหรับ ORIGIN ของ adapter-node หรือเปล่า?

🐛 หา bug ในโค้ดนี้

src/routes/dashboard/+page.server.ts
export const prerender = true;
export async function load({ cookies }) {
const session = cookies.get('session_id');
return { user: await getUserBySession(session) };
}
เฉลย

prerender = true บอก SvelteKit ให้ render route นี้ ครั้งเดียว ตอน build time แล้ว serve HTML แบบ static เดียวกันให้ทุกคนตลอดไป — แต่ load function อ่าน cookies.get('session_id') ค่าที่ต่างกันสำหรับผู้เยี่ยมชมแต่ละคนและมีอยู่แค่ตอน request time เท่านั้น ไม่มี “request” ระหว่าง build เลย ดังนั้นนี่ไม่ใช่ trade-off ด้าน performance ที่ subtle — มันคือ route ที่ทำสิ่งที่ตั้งใจไม่ได้เชิงโครงสร้าง ขึ้นอยู่กับสิ่งที่อ่านเป๊ะ ๆ prerendering ของ SvelteKit จะทำให้ build fail ด้วย error หรือ (แย่กว่านั้น) bake data ของ request ใดหนึ่งลงใน static output แบบเงียบ ๆ ที่ผู้เยี่ยมชมทุกคนหลังจากนั้นได้รับเหมือนกันหมด

วิธีแก้คือลบ prerender ออกไปเลย (หรือตั้งเป็น false) — dashboard ต่อผู้ใช้แต่ละคนคือฝั่ง “server” ของการแบ่ง static-vs-server ที่บทนี้อธิบายเป๊ะ ไม่ใช่ตัวเลือกของฝั่ง “static” ไม่ว่า static จะเร็วกว่าแค่ไหนก็ตาม

ทำไมแอป Svelte เล็กโดย default?
เมื่อวัดแล้วเจอ bundle ใหญ่ ปกติอะไรคือตัวการ?
SvelteKit adapter มีไว้ทำอะไร?
ตรงนี้ผิดตรงไหน?
// src/routes/dashboard/+page.server.ts
export const prerender = true;

export async function load({ cookies }) {
  return { user: await getUserBySession(cookies.get('session_id')) };
}
จะทำให้บาง route เป็น static และบาง route render on demand ได้อย่างไร?
SvelteMap จาก svelte/reactivity ให้อะไรเหนือกว่า Map ธรรมดาที่ห่อด้วย writable store?
import { SvelteMap } from 'svelte/reactivity';
let cache = new SvelteMap();