เข้าใจ Hydration ใน React และ Next.js: ทำไมเว็บเห็นเร็วแต่ยังกดไม่ได้
ทำความเข้าใจขั้นตอนที่ React ทำให้ HTML จากเซิร์ฟเวอร์กลายเป็นหน้าเว็บที่โต้ตอบได้ พร้อมสาเหตุของ hydration mismatch และแนวทางลดอาการกระตุกใน Next.js

Hydration คือช่วงเวลาที่หน้าเว็บเปลี่ยนจาก HTML ซึ่งแสดงผลได้อย่างเดียว ให้กลายเป็นแอปที่ตอบสนองต่อการคลิก การพิมพ์ และการเปลี่ยนสถานะได้ กระบวนการนี้ช่วยให้ React และ Next.js ส่งหน้าเว็บให้ผู้ใช้เห็นได้เร็ว ขณะเดียวกันก็ยังรองรับประสบการณ์แบบแอปฝั่งไคลเอนต์
Hydration คืออะไร
เมื่อเปิดหน้าเว็บ เซิร์ฟเวอร์สามารถส่ง HTML ที่สร้างไว้แล้วมาให้เบราว์เซอร์แสดงผลก่อน ผู้ใช้จึงเห็นข้อความ รูปภาพ และโครงสร้างของหน้าได้ทันที โดยไม่ต้องรอให้ JavaScript ทั้งหมดทำงานเสร็จ
อย่างไรก็ตาม การเห็นหน้าเว็บไม่ได้แปลว่าหน้านั้นพร้อมใช้งานแล้ว ในช่วงแรกปุ่มอาจยังไม่รู้จัก onClick เมนูอาจยังเปิดไม่ได้ และฟอร์มอาจยังไม่เชื่อมต่อกับ state จนกว่า React ฝั่งไคลเอนต์จะเข้ามาผูกพฤติกรรมเหล่านี้เข้ากับ HTML เดิม
กระบวนการที่ React เข้ามาตรวจสอบโครงสร้าง DOM และเชื่อม event handler รวมถึง state กลับเข้าไป เรียกว่า hydration หากเปรียบ HTML เป็นร่างของหน้าเว็บ hydration ก็คือขั้นตอนที่ทำให้ร่างนั้นเริ่มมีชีวิตและโต้ตอบกับผู้ใช้ได้
สิ่งที่เกิดขึ้นระหว่าง Hydration
ใน Next.js ที่ใช้ Server-Side Rendering หรือ Static Site Generation ผู้ใช้อาจได้รับ HTML ก่อน JavaScript พร้อมทำงาน วิธีนี้เป็นประโยชน์ต่อการแสดงผลช่วงแรกและช่วยให้โปรแกรมค้นหาเข้าถึงเนื้อหาได้ง่ายขึ้น แต่เบราว์เซอร์ยังมีงานอีกหลายขั้นตอนที่ต้องทำ
กระบวนการโดยสรุปมีลำดับดังนี้
- เซิร์ฟเวอร์หรือไฟล์ที่สร้างไว้ล่วงหน้าส่ง HTML มาให้เบราว์เซอร์
- เบราว์เซอร์แสดง HTML ทำให้ผู้ใช้มองเห็นหน้าเว็บ
- เบราว์เซอร์ดาวน์โหลดและประมวลผล JavaScript bundle
- React สร้างโครงสร้างของคอมโพเนนต์ฝั่งไคลเอนต์แล้วเปรียบเทียบกับ DOM ที่มีอยู่
- React เชื่อม event handler และ state เข้ากับองค์ประกอบที่ต้องโต้ตอบ
หากหน้าเว็บมี JavaScript จำนวนมาก ขั้นตอนเหล่านี้อาจใช้ทรัพยากรบน main thread สูง ผู้ใช้จึงอาจเห็นหน้าเว็บแล้ว แต่ยังต้องรออีกเล็กน้อยก่อนที่ปุ่ม เมนู หรือฟอร์มจะตอบสนองอย่างสมบูรณ์
ทำไมหน้าเว็บจึงดูเหมือนโหลดแล้วแต่ยังฝืด
อาการที่พบได้บ่อยคือหน้าเว็บปรากฏขึ้นก่อน จากนั้นเลย์เอาต์ขยับ ข้อความเปลี่ยน ปุ่มไม่ตอบสนองทันที หรือ dropdown กระตุกเมื่อเปิดครั้งแรก ภาพที่ผู้ใช้เห็นจึงคล้ายกับเว็บตื่นขึ้นมาแล้วจัดองค์ประกอบของตัวเองใหม่
ปัญหานี้ไม่ได้หมายความว่า hydration เป็นข้อผิดพลาดเสมอไป แต่บอกได้ว่ามีงานฝั่งไคลเอนต์มากเกินไปในช่วงเริ่มต้น ยิ่ง JavaScript มีขนาดใหญ่หรือคอมโพเนนต์ซับซ้อน เบราว์เซอร์ก็ยิ่งต้องใช้เวลานานกว่าจะทำให้หน้าทั้งหมดพร้อมใช้งาน
Hydration Mismatch เกิดขึ้นได้อย่างไร
Hydration mismatch เกิดเมื่อ HTML ที่สร้างจากฝั่งเซิร์ฟเวอร์ไม่ตรงกับผลลัพธ์ที่ React คำนวณได้ระหว่างการเรนเดอร์ครั้งแรกในเบราว์เซอร์ เมื่อทั้งสองฝั่งเห็นข้อมูลต่างกัน React อาจต้องแก้ DOM ใหม่ แสดงคำเตือน หรือทำให้ผู้ใช้เห็นการกระพริบและการเปลี่ยนเนื้อหาหลังโหลด
ตัวอย่างค่าที่ทำให้ผลลัพธ์ของเซิร์ฟเวอร์และไคลเอนต์ไม่ตรงกันได้ ได้แก่
- เวลาปัจจุบันจาก
Date.now() - ค่าสุ่มจาก
Math.random() - ข้อมูลจาก
windowหรือขนาดหน้าจอ - ค่าที่อ่านจาก
localStorage - วันและเวลาที่แสดงตาม timezone ของอุปกรณ์ผู้ใช้
- เงื่อนไขที่ให้ผลต่างกันระหว่างเซิร์ฟเวอร์กับเบราว์เซอร์
ตัวอย่างต่อไปนี้มีโอกาสสร้าง HTML คนละค่าระหว่างสองฝั่ง เพราะเวลาที่ใช้เรนเดอร์ไม่ตรงกัน
export default function CurrentTime() {
return <p>{Date.now()}</p>;
}
แนวทางที่เหมาะสมคือทำให้การเรนเดอร์ครั้งแรกใช้ค่าที่คาดเดาได้และตรงกันทั้งสองฝั่ง จากนั้นจึงอ่านข้อมูลเฉพาะของเบราว์เซอร์และอัปเดตหน้าภายใน useEffect หลัง hydration เสร็จแล้ว
"use client";
import { useEffect, useState } from "react";
export default function ViewportWidth() {
const [width, setWidth] = useState<number | null>(null);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return (
<p>
{width === null ? "กำลังตรวจสอบขนาดหน้าจอ..." : `${width}px`}
</p>
);
}
ในตัวอย่างนี้ เซิร์ฟเวอร์และไคลเอนต์เริ่มจากค่า null เหมือนกัน จึงสร้างผลลัพธ์แรกที่สอดคล้องกัน หลังจากคอมโพเนนต์พร้อมใช้งานแล้ว useEffect จึงอ่าน window.innerWidth และอัปเดตข้อความด้วยข้อมูลจริงของอุปกรณ์
เมื่อคอมโพเนนต์หนักเกินไป
หน้าแรกที่มีกราฟขนาดใหญ่ editor แผนที่ animation หรือไลบรารี UI จำนวนมาก อาจบังคับให้เบราว์เซอร์ hydrate ทุกอย่างพร้อมกัน แม้ผู้ใช้ยังไม่ได้เลื่อนไปเห็นหรือยังไม่ได้ใช้งานองค์ประกอบเหล่านั้นก็ตาม ภาระทั้งหมดจึงไปรวมอยู่ในช่วงเปิดหน้าเว็บ
ผลลัพธ์คือหน้าอาจแสดงได้เร็ว แต่ตอบสนองช้าเมื่อผู้ใช้เริ่มคลิกหรือเลื่อน การวัดคุณภาพของเว็บจึงไม่ควรดูเพียงว่าเนื้อหาปรากฏเร็วแค่ไหน แต่ควรดูด้วยว่าผู้ใช้เริ่มโต้ตอบกับหน้าได้เร็วเพียงใด
Server Components ช่วยลดงานอย่างไร
Next.js รุ่นใหม่มี Server Components เพื่อให้ส่วนที่ไม่ต้องโต้ตอบสามารถเรนเดอร์อยู่ฝั่งเซิร์ฟเวอร์ได้ คอมโพเนนต์เหล่านี้ไม่จำเป็นต้องส่ง JavaScript สำหรับ hydration ไปยังเบราว์เซอร์ จึงช่วยลดทั้งขนาด bundle และภาระงานช่วงเริ่มต้น
ส่วนที่ต้องตอบสนองต่อผู้ใช้ เช่น ปุ่มที่เปลี่ยน state ฟอร์ม เมนูเปิดปิด หรือองค์ประกอบที่ใช้ browser API จึงค่อยกำหนดให้เป็น Client Component ด้วย "use client" แนวคิดสำคัญไม่ใช่การทำให้ทั้งหน้ามีชีวิตพร้อมกัน แต่คือการเลือกอย่างตั้งใจว่าส่วนใดจำเป็นต้องมีพฤติกรรมฝั่งไคลเอนต์จริง ๆ
แนวทางลดอาการกระตุกจาก Hydration
การปรับปรุงควรเริ่มจากลดขอบเขตของงานฝั่งไคลเอนต์และรักษาผลลัพธ์แรกให้สอดคล้องกัน แนวทางที่นำไปใช้ได้มีดังนี้
- แยกคอมโพเนนต์ให้เล็กและกำหนดขอบเขตของ Client Component เท่าที่จำเป็น
- หลีกเลี่ยงการวาง
"use client"ไว้บนคอมโพเนนต์ระดับบนสุด หากมีเพียงบางส่วนที่ต้องโต้ตอบ - เลื่อนกราฟ editor แผนที่ และคอมโพเนนต์หนักไปโหลดเมื่อจำเป็นด้วย dynamic import
- ใช้ skeleton หรือ placeholder ที่มีขนาดใกล้เคียงเนื้อหาจริงเพื่อลด layout shift
- หลีกเลี่ยงค่าที่เปลี่ยนได้ระหว่างการเรนเดอร์ครั้งแรก เช่น เวลา ค่าสุ่ม และข้อมูลเฉพาะของอุปกรณ์
- อ่าน
window,localStorageและ browser API ภายในuseEffectหรือหลังจากคอมโพเนนต์พร้อมทำงานแล้ว - ตรวจสอบว่าคอมโพเนนต์ที่ไม่ต้องใช้ state, event handler หรือ browser API สามารถเป็น Server Component ได้หรือไม่
การแก้ปัญหาที่ดีไม่จำเป็นต้องกำจัด JavaScript ทั้งหมด แต่ควรส่งเฉพาะ JavaScript ที่ช่วยให้ผู้ใช้ทำสิ่งที่ต้องการได้จริง เมื่อแต่ละส่วนของหน้ารับผิดชอบงานเท่าที่จำเป็น hydration จะเสร็จเร็วขึ้นและประสบการณ์ใช้งานจะลื่นไหลกว่าเดิม
สรุป
Hydration ไม่ใช่บั๊ก แต่เป็นต้นทุนของการรวมข้อดีจากสองฝั่งเข้าด้วยกัน ฝั่งเซิร์ฟเวอร์ช่วยส่งเนื้อหาให้เห็นเร็ว ส่วน React ฝั่งไคลเอนต์ช่วยทำให้หน้าเว็บตอบสนองต่อการคลิก การพิมพ์ และการเปลี่ยนแปลงของ state
จำง่าย ๆ ว่า HTML ทำให้ผู้ใช้เห็นหน้า ส่วน hydration ทำให้หน้านั้นมีชีวิต เว็บที่ดีจึงไม่ควรเพียงแค่แสดงผลเร็ว แต่ต้องพร้อมใช้งานเร็วด้วย โดยไม่ปลุกทุกองค์ประกอบให้ทำงานหนักพร้อมกันตั้งแต่เฟรมแรก