Deep links สำหรับแอปมือถือเนทีฟ: เส้นทาง โทเค็น และการเปิดในแอป
เรียนรู้การทำ deep link สำหรับแอปมือถือเนทีฟ: วางแผนเส้นทาง จัดการการเปิดในแอป และส่งมอบโค้ดชั่วคราวอย่างปลอดภัยสำหรับ Kotlin และ SwiftUI โดยไม่ต้องเขียน routing แบบกำหนดเองยุ่งเหยิง

สิ่งที่ deep link ควรทำ อธิบายแบบง่าย ๆ
เมื่อใครสักคนแตะลิงก์บนโทรศัพท์ พวกเขาคาดหวังผลลัพธ์หนึ่งอย่าง: พาไปยังที่ที่ถูกต้อง ทันที ไม่ใช่ที่ใกล้เคียง ไม่ใช่หน้าหลักที่มีแถบค้นหา หรือหน้าล็อกอินที่ลืมเหตุผลที่เขามา
ประสบการณ์ deep link ที่ดีควรเป็นแบบนี้:
- ถ้าแอปติดตั้งอยู่ จะเปิดตรงไปยังหน้าจอที่ลิงก์ต้องการ
- ถ้าแอปยังไม่ได้ติดตั้ง การแตะยังช่วยได้ (เช่น เปิดเว็บ fallback หรือหน้าร้านแอป และสามารถพาผู้ใช้กลับไปยังปลายทางเดิมหลังติดตั้ง)
- ถ้าผู้ใช้ต้องล็อกอิน ให้ล็อกอินครั้งเดียวแล้วไปยังหน้าที่ตั้งใจไว้ ไม่ใช่จุดเริ่มต้นของแอป
- ถ้าลิงก์มีการกระทำ (ยอมรับคำเชิญ ดูคำสั่ง ยืนยันอีเมล) การกระทำนั้นต้องชัดเจนและปลอดภัย
ความหงุดหงิดส่วนใหญ่เกิดจากลิงก์ที่ "ทำงานแบบพอใช้" แต่ทำให้การไหลแตก ผู้ใช้เห็นหน้าผิด สูญเสียสิ่งที่ทำ หรือวนลูป: แตะลิงก์ ล็อกอิน ไปแดชบอร์ด แตะอีกครั้ง ล็อกอินอีกครั้ง แม้แต่ก้าวเพิ่มเติมเดียวก็ทำให้ผู้ใช้ยอมแพ้ได้ โดยเฉพาะการกระทำครั้งเดียวเช่นคำเชิญหรือรีเซ็ตรหัสผ่าน
ก่อนเขียนโค้ด Kotlin หรือ SwiftUI ให้ตัดสินใจว่าคุณต้องการให้ลิงก์หมายถึงอะไร หน้าจอไหนเปิดจากภายนอกได้ อะไรเปลี่ยนถ้าแอปปิดเทียบกับกำลังรัน ควรเกิดอะไรขึ้นเมื่อผู้ใช้ออกจากระบบ
การวางแผนนี้ป้องกันความเจ็บปวดได้มาก: เส้นทางที่ชัดเจน พฤติกรรม "open in app" ที่คาดเดาได้ และวิธีส่งมอบโค้ดครั้งเดียวอย่างปลอดภัยโดยไม่ใส่ความลับลงใน URL
ประเภทของ deep link และจุดที่ "open in app" พัง
ไม่ใช่ทุก "ลิงก์ที่เปิดแอป" จะทำงานเหมือนกัน การปฏิบัติต่อพวกมันเหมือนกันจะทำให้เกิดความล้มเหลวคลาสสิก: ลิงก์เปิดผิดที่ เปิดเบราว์เซอร์แทนแอป หรือทำงานเฉพาะแพลตฟอร์มเดียว
สามกลุ่มทั่วไป:
- Custom schemes (เช่น myapp:) ตั้งค่าง่าย แต่แอปและเบราว์เซอร์หลายตัวจัดการด้วยความระมัดระวัง
- Universal Links (iOS) และ App Links (Android). ใช้ลิงก์เว็บปกติและสามารถเปิดแอปเมื่อมีการติดตั้ง หรือ fallback เป็นเว็บไซต์เมื่อไม่มี
- ลิงก์ในเบราว์เซอร์ฝังตัวภายในแอป. ลิงก์ที่เปิดในแอปอีเมลหรือ messenger มักมีพฤติกรรมต่างจาก Safari หรือ Chrome
"Open in app" อาจหมายถึงสิ่งต่าง ๆ ขึ้นกับที่แตะ ลิงก์ที่แตะใน Safari อาจกระโดดตรงเข้าแอปได้ ขณะที่เดียวกันในอีเมลหรือ messenger อาจเปิด web view ฝังตัวก่อน และผู้ใช้ต้องกดปุ่ม "เปิด" เพิ่มอีกขั้น (หรือไม่เห็นเลย) บน Android Chrome อาจเคารพ App Links ขณะที่เบราว์เซอร์ในแอปโซเชียลอาจไม่สนใจ
Cold start vs แอปกำลังรันเป็นกับดักถัดไป
- Cold start: OS เปิดแอปของคุณ แอปเริ่มต้น แล้วคุณได้รับ deep link ถ้า flow การเริ่มต้นแสดง splash, ตรวจ auth หรือโหลด config ระยะไกล ลิงก์อาจหายไป เว้นแต่คุณเก็บมันและเล่นซ้ำหลังแอปพร้อม
- แอปกำลังรัน: คุณได้รับลิงก์ขณะที่ผู้ใช้กำลังใช้งาน stack การนำทางมีอยู่แล้ว ดังนั้นปลายทางเดียวกันอาจต้องการการจัดการต่างกัน (push หน้าจอ vs รีเซ็ต stack)
ตัวอย่างง่าย ๆ: ลิงก์คำเชิญที่แตะจาก Telegram มักเปิดในเบราว์เซอร์ฝังตัวก่อน ถ้าแอปของคุณสมมติว่า OS จะส่งต่อโดยตรง ผู้ใช้จะเห็นหน้าเว็บแทนและคิดว่าลิงก์เสีย แผนรองรับสภาพแวดล้อมเหล่านี้ล่วงหน้าจะทำให้คุณเขียน glue เฉพาะแพลตฟอร์มน้อยลง
วางแผนเส้นทางก่อนลงมือทำ
บั๊กส่วนใหญ่ของ deep link ไม่ใช่ปัญหา Kotlin หรือ SwiftUI แต่เป็นปัญหาการวางแผน ลิงก์ไม่แมปตรงไปยังหน้าจอเดียว หรือมีตัวเลือกแบบ "อาจจะ" มากเกินไป
เริ่มด้วยรูปแบบเส้นทางที่สอดคล้องกับวิธีที่ผู้คนคิดเกี่ยวกับแอป: รายการ, รายละเอียด, การตั้งค่า, เช็คเอาต์, คำเชิญ เก็บให้อ่านง่ายและคงที่ เพราะคุณจะใช้ซ้ำในอีเมล, รหัส QR, และหน้าเว็บ
ชุดเส้นทางตัวอย่าง:
- หน้าแรก
- รายการคำสั่งและรายละเอียดคำสั่ง (orderId)
- การตั้งค่าบัญชี
- การยอมรับคำเชิญ (inviteId)
- ค้นหา (query, tab)
แล้วกำหนดพารามิเตอร์:
- ใช้ ID สำหรับออบเจ็กต์เดี่ยว (orderId)
- ใช้พารามิเตอร์ที่เป็นทางเลือกสำหรับสถานะ UI (tab, filter)
- ตัดสินค่าเริ่มต้นเพื่อให้ทุกลิงก์มีปลายทางที่ดีที่สุดเดียว
ตัดสินใจด้วยว่าจะทำอย่างไรเมื่อลิงก์ผิด: ขาดข้อมูล, ID ไม่ถูกต้อง, หรือเนื้อหาที่ผู้ใช้เข้าถึงไม่ได้ ค่าเริ่มต้นที่ปลอดภัยที่สุดคือเปิดหน้าจอที่มั่นคงใกล้เคียง (เช่น หน้ารายการ) และแสดงข้อความสั้น ๆ หลีกเลี่ยงการโยนผู้ใช้ไปยังหน้าว่างหรือหน้าล็อกอินที่ไม่มีบริบท
สุดท้าย วางแผนตามแหล่งที่มา รหัส QR มักต้องการเส้นทางสั้นที่เปิดเร็วและทนต่อการเชื่อมต่อไม่ดี ลิงก์ในอีเมลอาจยาวกว่าและมีบริบทพิเศษ หน้าเว็บควรลดรูปอย่างสมเหตุผล: ถ้าแอปยังไม่ได้ติดตั้ง ผู้ใช้ยังควรไปถึงที่ที่อธิบายว่าทำอย่างไรต่อ
ถ้าคุณใช้แนวทางที่ขับเคลื่อนด้วย backend (เช่น สร้าง endpoint และหน้าจอด้วยแพลตฟอร์มอย่าง AppMaster) แผนเส้นทางนี้จะกลายเป็นสัญญาที่แชร์: แอปรู้ว่าจะไปที่ไหน และ backend รู้ว่า ID และสถานะใดที่ถูกต้อง
การส่งมอบโทเค็นอย่างปลอดภัยโดยไม่ใส่ความลับใน URL
ลิงก์มักถูกคิดว่าเป็นซองปลอดภัย แต่ไม่ใช่ ทุกอย่างใน URL อาจจบลงในประวัติการท่องเว็บ สกรีนช็อต พรีวิวที่ถูกแชร์ บันทึกวิเคราะห์ หรือคัดลอกไปยังแชท
หลีกเลี่ยงการใส่ความลับในลิงก์ รวมถึง access token ระยะยาว, refresh token, รหัสผ่าน, ข้อมูลส่วนบุคคล หรือสิ่งที่จะทำให้คนอื่นทำตัวเป็นผู้ใช้ถ้าลิงก์ถูกส่งต่อ
รูปแบบที่ปลอดภัยกว่าคือโค้ดสั้นที่ใช้ได้ครั้งเดียว ลิงก์มีแค่โค้ดนั้น แอปแลกมันเป็น session จริงหลังเปิด หากคนหาประโยชน์จากลิงก์ โค้ดควรไร้ประโยชน์หลังหนึ่งนาทีสองนาที หรือหลังแลกครั้งแรก
โฟลว์การส่งมอบอย่างง่าย:
- ลิงก์มีโค้ดครั้งเดียว ไม่ใช่ session token
- แอปเปิดแล้วเรียก backend เพื่อแลกโค้ด
- Backend ตรวจสอบวันหมดอายุ ตรวจสอบว่าไม่เคยถูกใช้ แล้วทำเครื่องหมายว่าใช้แล้ว
- Backend คืน session ที่ยืนยันแล้วให้แอป
- แอปล้างโค้ดออกจากหน่วยความจำเมื่อแลกเสร็จ
แม้หลังแลกสำเร็จ ยืนยันตัวตนภายในแอปก่อนทำสิ่งที่ละเอียดอ่อน ถ้าลิงก์ใช้อนุมัติการจ่ายเงิน เปลี่ยนอีเมล หรือส่งออกข้อมูล ให้ขอการยืนยันรวดเร็วเช่น biometric หรือการล็อกอินใหม่
เก็บ session ที่ได้อย่างปลอดภัย บน iOS ปกติจะใช้ Keychain บน Android ให้ใช้ที่เก็บที่สนับสนุน Keystore เก็บแค่ที่จำเป็น และล้างเมื่อ logout, ลบบัญชี, หรือเมื่อพบการใช้งานที่น่าสงสัย
ตัวอย่างปฏิบัติ: ส่งลิงก์คำเชิญที่มีโค้ดครั้งเดียวหมดอายุใน 10 นาที แอปแลกโค้ด แล้วแสดงหน้าจอที่ระบุชัดว่าจะเกิดอะไรขึ้นต่อ (เข้าร่วม workspace ใด) ก่อนผู้ใช้ยืนยัน แอปจึงดำเนินการเข้าร่วม
ถ้าคุณสร้างด้วย AppMaster แนวทางนี้แมปได้ตรงไปยัง endpoint ที่แลกโค้ดและคืน session ขณะที่ UI มือถือจัดการขั้นตอนยืนยันก่อนการกระทำที่มีผลกระทบสูง
การยืนยันตัวตนและ “กลับไปยังที่คุณค้างไว้”
Deep link มักชี้ไปยังหน้าที่มีข้อมูลส่วนตัว เริ่มจากการตัดสินใจว่าหน้าไหนเปิดได้โดยทุกคน (public) และหน้าไหนต้องล็อกอิน (protected) การตัดสินใจนี้ป้องกันความประหลาดใจว่า "ในการทดสอบมันใช้งานได้" ส่วนใหญ่
กฎง่าย ๆ: เปิดไปยังสถานะลงจอดที่ปลอดภัยก่อน แล้วนำทางไปยังหน้าที่ต้องล็อกอินหลังยืนยันตัวตน
ตัดสินใจว่าอะไรสาธารณะ vs ป้องกัน
ปฏิบัติต่อ deep link เหมือนอาจถูกส่งต่อให้คนผิดคน
- สาธารณะ: หน้าการตลาด บทความช่วยเหลือ เริ่มรีเซ็ตรหัสผ่าน เริ่มการยอมรับคำเชิญ (ยังไม่แสดงข้อมูล)
- ป้องกัน: รายละเอียดคำสั่ง ข้อความ การตั้งค่าบัญชี หน้าผู้ดูแล
- ผสม: หน้าพรีวิวใช้ได้ แต่แสดงเฉพาะข้อมูลไม่ละเอียดจนกว่าจะล็อกอิน
“ต่อหลังล็อกอิน” ที่พาผู้ใช้กลับไปยังที่ถูกต้อง
แนวทางที่เชื่อถือได้คือ: แยกลิงก์ เก็บเป้าหมายที่ตั้งใจไว้ แล้วนำทางตามสถานะ auth
ตัวอย่าง: ผู้ใช้แตะลิงก์ "เปิดในแอป" ไปยังตั๋วซัพพอร์ตเฉพาะขณะที่ยังล็อกเอาต์ แอปควรเปิดไปยังหน้ากลาง ขอให้ล็อกอิน แล้วพาไปยังตั๋วนั้นโดยอัตโนมัติ
เพื่อให้เชื่อถือได้ ให้เก็บ "return target" เล็ก ๆ ท้องถิ่น (ชื่อเส้นทาง + ID) พร้อมวันหมดอายุสั้น หลังล็อกอินอ่านครั้งเดียว นำทาง แล้วลบ ถ้าการล็อกอินล้มเหลวหรือเป้าหมายหมดอายุ ให้ย้อนกลับไปยังหน้าโฮมที่ปลอดภัย
จัดการ edge cases ด้วยความเคารพ:
- เซสชันหมดอายุ: แสดงข้อความสั้น ๆ ขอการยืนยันตัวใหม่ แล้วดำเนินต่อ
- สิทธิ์ถูกเพิกถอน: เปิดเปลือกปลายทาง แล้วแสดงว่า "คุณไม่สามารถเข้าถึงได้อีกต่อไป" พร้อมตัวเลือกถัดไป
นอกจากนี้ หลีกเลี่ยงการแสดงข้อมูลส่วนตัวในพรีวิวหน้าจอล็อก, สลับแอป, หรือพรีวิวการแจ้งเตือน ให้หน้าจอสำคัญว่างจนกว่าจะโหลดข้อมูลและยืนยันเซสชัน
แนวทาง routing ที่หลีกเลี่ยงการพันกันของ navigation
Deep link กลายเป็นเรื่องยุ่งเมื่อทุกหน้าจอแยกย่อยการแยก URL ของตัวเอง การตัดสินใจเล็ก ๆ น้อย ๆ กระจายไปทั่วแอป ทำให้ยากต่อการเปลี่ยนอย่างปลอดภัย
ปฏิบัติตัว routing เหมือนงานประปาที่แชร์ เก็บตารางเส้นทางและ parser หนึ่งชุด และให้ UI รับอินพุตที่สะอาด
ใช้ตารางเส้นทางที่แชร์เดียว
ให้ iOS และ Android ตกลงกันบนรายการเส้นทางเดียวที่อ่านได้สำหรับมนุษย์ มองมันเป็นสัญญา
แต่ละเส้นทางแมปไปยัง:
- หน้าจอ และ
- โมเดลอินพุตขนาดเล็ก
เช่น “Order details” แมปไปยังหน้าจอ Order ที่รับอินพุต OrderRouteInput(id) ถ้าเส้นทางต้องการค่าพิเศษ (เช่น ref source) ให้เก็บไว้ในโมเดลอินพุต ไม่ใช่กระจายอยู่ในโค้ดมุมมอง
รวมการแยกวิเคราะห์และการตรวจสอบไว้ที่เดียว
เก็บการแยก การถอดรหัส และการตรวจสอบไว้ที่เดียว UI ไม่ควรถามว่า "โทเค็นนี้มีไหม?" หรือ "ID นี้ถูกไหม?" ควรได้รับอินพุตเส้นทางที่ถูกต้อง หรือสถานะข้อผิดพลาดที่ชัดเจน
โฟลว์ปฏิบัติได้:
- รับ URL (แตะ สแกน แชร์ชีท)
- แยกเป็นเส้นทางที่รู้จัก
- ตรวจสอบฟิลด์จำเป็นและรูปแบบที่ยอมรับได้
- ผลิตเป้าหมายหน้าจอพร้อมโมเดลอินพุต
- นำทางผ่านจุดเข้าเดียว
เพิ่มหน้าฟอลล์แบ็ก "ลิงก์ไม่รู้จัก" ให้มีประโยชน์ ไม่ใช่ทางตัน: แสดงสิ่งที่เปิดไม่ได, อธิบายสาเหตุเป็นภาษาง่าย ๆ, และเสนอการกระทำต่อเช่นกลับบ้าน ค้นหา หรือเข้าสู่ระบบ
ขั้นตอนทีละขั้น: ออกแบบ deep link และพฤติกรรม "open in app"
Deep link ที่ดีรู้สึกน่าเบื่อในความหมายที่ดี: ผู้ใช้แตะแล้วไปยังหน้าที่ถูกต้อง ไม่ว่าจะแอปติดตั้งหรือไม่
ขั้นตอน 1: เลือกจุดเข้าที่สำคัญ
จดลิงก์ประเภทแรก ๆ ที่คนใช้จริง ๆ สัก 10 แบบ: คำเชิญ, รีเซ็ตรหัสผ่าน, ใบเสร็จคำสั่ง, ดูตั๋ว, โปรโมชัน จำกัดให้น้อยโดยตั้งใจ
ขั้นตอน 2: เขียนแพทเทิร์นเป็นสัญญา
สำหรับแต่ละจุดเข้ากำหนดแพทเทิร์นหลักหนึ่งแบบและข้อมูลขั้นต่ำที่ต้องมีเพื่อเปิดหน้าที่ถูกต้อง ใช้ ID ที่มั่นคงแทนชื่อ กำหนดอะไรจำเป็นหรือไม่จำเป็น
กฎช่วยเหลือ:
- หนึ่งจุดประสงค์ต่อเส้นทาง
- พารามิเตอร์ที่จำเป็นต้องอยู่เสมอ; ตัวเลือกมีค่าเริ่มต้นที่ปลอดภัย
- ใช้แพทเทิร์นเดียวกันระหว่าง iOS (SwiftUI) และ Android (Kotlin)
- ถ้าคาดจะเปลี่ยน ให้สำรองเวอร์ชันง่าย ๆ (เช่น v1)
- กำหนดพฤติกรรมเมื่อพารามิเตอร์ขาด (แสดงหน้าข้อผิดพลาด ไม่ใช่หน้าว่าง)
ขั้นตอน 3: ตัดสินใจพฤติกรรมล็อกอินและเป้าหมายหลังล็อกอิน
เขียนลงว่าแต่ละลิงก์ต้องล็อกอินหรือไม่ ถ้าต้อง ให้จดเป้าหมายแล้วดำเนินหลังล็อกอิน
ตัวอย่าง: ลิงก์ใบเสร็จอาจแสดงพรีวิวได้โดยไม่ต้องล็อกอิน แต่ว่ากด "ดาวน์โหลดใบแจ้งหนี้" อาจต้องล็อกอินและควรพาผู้ใช้กลับไปยังใบเสร็จนั้น
ขั้นตอน 4: ตั้งกฎการส่งมอบโทเค็น (อย่าใส่ความลับใน URL)
ถ้าลิงก์ต้องมีโทเค็นครั้งเดียว ให้กำหนดระยะเวลาคงอยู่และวิธีใช้งาน วิธีปฏิบัติ: URL มีโค้ดสั้น-ครั้งเดียว แอปแลกกับ backend เพื่อรับ session จริง
ขั้นตอน 5: ทดสอบสามสถานะในโลกความเป็นจริง
Deep link พังที่ขอบ ทดสอบแต่ละประเภทลิงก์ใน:
- Cold start (แอปปิด)
- Warm start (แอปอยู่ในหน่วยความจำ)
- ไม่มีแอปติดตั้ง (ลิงก์ยังพาไปที่มีความหมาย)
ถ้าคุณเก็บเส้นทาง การตรวจสอบ auth และกฎแลกโค้ดในที่เดียว คุณจะไม่กระจายโลจิกการ routing เฉพาะ Kotlin และ SwiftUI มากนัก
ความผิดพลาดทั่วไปที่ทำให้ deep link พัง (และวิธีป้องกัน)
Deep link มักพังด้วยเหตุผลน่าเบื่อ: สมมติฐานเล็ก ๆ การเปลี่ยนชื่อหน้าจอ หรือโทเค็น "ชั่วคราว" ที่ไปทั่ว
ความล้มเหลวที่พบและการแก้ไข
-
ใส่ access token ใน URL (แล้วรั่วไปรวมถึง logs). Query string ถูกคัดลอก แชร์ เก็บในประวัติ และจับโดย analytics และ crash logs แก้: ใส่แค่โค้ดครั้งเดียวในลิงก์ แลกในแอป แล้วหมดอายุเร็ว
-
สมมติว่าแอปติดตั้ง (ไม่มี fallback). ถ้าลิงก์เปิดไปหน้าข้อผิดพลาดหรือไม่ทำอะไร ผู้ใช้ยอมแพ้ แก้: ให้หน้า fallback บนเว็บที่อธิบายสิ่งที่จะเกิดขึ้นและเสนอเส้นทางติดตั้ง แม้แต่หน้า "เปิดแอปเพื่อดำเนินการต่อ" ง่าย ๆ ก็ยังดีกว่าไม่มีอะไร
-
ไม่จัดการหลายบัญชีบนอุปกรณ์เดียว. เปิดหน้าถูกแต่ภายใต้ผู้ใช้ผิดยิ่งกว่าเสีย ลอง: เมื่แอปได้รับลิงก์ ให้ตรวจบัญชีที่ใช้งาน ถามผู้ใช้ยืนยันหรือสลับ แล้วค่อยดำเนินการ หากการกระทำต้องการ workspace ระบุ workspace ID (ไม่ใช่ความลับ) แล้วตรวจสอบ
-
ลิงก์พังเมื่อหน้าหรือเส้นทางเปลี่ยนชื่อ. ถ้าเส้นทางผูกกับชื่อ UI เก่า ลิงก์เก่าจะตายเมื่อคุณเปลี่ยนชื่อแท็บ แก้: ออกแบบเส้นทางที่ยืนตามเจตนา (invite, ticket, order) และรักษาความเข้ากันได้ของเวอร์ชันเก่าไว้
-
ไม่มีการติดตามเมื่อเกิดความผิดพลาด. โดยไม่มีวิธีเล่นซ้ำที่เกิดขึ้น support จะเดา แก้: ใส่ request ID ที่ไม่ละเอียดอ่อนในลิงก์ ล็อกมันทั้งบนเซิร์ฟเวอร์และในแอป และแสดงข้อความผิดพลาดที่มี ID นั้น
เช็กลูกตุ้มความเป็นจริงอย่างรวดเร็ว: ลองนึกถึงลิงก์คำเชิญในแชทกลุ่ม ใครสักคนเปิดบนโทรศัพท์งานที่มีสองบัญชี แอปไม่ได้ติดตั้งบนแท็บเล็ต และลิงก์ถูกส่งต่อ หากลิงก์มีแค่โค้ดคำเชิญ รองรับ fallback ยืนยันบัญชี และล็อก request ID ลิงก์เดียวนี้สามารถสำเร็จในทุกสถานการณ์โดยไม่เปิดเผยความลับ
ตัวอย่าง: ลิงก์คำเชิญที่เปิดตรงหน้าจอทุกครั้ง
คำเชิญเป็นคลาสสิก: ใครสักคนส่งลิงก์ใน messenger และผู้รับคาดหวังแตะครั้งเดียวถึงหน้าคำเชิญ ไม่ใช่หน้าโฮมทั่วไป
สถานการณ์: ผู้จัดการเชิญเอเยนต์คนใหม่เข้าร่วม workspace "Support Team" ผู้รับแตะคำเชิญใน Telegram
ถ้าแอปติดตั้ง ระบบควรเปิดแอปและส่งรายละเอียดคำเชิญให้ ถ้าแอปไม่ได้ติดตั้ง ผู้ใช้ควรไปยังหน้าเว็บที่อธิบายคำเชิญและเสนอเส้นทางติดตั้ง หลังติดตั้งและเปิดครั้งแรก แอปควรยังสามารถทำฟลอว์คำเชิญให้เสร็จโดยไม่ต้องให้ผู้ใช้ค้นหาลิงก์อีก
ภายในแอป ฟลอว์เหมือนกันทั้ง Kotlin และ SwiftUI:
- อ่านโค้ดคำเชิญจากลิงก์ขาเข้า
- ตรวจสอบว่าผู้ใช้ล็อกอินหรือไม่
- ยืนยันคำเชิญกับ backend แล้วนำทางไปยังหน้าที่ถูกต้อง
การยืนยันเป็นจุดสำคัญ ลิงก์ไม่ควรมีความลับเช่น session token ระยะยาว ควรมีโค้ดคำเชิญสั้นที่ใช้ได้หลังจากเซิร์ฟเวอร์ตรวจสอบมันแล้ว
ประสบการณ์ผู้ใช้ควรรู้สึกคาดเดาได้:
- ยังไม่ได้ล็อกอิน: เห็นหน้าจอล็อกอิน แล้วกลับไปยอมรับคำเชิญหลังล็อกอิน
- ล็อกอินแล้ว: เห็นการยืนยัน "เข้าร่วม workspace" หนึ่งครั้ง แล้วเข้าไปใน workspace ที่ถูกต้อง
ถ้าคำเชิญหมดอายุหรือถูกใช้แล้ว อย่าโยนผู้ใช้ไปหน้าข้อผิดพลาดว่าง ให้แสดงข้อความชัดเจนและตัวเลือกต่อไป: ขอคำเชิญใหม่ สลับบัญชี หรือติดต่อผู้ดูแล "คำเชิญนี้ถูกยอมรับแล้ว" ดีกว่า "โทเค็นไม่ถูกต้อง"
เช็กลิสต์ด่วนและขั้นตอนถัดไป
Deep link จะรู้สึกว่า "เสร็จ" เมื่อพวกมันทำงานเหมือนกันทุกที่: cold start, warm start, และเมื่อผู้ใช้ล็อกอินแล้ว
เช็กลิสต์ด่วน
ก่อนปล่อยทดสอบแต่ละหัวข้อบนอุปกรณ์จริงและเวอร์ชัน OS ต่าง ๆ:
- ลิงก์เปิดหน้าที่ถูกต้องทั้งใน cold start และ warm start
- ไม่มีข้อมูลละเอียดอ่อนไปอยู่ใน URL หากจำเป็นต้องส่งโทเค็น ให้เป็นโค้ดสั้นครั้งเดียว
- ลิงก์ที่ไม่รู้จัก หมดอายุ หรือถูกใช้แล้ว fallback ไปยังหน้าชัดเจนที่มีข้อความและตัวเลือกต่อไป
- ทำงานได้จากอีเมล เบราว์เซอร์ สแกนเนอร์ QR และพรีวิวใน messenger (บางตัวจะเปิดลิงก์ก่อน)
- การล็อกจะบอกคุณว่าเกิดอะไรขึ้น (รับลิงก์ แยกเส้นทาง auth ต้องการ สำเร็จหรือเหตุผลล้มเหลว)
วิธีตรวจสอบพฤติกรรมง่าย ๆ คือเลือกชุดลิงก์ที่ต้องทำงาน (คำเชิญ, รีเซ็ตรหัสผ่าน, รายละเอียดคำสั่ง, ตั๋วซัพพอร์ต, โปรโมชัน) แล้วรันผ่าน flow การทดสอบเดียวกัน: แตะจากอีเมล แตะจากแชท สแกน QR เปิดหลังติดตั้งใหม่
ขั้นตอนถัดไป (ทำให้ง่ายต่อการดูแล)
ถ้า deep link เริ่มกระจายข้ามหน้าจอ ให้จัดการ routing และ auth เป็นงานพื้นฐานร่วม ไม่ใช่โค้ดเฉพาะหน้าจอ รวม parsing ของเส้นทางไว้ที่เดียว และทำให้แต่ละปลายทางรับพารามิเตอร์สะอาด (ไม่ใช่ URL ดิบ) ทำแบบเดียวกันสำหรับ auth: จุดตรวจหนึ่งที่ตัดสิน "ดำเนินเลย" หรือ "ล็อกอินก่อน แล้วค่อยดำเนิน"
ถ้าต้องการลดโค้ดส่วนเชื่อมต่อที่กำหนดเอง อาจช่วยได้ถ้าสร้าง backend, auth และแอปมือถือร่วมกัน AppMaster (appmaster.io) เป็นแพลตฟอร์ม no-code ที่สร้าง backend และแอปเนทีฟที่พร้อมใช้งาน production ซึ่งช่วยให้ชื่อเส้นทางและ endpoint แลกโค้ดครั้งเดียวสอดคล้องกันเมื่อต้องการเปลี่ยนแปลง
ถ้าจะทำอย่างใดอย่างหนึ่งสัปดาห์หน้า ให้ทำสิ่งนี้: เขียนเส้นทาง canonical ของคุณและพฤติกรรม fallback สำหรับแต่ละกรณีความล้มเหลว แล้วนำกฎเหล่านั้นไปใช้ในชั้น routing เดียว
คำถามที่พบบ่อย
ลิงก์ควรเปิดไปยังหน้าจอที่ลิงก์ระบุอย่างชัดเจน ไม่ใช่หน้าโฮมหรือแดชบอร์ดแบบรวม ถ้าแอปไม่ได้ติดตั้ง ก็ควรยังช่วยผู้ใช้ด้วยการนำไปยังที่ที่สมเหตุสมผลและแนะนำวิธีกลับมาที่ปลายทางเดิมหลังติดตั้ง
Universal Links (iOS) และ App Links (Android) ใช้ URL แบบเว็บปกติและสามารถเปิดแอปเมื่อมีการติดตั้ง โดยจะมี fallback เป็นเว็บไซต์เมื่อไม่มี ส่วน custom scheme ตั้งค่าได้ง่ายแต่เบราว์เซอร์หรือแอปอื่นอาจจัดการไม่สม่ำเสมอ จึงควรใช้เป็นตัวเลือกสำรอง
หลายแอปอีเมลและแอปส่งข้อความเปิดลิงก์ในเบราว์เซอร์ฝังตัวของตัวเอง ซึ่งอาจไม่ส่งต่อไปยังระบบปฏิบัติการเหมือน Safari หรือ Chrome ดังนั้นต้องเตรียม fallback บนเว็บให้ชัดเจนและรองรับกรณีที่ผู้ใช้เห็นหน้าเว็บก่อน
ใน cold start แอปอาจแสดง splash ทำการตรวจสอบ หรือโหลดคอนฟิกก่อนจะพร้อมนำทาง วิธีที่เชื่อถือได้คือเก็บเป้าหมายลิงก์ขาเข้าไว้ทันที ทำการเริ่มต้นให้เสร็จ แล้ว "เล่นซ้ำ" การนำทางเมื่อตัวแอปพร้อม
อย่าใส่ access token ระยะยาว, refresh token, รหัสผ่าน หรือข้อมูลส่วนบุคคลใน URL เพราะ URL ถูกบันทึก แบ่งปัน และเก็บอยู่ในแคช ให้ใช้โค้ดแบบครั้งเดียวที่หมดอายุเร็ว และให้แอปแลกโค้ดกับ backend หลังเปิดแอปแล้ว
ให้แอปแยกลิงก์ออกมา แปลงเป็นเป้าหมายที่ตั้งใจไว้ แล้วนำทางตามสถานะการยืนยันตัวตน โดยเก็บ "return target" ขนาดเล็กไว้ชั่วคราวหลังจากนั้นเมื่อล็อกอินเสร็จให้ดึงหนึ่งครั้ง นำทาง แล้วลบ หากการล็อกอินล้มเหลวหรือเป้าหมายหมดอายุ ให้ fallback ไปหน้าโฮมที่ปลอดภัย
กำหนดเส้นทางเป็นสัญญาร่วมและรวมการแยกวิเคราะห์กับการตรวจสอบไว้ที่เดียว ส่งพารามิเตอร์สะอาดให้หน้าจอแทนที่จะส่ง URL ดิบ วิธีนี้จะป้องกันไม่ให้แต่ละหน้าจอเขียนกฎของตัวเองและทำให้การเปลี่ยนแปลงปลอดภัยขึ้น
เมื่อได้รับลิงก์ ให้ตรวจสอบบัญชีที่ใช้งานอยู่ว่าตรงกับ workspace หรือ tenant ที่ลิงก์ระบุหรือไม่ ถ้าไม่ตรงให้ถามผู้ใช้ให้ยืนยันหรือสลับบัญชีก่อนจะแสดงข้อมูลส่วนตัว ดีกว่าการเปิดหน้าภายใต้บัญชีที่ผิด
กลับไปยังหน้าจอที่มั่นคงที่สุด เช่น หน้ารายการ แล้วแสดงข้อความสั้น ๆ อธิบายว่าไม่สามารถเปิดอะไรได้ หลีกเลี่ยงการแสดงหน้าว่าง ความล้มเหลวเงียบ หรือการโยนผู้ใช้ไปยังหน้าล็อกอินโดยไม่มีบริบท
ทดสอบแต่ละลิงก์สำคัญในสามสถานะ: แอปปิดอยู่, แอปกำลังรัน, และไม่มีแอปติดตั้งจริง และทดสอบจากแหล่งจริงเช่น อีเมล แชท และสแกน QR ถ้าคุณใช้ AppMaster คุณจะรักษาชื่อเส้นทางและ endpoint แลกโค้ดให้สอดคล้องระหว่าง backend และแอปได้ง่ายขึ้น ซึ่งลดโค้ดเชื่อมต่อแบบกำหนดเองที่ต้องดูแล


