37 คะแนน โดย GN⁺ 2025-11-03 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • โครงสร้าง URL ไม่ได้เป็นเพียงที่อยู่เท่านั้น แต่ยังทำงานเป็น วิธีเก็บและกู้คืนสถานะของแอปพลิเคชัน
  • ยกตัวอย่างกรณีอย่าง หน้าดาวน์โหลด PrismJS ที่สามารถสร้างการตั้งค่าธีม ภาษา และปลั๊กอินกลับมาได้ครบถ้วนด้วย URL เพียงเส้นเดียว
  • องค์ประกอบแต่ละส่วน เช่น พาธ, คิวรีพารามิเตอร์, ฟรากเมนต์ สามารถใช้แทนสถานะได้หลากหลาย ทั้งการนำทางแบบลำดับชั้น การกรอง และการนำทางภายในฝั่งไคลเอนต์
  • สถานะอย่าง ตัวกรองการค้นหา, pagination, โหมดการแสดงผล, ช่วงวันที่ เหมาะจะใส่ไว้ใน URL ขณะที่ ข้อมูลอ่อนไหวหรือสถานะ UI ชั่วคราว ไม่เหมาะ
  • URL ที่ออกแบบมาดีจะช่วยเพิ่ม ความสามารถในการแชร์, ความคาดเดาได้, ประสิทธิภาพการแคช และเสริม ความน่าเชื่อถือกับประสบการณ์ผู้ใช้ ของเว็บแอปพลิเคชัน

ศักยภาพของ URL

  • URL ไม่ได้เป็นแค่ที่อยู่ของทรัพยากร แต่ยังทำหน้าที่เป็น ส่วนติดต่อผู้ใช้ (UI) และ คอนเทนเนอร์ของสถานะ
    • ช่วยเก็บสถานะโดยอัตโนมัติในการแชร์, บุ๊กมาร์ก, ประวัติเบราว์เซอร์, deep link
    • ทำหน้าที่เป็นกลไกจัดการสถานะพื้นฐานของเว็บมาตั้งแต่ปี 1991
  • องค์ประกอบแต่ละส่วนของ URL ใช้แสดงสถานะคนละแบบ
    • พาธ (Path) : การนำทางทรัพยากรแบบลำดับชั้น (/users/123/posts)
    • คิวรี (Query) : ตัวกรอง ตัวเลือก และการตั้งค่า (?theme=dark&lang=en)
    • ฟรากเมนต์ (Fragment) : ตำแหน่งภายในเอกสารหรือการ route ใน SPA (#features, #/dashboard)
  • ฟีเจอร์ Text Fragments ช่วยลิงก์ไปยังข้อความเฉพาะภายในหน้าได้โดยตรง

แพตเทิร์นของคิวรีพารามิเตอร์

  • ใช้ ตัวคั่น (delimiter) เพื่อเก็บหลายค่าไว้ในคีย์เดียว (?tags=frontend,react,hooks)
  • ทำ ข้อมูลแบบซ้อน ให้เป็นซีเรียลไลซ์ด้วย JSON หรือ Base64 (?config=eyJyaWNrIjoicm9sbCJ9==)
  • Boolean flag แสดงด้วยการมีอยู่ของคีย์ (?mobile)
  • รูปแบบอาร์เรย์ (bracket notation) ใช้แทนค่าหลายค่าในรูป tags[]=frontend&tags[]=react
    • เครื่องมืออย่าง qs ของ Node หรือ Express middleware รู้จำได้อัตโนมัติ แต่ยังไม่ใช่มาตรฐาน
  • หัวใจสำคัญคือ ต้องรักษาความสม่ำเสมอ

ตัวอย่างการแทนสถานะผ่าน URL

  • PrismJS: เก็บการตั้งค่าธีม ภาษา และปลั๊กอินทั้งหมดไว้ใน URL hash
  • GitHub: ใช้ #L108-L136 เพื่อไฮไลต์ช่วงบรรทัดโค้ดที่ต้องการ
  • Google Maps: ใส่พิกัด ระดับการซูม และประเภทแผนที่ไว้ใน URL
  • Figma: แชร์บริบทการทำงาน เช่น ตำแหน่งบนแคนวาส การซูม และองค์ประกอบที่เลือก ผ่าน URL
  • เว็บไซต์อีคอมเมิร์ซ: ใส่ตัวกรอง การเรียงลำดับ และช่วงราคาไว้ใน URL เพื่อกู้คืนสถานะการค้นหา

แพตเทิร์นทางวิศวกรรมฟรอนต์เอนด์

  • สถานะที่เหมาะจะใส่ใน URL
    • คำค้นหา ตัวกรอง หน้าและการเรียงลำดับ โหมดการแสดงผล ช่วงวันที่ แท็บที่เปิดอยู่ การจัดวาง UI และฟีเจอร์แฟลก
  • สถานะที่ไม่เหมาะกับ URL
    • ข้อมูลอ่อนไหวอย่างรหัสผ่านหรือโทเค็น สถานะ UI ชั่วคราว ข้อมูลที่ยังไม่บันทึก ข้อมูลขนาดใหญ่ และสถานะที่เปลี่ยนถี่มาก
  • เกณฑ์ตัดสินคือ: ถ้าผู้ใช้อีกคนคลิก URL เดียวกัน เขาควรเห็นสถานะเดียวกันหรือไม่

การทำงานใน JavaScript

  • ใช้ API URLSearchParams เพื่ออ่านและเขียนคิวรีพารามิเตอร์
  • ใช้ pushState เพื่อเพิ่มรายการใหม่ในประวัติ และใช้ replaceState เพื่ออัปเดตรายการปัจจุบัน
  • ใช้อีเวนต์ popstate เพื่อกู้คืน UI เมื่อผู้ใช้กดย้อนกลับในเบราว์เซอร์

การทำงานใน React

  • ใช้ฮุก useSearchParams ของ React Router เพื่อจัดการสถานะใน URL ได้อย่างกระชับ
    • เมื่ออ่านหรืออัปเดตพารามิเตอร์ ระบบจะซิงก์ URL กับ UI ให้อัตโนมัติ

แนวปฏิบัติที่ดีในการจัดการสถานะด้วย URL

  • อย่าใส่ค่าเริ่มต้นไว้ใน URL (เก็บไว้เฉพาะ ?theme=dark และให้โค้ดจัดการค่าเริ่มต้น)
  • ใช้ debouncing เพื่อป้องกันการอัปเดต URL ถี่เกินไประหว่างพิมพ์ (lodash.debounce)
  • pushState vs replaceState
    • pushState: เหมาะกับสถานะที่ควรย้อนกลับได้ เช่น การเปลี่ยนตัวกรองหรือเปลี่ยนหน้า
    • replaceState: เหมาะกับการแก้ไขย่อย ๆ เช่น การพิมพ์ในช่องค้นหา

มอง URL เป็นสัญญา (Contract)

  • URL ที่ออกแบบมาดีทำหน้าที่เป็น สัญญาที่ชัดเจนระหว่างแอปพลิเคชันกับผู้ใช้
    • ช่วยกำหนดขอบเขตระหว่างสถานะแบบสาธารณะ/ส่วนตัว, ฝั่งไคลเอนต์/ฝั่งเซิร์ฟเวอร์, แบบแชร์ได้/แบบเซสชัน ได้ชัดเจน
  • URL ที่อ่านเข้าใจง่าย ช่วยอธิบายเจตนา และให้ทั้งคนกับเครื่องเข้าใจได้
    • รูปแบบอย่าง example.com/products/laptop?color=silver&sort=price สื่อความหมายได้ดี
  • ช่วยเพิ่ม ประสิทธิภาพการแคช
    • URL เดียวกันจะถูกมองเป็นทรัพยากรเดียวกัน จึงเพิ่มอัตราแคชฮิต
    • ควบคุมการแปรผันของแคชได้ผ่านคิวรีพารามิเตอร์
  • การจัดการเวอร์ชันและการทดลอง
    • ใช้ ?v=2, ?beta=true, ?experiment=new-ui เพื่อแยกเวอร์ชัน API หรือการทดสอบ A/B

แอนติแพตเทิร์นที่ควรหลีกเลี่ยง

  • เก็บสถานะไว้แค่ในหน่วยความจำของ SPA จนรีเฟรชแล้วสถานะหาย
  • ใส่ข้อมูลอ่อนไหวไว้ใน URL (?password=secret123)
  • ใช้ชื่อพารามิเตอร์ที่ไม่ชัดเจน (?foo=true&bar=2 แทนที่จะเป็น ?mobile=true&page=2)
  • เข้ารหัส JSON ที่ซับซ้อนด้วย Base64 จน URL ยาวเกินจำเป็น
  • เกินข้อจำกัดความยาวของ URL (มีข้อจำกัดจากเบราว์เซอร์ เซิร์ฟเวอร์ และ CDN)
  • ทำให้ปุ่มย้อนกลับใช้งานไม่ได้ (มักเกิดจากการใช้ replaceState มากเกินไป)

บทสรุป

  • URL ที่ดีไม่ได้แค่ชี้ไปยังเนื้อหา แต่ยังแสดงบทสนทนาระหว่างผู้ใช้กับแอปพลิเคชัน
  • URL คือเครื่องมือจัดการสถานะที่เก่าแก่และสง่างามที่สุดในการเก็บ เจตนา บริบท และความสามารถในการแชร์
  • แม้จะมีเครื่องมือจัดการสถานะที่ซับซ้อนอย่าง Redux, MobX, Zustand, Recoil
    การไม่ลืมความสามารถพื้นฐานของ URL คือจุดแข็งที่แท้จริงของเว็บ
  • แอปที่ทำสถานะหายเมื่อรีเฟรช กำลังพลาดคุณสมบัติพื้นฐานของเว็บไป

2 ความคิดเห็น

 
GN⁺ 2025-11-03
ความเห็นบน Hacker News
  • เวลาทำ code review ฉันพยายามเก็บ state ให้มากที่สุดเท่าที่จะทำได้ไว้ใน URL
    หลังรีเฟรชแล้วกระโดดไปคนละตำแหน่งโดยสิ้นเชิง หรือ URL ที่แชร์กลับเปิดหน้าจอผิด ๆ ถือเป็นประสบการณ์ที่แย่มากสำหรับผู้ใช้
    วิธีนี้ทำให้การพัฒนาช้าลง แต่ก็ช่วยให้ทีมตระหนักเรื่อง UX มากขึ้น และเห็นชัดว่าใน view หนึ่ง ๆ มี state อยู่มากแค่ไหน
    บางคนกังวลว่า URL จะกลายเป็นเหมือน public API ที่สร้างข้อจำกัดขึ้นมา แต่ฉันคิดว่า URL ส่วนใหญ่ถูกใช้งานแค่ช่วงสั้น ๆ จึงไม่ใช่ปัญหาใหญ่
    ถ้าจำเป็นก็แก้ได้ด้วยโค้ด migration ที่แปลง URL เก่าเป็น URL ใหม่ตอนโหลด

    • ฉันชอบแนวทางนี้ แต่บางครั้ง ประวัติการเติมข้อความอัตโนมัติของเบราว์เซอร์ จะดึง state ที่ไม่ต้องการกลับมา
      ฉันคิดว่าใช้ query parameter แทน path จะดีกว่าเล็กน้อย
    • เว็บแอปที่ใช้ทำงานของฉันทำปุ่ม “ย้อนกลับ” ของตัวเองขึ้นมา จนปุ่มย้อนกลับของเบราว์เซอร์พังไปหมด
      จากมุมผู้ใช้ คำว่า “ย้อนกลับ” มันผูกกับปุ่มของเบราว์เซอร์อยู่แล้ว เลยทำให้สับสน
      การที่รีเฟรชแล้ว state ถูกรีเซ็ตยังน่าหงุดหงิดน้อยกว่า เพราะคนมักเข้าใจว่า “รีเฟรช = เริ่มใหม่ตั้งแต่ต้น”
    • ถ้าเป็นหน้า server-rendered เวลารีเฟรช ตำแหน่งการเลื่อนหน้าจอ จะถูกกู้คืนให้อัตโนมัติ
      แต่พอให้ JS จัดการทุกอย่าง ฟีเจอร์พื้นฐานแบบนี้มักเสียไปแบบแนบเนียน
    • ฉันคิดว่าการออกแบบ URL เป็น ส่วนหนึ่งของการออกแบบ UX
      แต่แม้จะเคยทำงานกับ UX designer มากกว่า 30 คน ก็ไม่เคยได้รับแนวทางเรื่อง URL เลย
    • พอเว็บพัฒนามาเรื่อย ๆ ความหมายของการรีเฟรช ก็เปลี่ยนไปตามบริบท
      โดยเฉพาะบนมือถือ การพาหน้ากลับไปสู่ state เริ่มต้นทำได้ยาก เลยทำให้รีเฟรชเป็นทางออกที่เร็วที่สุด
      ใน UI ที่มี infinite scroll หรือ filter ซับซ้อน ยิ่งมี state อยู่ใน URL มาก การรีเซ็ตก็ยิ่งน่ารำคาญ
      ถ้าผู้ใช้ไม่พอใจกับ UX อยู่แล้ว แล้วยังต้องมาจัดการ URL อีก นั่นคือความเครียดซ้ำซ้อนสำหรับผู้ใช้
  • ฉันรู้สึกว่าแม้แต่คนที่มี digital literacy สูงก็ยัง เข้าใจ URL และ DNS ต่ำ
    ควรรู้วิธีลดความเสี่ยงจากฟิชชิง เข้าใจความหมายของ URL parameter (?t=_, utm_) และลบข้อมูลส่วนตัวก่อนแชร์ได้
    และควรรู้ด้วยว่าแม่กุญแจ HTTPS ไม่ได้หมายถึง ‘ความน่าเชื่อถือ’

    • แต่เบราว์เซอร์กลับ ซ่อนหรือย่อ URL เป็นค่าเริ่มต้น และบริษัทต่าง ๆ ก็โปรโมตแต่ QR code หรือคำค้น ทำให้การให้ความรู้เป็นเรื่องยาก
  • ถ้าใช้ URL เป็น state container โครงสร้างภายในก็จะ ถูกเปิดเผย และต้องมีการจัดการเวอร์ชัน
    ยังอาจมีปัญหาเรื่องความเข้ากันได้ระหว่างเบราว์เซอร์หรือ flow การยืนยันตัวตนด้วย
    ถึงอย่างนั้นฉันก็ยังพยายามเปิดเผย state ให้มากที่สุดใน URL เหมือนกับ argument ของ command line
    แต่ทั้งหมดนี้เป็น trade-off ที่ตั้งใจเลือก ไม่ใช่เพราะไม่รู้หรือขาดประสบการณ์

  • ขอแนะนำ Rison แม้จะเป็นไลบรารีเก่าแต่ยังมีประโยชน์มาก
    มันช่วยเก็บ JSON ลงใน URL ได้อย่างเรียบร้อย และยังถูกใช้ใน Kibana ของ Elastic ด้วย
    ตัวอย่าง: http://example.com/service?query=q:'*',start:10,count:10

    • นี่แหละสิ่งที่ฉันหาอยู่! เมื่อก่อนฉันเคยทำแบบขัดตาทัพเอง แต่นี่ดูเป็นวิธีที่ มาตรฐานและเป็นระเบียบ กว่ามาก
  • เมื่อระบบพัฒนาไป โครงสร้างของ state ก็เปลี่ยนตาม ดังนั้นการใส่ state ไว้ใน URL จึงทำให้การวิวัฒนาการของระบบ ถูกจำกัด
    เพราะ URL โดยพื้นฐานแล้วคือ สตริงถาวร
    ทางที่ดีกว่าคือมอง URL เป็นเหมือน protocol ชนิดหนึ่ง แล้วเข้ารหัส/ถอดรหัส state ผ่านมัน
    ถ้าเป็นหน้าที่เรียบง่าย ก็อาจใส่ state ทั้งหมดลงใน URL ได้

    • สำหรับคอนเทนต์ที่มีอายุยาวนาน เช่นบล็อกโพสต์ การเก็บ state ไว้ใน URL มีประโยชน์
      แต่สำหรับสิ่งอย่าง feed ก็ขึ้นอยู่กับ ความคาดหวังของผู้ใช้ เช่น “พอกดรีเฟรชแล้วควรกลับไปเป็นสถานะล่าสุดหรือไม่?”
    • ถ้านำการจัดการเวอร์ชันเข้ามาใช้ ก็ช่วยบรรเทาปัญหานี้ได้
  • ข้อจำกัดเรื่องความยาว URL แตกต่างกันไปตามการตั้งค่าของเบราว์เซอร์ เซิร์ฟเวอร์ CDN และเสิร์ชเอนจิน แต่โดยทั่วไปจะอยู่ที่ ไม่เกิน 2000 อักขระ
    เลยต้องคิดว่าจะยัด state ได้มากแค่ไหนภายใต้ข้อจำกัดนี้ หรือควรใช้วิธีอื่นแทน

    • ถ้าไม่นับโดเมน อักขระแต่ละตัวใช้ได้ 66 แบบ (ตัวพิมพ์เล็ก-ใหญ่ ตัวเลข และอักขระพิเศษ - . _ ~) ดังนั้น ความหนาแน่นของข้อมูล จึงค่อนข้างสูง
  • draw.io สามารถเก็บ state ทั้งหมดไว้ใน URL เพื่อแชร์ได้
    ข้อมูลไดอะแกรมจะถูกเข้ารหัสแบบ Base64 ทำให้กู้คืนได้ครบถ้วนจากลิงก์เดียว
    แต่ก็ยังไม่แน่ใจว่าสิ่งนี้เข้ากับนิยามของ ‘state container’ หรือไม่

  • ฉันใช้ hash routing (#/dashboard) ในแอป self-hosted
    เลยไม่ต้องพึ่งการ rewrite URL ฝั่งเซิร์ฟเวอร์ (.htaccess เป็นต้น) ซึ่งถึงจะไม่สมบูรณ์แบบ แต่ก็ช่วยลด ข้อจำกัดของสภาพแวดล้อมในการ deploy ได้

  • Microsoft Teams รุ่นล่าสุดจัดการทุกหน้าด้วย URL เดียว ทำให้ บุ๊กมาร์กไม่ได้
    เปิดไปยังทีมหรือแชนเนลที่ต้องการโดยตรงไม่ได้เลย จึงไม่สะดวกมาก

  • HATEOAS ไม่ค่อยได้รับความสนใจเพราะชื่อไม่ดี แต่จริง ๆ แล้วมันคือแนวคิดพื้นฐานของเว็บ

    • จากมุมผู้ใช้ การคลิกลิงก์และส่งฟอร์มก็คือ HATEOAS นั่นเอง
      แต่ในสภาพแวดล้อมที่ควบคุมทั้งเซิร์ฟเวอร์และไคลเอนต์ได้ มันกลับเพิ่ม ความซับซ้อนส่วนเกิน
      โดยเฉพาะถ้าไคลเอนต์ยังต้องรู้โครงสร้างของ endpoint อยู่ดี มันก็แค่ทำให้ URL ดูทึบขึ้นเท่านั้น
    • ที่จริงประเด็นนี้ไม่ได้เกี่ยวกับ HATEOAS โดยตรง ทั้งคู่ใช้ URL ก็จริง แต่ HATEOAS ว่าด้วย โครงสร้างการนำทาง ไม่ใช่ การเก็บสถานะ
    • พูดเล่น ๆ แต่สุดท้าย HATEOAS ก็น่าจะเหมาะกับคำว่า “cerealization” มากกว่า “serialized format”
 
ndrgrd 2025-11-03

ฉันใช้ฟีเจอร์พักแท็บเป็นประจำ แต่เว็บแอปที่ตรึงอยู่กับ URL เดียวและเคลื่อนย้ายเป็นก้อนเดียวกัน พอเข้าสู่โหมดพัก ข้อมูลก็หายไป

แต่ในขณะเดียวกัน หน้าเว็บพวกนั้นก็มักจะหนักกันทุกเว็บจนไม่พักแท็บก็ไม่ได้