บทความทั้งหมด
ViteHMRlocal developmentlocalhost tunneling

Vite + ทันเนล: HMR ที่ทำงานได้จริง

การทันเนล dev server ของ Vite ทำงานได้ดี — ยกเว้นสองเรื่องที่จะทำให้คุณสะดุดในครั้งแรก หน้าโหลดได้แต่ HMR ไม่ทำงาน หรือหน้าไม่โหลดเลยเพราะ Vite ปฏิเสธ hostname ของทันเนล ทั้งสองเป็นการแก้ config บรรทัดเดียวเมื่อคุณรู้ว่าต้องดูตรงไหน

ปัญหาที่ 1: "Blocked request. This host is not allowed."

Vite 5+ เพิ่มการป้องกัน host-check เป็นค่าเริ่มต้น หากคุณเปิด URL ทันเนลแล้วเห็น Blocked request. This host is not allowed นั่นคือการตรวจสอบนี้ปฏิเสธ hostname *.portpreview.dev ของคุณ

แก้ใน vite.config.ts:

export default defineConfig({
  server: {
    host: true,                            // listen on 0.0.0.0
    allowedHosts: ['.portpreview.dev'],   // accept any subdomain
  },
});

จุดนำหน้าทำให้เป็นการจับคู่แบบ suffix คุณใส่ hostname ทันเนลเฉพาะได้หากต้องการเข้มงวด แต่สำหรับงานพัฒนาประจำวัน suffix สะดวกกว่า — URL ของเซสชันทันเนลหมุนเวียน

ปัญหาที่ 2: HMR โหลดแล้วตายเงียบๆ

HMR ทำงานผ่าน WebSocket เมื่อ Vite เสิร์ฟที่ localhost:5173 แต่เบราว์เซอร์โหลดจาก https://abc123.portpreview.dev ไคลเอนต์ HMR พยายามเชื่อมต่อกับ URL ที่เซิร์ฟเวอร์บอก ซึ่งเป็นแบบโลคัล ทันเนลไม่ส่งต่อสิ่งนั้น WebSocket จึงล้มเหลวและ HMR หยุดเงียบๆ

แก้ config ของ HMR:

export default defineConfig({
  server: {
    host: true,
    allowedHosts: ['.portpreview.dev'],
    hmr: {
      clientPort: 443,            // browser connects on 443 (HTTPS)
      protocol: 'wss',            // secure WebSocket through the tunnel
    },
  },
});

ตอนนี้ไคลเอนต์ HMR เชื่อมต่อกับ wss://abc123.portpreview.dev (พอร์ต 443) ทันเนลส่งต่อ upgrade และการแก้ไขกระจายตามที่คาดหวัง หากคุณต้องการแค่การรีโหลดแบบ static และไม่สนใจ HMR ผ่านทันเนล คุณข้ามได้ — แต่คุณจะกด cmd-R บ่อยมาก

ทำไมจึงอยากทันเนล Vite ตั้งแต่แรก

เหตุผลจริงสามข้อ:

  • การทดสอบบนมือถือ เปิด URL บนมือถือ เห็น build เดียวกันพร้อม HMR ดีกว่าการแชร์หน้าจอหรือ emulator แยกตามอุปกรณ์ ดู การทดสอบบนมือถือด้วยทันเนล สำหรับรูปแบบเต็ม
  • แชร์งานที่กำลังทำ ส่งลิงก์ให้ดีไซเนอร์หรือ PM พวกเขาเห็น branch ของคุณแบบสด ไม่มีขั้นตอน deploy
  • ฟลว์ OAuth และ webhook ที่ต้องใช้ HTTPS frontend ที่เสิร์ฟด้วย Vite ที่คุยกับ Stripe (redirect เท่านั้น), Auth0 หรือคล้ายกัน mkcert กับทันเนล ครอบคลุมการเลือก

SvelteKit, Astro, SolidStart ล้วนใช้ Vite

config ข้างต้นใช้ได้กับเฟรมเวิร์กใดก็ตามที่ส่งมอบ Vite เป็น dev server vite.config.js ของ SvelteKit มีรูปแบบเดียวกัน Astro มี astro.config.mjs ของตัวเองพร้อม sub-object vite — ใส่ตัวเลือก server เดียวกันที่นั่น SolidStart, Nuxt 3 (ผ่าน Vite) และ Qwik City: รูปแบบเดียวกัน

Build โปรดักชันเป็นอีกเรื่อง

ทุกอย่างข้างต้นคือ config ของ dev server เมื่อคุณรัน vite preview หรือเสิร์ฟ bundle ที่ build แล้ว ไม่มีอะไรเกี่ยวข้องเพราะไม่มี HMR และไม่มี middleware host-check ทันเนลแค่ส่งต่อไฟล์ static ณ จุดนั้น

เรื่องเล็กๆ อีกสองสามอย่าง

  • host CORS แบบเข้มงวด หาก frontend เรียก API ที่ origin อื่น (พอร์ต localhost อื่น หรือทันเนล API แยก) ให้ตั้ง CORS ฝั่ง API Vite ไม่ proxy โดยค่าเริ่มต้นเว้นแต่คุณสั่งผ่าน server.proxy
  • แอปที่ใช้ WebSocket หนัก Vite HMR คือ WebSocket หนึ่งตัว หากแอปของคุณใช้ WebSocket อีกตัว สำหรับสถานะเกม แชต หรือข้อมูลสด ตัวนั้นแยกต่างหากและต้องตั้งค่าในโค้ดไคลเอนต์ให้ใช้ URL ทันเนลด้วย
  • URL ทันเนลในไฟล์ env เราใส่ URL ทันเนลที่ใช้งานใน .env.local เป็น VITE_PUBLIC_URL เมื่อ frontend ต้องรู้ที่อยู่สาธารณะของตัวเอง import.meta.env.VITE_PUBLIC_URL อ่านมันในไคลเอนต์

ทีละขั้นตอน

  1. เพิ่มบล็อก server.host, allowedHosts และ hmr ใน config ของ Vite
  2. รีสตาร์ท dev server
  3. รัน npx portpreview 5173 (หรือพอร์ตที่ Vite ใช้)
  4. เปิด URL HTTPS ในเบราว์เซอร์หรือบนมือถือ
  5. แก้ไข component ยืนยันว่า HMR อัปเดตหน้าโดยไม่ต้องรีโหลดทั้งหมด

หาก HMR ไม่อัปเดต ให้เปิด console ของเบราว์เซอร์ — Vite log ความพยายามเชื่อมต่อ WebSocket และคุณจะเห็น URL ที่มันลอง นั่นบอกว่าการเปลี่ยน hmr.clientPort/protocol มีผลหรือไม่

เข้าร่วม waitlist ของ PortPreview สำหรับทันเนลที่รักษา WebSocket upgrade ไว้โดยค่าเริ่มต้น

คำถามที่พบบ่อย

ทำไม Vite จึงบล็อก URL ทันเนลของฉันด้วย "host is not allowed"?
Vite 5+ เพิ่ม host-check ที่ปฏิเสธ hostname ที่ไม่รู้จักโดยค่าเริ่มต้น เพิ่ม allowedHosts: ['.portpreview.dev'] (หรือโดเมนทันเนลของคุณ) ใน config ของ server เพื่อรับคำขอจากทันเนล
จะทำให้ Vite HMR ทำงานผ่านทันเนลได้อย่างไร?
ตั้ง hmr.clientPort: 443 และ hmr.protocol: 'wss' ใน vite.config.ts สิ่งนี้บอกไคลเอนต์ HMR ให้เชื่อมต่อผ่าน HTTPS/WSS ของทันเนลแทนการพยายามเข้าถึง localhost โดยตรง ซึ่งล้มเหลวจากภายนอกเครื่องของคุณ
ใช้กับ SvelteKit, Astro และ Nuxt ได้ไหม?
ได้ เฟรมเวิร์กใดก็ตามที่ใช้ Vite เป็น dev server ใช้ config server.host, allowedHosts และ hmr เดียวกัน Astro ซ้อนไว้ใต้ vite ใน astro.config.mjs ส่วนอื่นใส่ในไฟล์ config Vite ปกติ