Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 26 — Deeplink Spec

Viết PRD & Product Spec cho PM Bài 26/60

Mở đầu — vì sao bài này quan trọng

Hãy thử hình dung một tình huống quen thuộc. Bạn nhận được email từ Shopee: "Đơn hàng của bạn đã được giao". Bạn bấm vào nút "Xem đơn hàng". Có hai kịch bản xảy ra. Kịch bản tốt: app Shopee mở lên, nhảy thẳng vào màn hình chi tiết đúng đơn hàng đó, bạn thấy ngay trạng thái giao hàng. Kịch bản tệ: app mở lên ở màn hình trang chủ, bạn phải tự mò vào tab "Đơn mua", cuộn tìm đúng đơn, bấm vào xem. Hai trải nghiệm này khác nhau một trời một vực, dù về mặt kỹ thuật chỉ cách nhau đúng một dòng spec mà PM đã viết — hoặc đã quên viết.

Cái dòng spec đó chính là deeplink — đường link đưa người dùng đi thẳng vào một màn hình cụ thể bên trong ứng dụng, thay vì chỉ mở app ở trạng thái mặc định. Deeplink là cây cầu nối giữa thế giới bên ngoài (email, push notification, tin nhắn, quảng cáo, link chia sẻ) và một điểm sâu bên trong app của bạn.

Vì sao PM phải tự tay viết deeplink spec mà không để engineer "tự lo"? Bởi vì deeplink không phải là một chi tiết kỹ thuật thuần túy. Nó là quyết định về sản phẩm: người dùng nên hạ cánh ở đâu, với context gì, khi họ chưa đăng nhập thì sao, khi nội dung đã bị xóa thì sao, khi họ chưa cài app thì sao. Đây toàn là những câu hỏi mà chỉ PM mới trả lời được, vì chúng gắn với hành trình người dùng và mục tiêu kinh doanh. Engineer giỏi đến mấy cũng không thể đoán được bạn muốn người dùng thấy gì khi họ bấm vào một link sản phẩm đã hết hàng.

Trong các bài trước về spec mobile, chúng ta sẽ chạm tới push và offline. Bài này tập trung riêng một mảnh: làm thế nào để viết một deeplink spec đầy đủ, không lỗ hổng, để engineer build đúng ngay từ lần đầu và QA test được mọi nhánh.

Khái niệm cốt lõi

Deeplink là gì và ba "tầng" của nó

Về bản chất, deeplink là một URL được app của bạn "nhận diện" và xử lý. Có ba loại bạn cần phân biệt rõ trong spec:

  • Custom URL scheme (ví dụ shopee://product/12345): loại cũ, đơn giản, nhưng có nhược điểm chí mạng — nếu người dùng chưa cài app, bấm vào link sẽ báo lỗi "không mở được", trải nghiệm rất tệ.
  • Universal Links (iOS) / App Links (Android): dùng chính URL https:// thông thường (ví dụ https://shopee.vn/product/12345). Nếu đã cài app thì mở app; nếu chưa cài thì mở web. Đây là chuẩn hiện đại, và 90% trường hợp bạn nên spec loại này.
  • Deferred deeplink: trường hợp đặc biệt — người dùng chưa cài app, bấm link, được đưa tới App Store/CH Play, cài app, mở lần đầu, và app vẫn "nhớ" được link ban đầu để đưa họ vào đúng màn hình. Cực kỳ quan trọng cho marketing và referral.
Khi viết spec, PM phải nói rõ mình đang dùng loại nào, vì hành vi fallback (dự phòng) hoàn toàn khác nhau.

Cấu trúc một deeplink entry trong spec

Một deeplink không chỉ là "cái link". Mỗi deeplink trong spec của bạn nên được mô tả như một bản hợp đồng (contract) gồm các thành phần:

  • URL pattern: dạng đường dẫn, ví dụ /order/{orderId}. Phần {orderId} là tham số động.
  • Tham số (parameters): liệt kê từng tham số, kiểu dữ liệu, bắt buộc hay tùy chọn, ví dụ orderId là chuỗi, bắt buộc; tab là tùy chọn với giá trị mặc định.
  • Màn hình đích (destination): chính xác màn hình nào, với state nào.
  • Điều kiện tiên quyết (preconditions): cần đăng nhập không? Cần quyền gì không?
  • Hành vi fallback: khi đăng nhập thất bại, khi nội dung không tồn tại, khi tham số sai, khi chưa cài app.
  • Back stack behavior: bấm nút Back từ màn hình đích thì đi về đâu? Đây là chi tiết hay bị bỏ sót nhất.

Ba use case kinh điển của deeplink

Theo đúng tinh thần bài học, có ba ngữ cảnh chính mà deeplink phát huy giá trị:

  • Email → mở app ở màn hình cụ thể: email khuyến mãi, email xác nhận, email nhắc nhở. Mục tiêu là rút ngắn khoảng cách từ "đọc email" tới "hành động trong app".
  • Push notification → landing theo ngữ cảnh: thông báo đẩy luôn đi kèm một ý định. "Có người nhắn tin cho bạn" thì phải mở đúng cuộc trò chuyện đó, không phải mở hộp thư chung.
  • Share link → người nhận hạ cánh sâu trong app: bạn chia sẻ một sản phẩm, một bài viết, một playlist cho bạn bè. Khi họ bấm, họ phải thấy đúng thứ bạn chia sẻ, kể cả khi họ chưa cài app hoặc chưa đăng nhập.

Tình huống thực tế

Ví dụ 1: Shopee và đơn hàng "biến mất"

Một PM tại sàn thương mại điện tử (gọi là sàn X cho khách quan) viết spec cho email "đơn hàng đã giao". Spec ban đầu chỉ có một dòng: "Bấm nút trong email → mở app vào màn hình chi tiết đơn hàng /order/{orderId}". Trông có vẻ đủ.

Nhưng khi ship ra, support nhận về hàng loạt phàn nàn. Lý do: nhiều người dùng bấm vào email cũ từ 2 tháng trước, lúc đó đơn hàng đã được hệ thống archive (lưu trữ) và endpoint trả về 404. App nhận deeplink, gọi API, API trả về "không tìm thấy" — và app... đứng im ở màn hình loading trắng xóa. Có khoảng 3% lượt mở email rơi vào trạng thái này, tương đương vài nghìn người dùng mỗi tuần bị màn hình trắng.

Bài học rút ra: spec thiếu hẳn nhánh error/empty state. Bản spec sửa lại phải bổ sung: "Nếu orderId không tồn tại hoặc đã archive → hiển thị màn hình thông báo nhẹ nhàng 'Không tìm thấy đơn hàng này' kèm nút điều hướng về danh sách đơn hàng, KHÔNG để màn hình loading vô hạn." Chỉ một câu, nhưng nó cứu cả nghìn lượt trải nghiệm tệ mỗi tuần.

Ví dụ 2: Ứng dụng đặt xe và cú push lúc 7 giờ sáng

Một startup gọi xe ở Đông Nam Á (giả định, quy mô vài triệu user) gửi push notification: "Tài xế đang đến, còn 3 phút nữa". Đây là loại push cực kỳ nhạy thời gian — người dùng bấm vào là vì họ muốn xem tài xế đang ở đâu trên bản đồ ngay lập tức.

Spec ban đầu đưa deeplink tới /trip/{tripId}. Nhưng có một lỗ hổng: app yêu cầu đăng nhập, và nếu session của người dùng đã hết hạn (token expired), app sẽ đẩy họ ra màn hình login. Người dùng đang vội, đứng dưới mưa, bấm push, và bị bắt nhập lại số điện thoại + OTP. Đến lúc đăng nhập xong thì tài xế đã tới rồi. Tỷ lệ hủy chuyến sau những push này cao bất thường — đội phân tích phát hiện nhóm bị văng ra login có tỷ lệ hủy cao hơn 40% so với nhóm vào thẳng.

Bài học: spec deeplink phải mô tả rõ luồng re-authentication. Bản sửa quy định: "Nếu token hết hạn, thực hiện refresh token im lặng ở nền (silent refresh). CHỈ khi refresh thất bại mới hiện login, và sau khi login xong PHẢI quay lại đúng deeplink ban đầu /trip/{tripId}, không để rơi về trang chủ." Yếu tố "quay lại đúng deeplink sau khi login" này là thứ PM cực hay quên ghi.

Ví dụ 3: Tính năng share playlist và người bạn chưa cài app

Một ứng dụng nghe nhạc cho phép người dùng chia sẻ playlist qua link. Bạn A gửi link https://music.app/playlist/abc cho bạn B qua Zalo. Bạn B chưa cài app.

PM ở đây đã làm tốt: spec dùng Universal Link, nên khi bạn B chưa cài app, link mở web preview của playlist (vẫn nghe được 30 giây mỗi bài, kèm nút "Tải app để nghe trọn vẹn"). Quan trọng hơn, PM spec cả deferred deeplink: khi bạn B tải app từ nút đó, cài xong và mở lần đầu, app tự động mở thẳng vào playlist abc mà bạn A đã chia sẻ — không bắt bạn B tự đi tìm lại.

Kết quả: tỷ lệ chuyển đổi từ link chia sẻ thành người dùng mới hoạt động (activated) tăng rõ rệt, vì người mới được dẫn ngay tới đúng nội dung khiến họ tải app, thay vì lạc vào màn hình onboarding chung chung. Bài học: với link chia sẻ, deferred deeplink không phải tính năng "nice-to-have" mà là đòn bẩy tăng trưởng thực sự, và nó phải nằm trong spec ngay từ đầu.

Hướng dẫn từng bước

Đây là quy trình viết một deeplink spec mà tôi khuyên bạn áp dụng cho mọi tính năng có deeplink.

Bước 1 — Liệt kê toàn bộ entry point. Trước khi viết link, hãy hỏi: deeplink này xuất phát từ đâu? Email? Push? Link chia sẻ? Quảng cáo? Banner trong app? Mỗi nguồn có thể cần context khác nhau (ví dụ link từ quảng cáo cần kèm tham số tracking như utm_source).

Bước 2 — Định nghĩa URL pattern và bảng tham số. Viết rõ pattern, ví dụ /product/{productId}?variant={variantId}. Lập một bảng nhỏ: mỗi hàng là một tham số, gồm cột tên, kiểu, bắt buộc/tùy chọn, giá trị mặc định, ví dụ minh họa. Bảng này là thứ engineer đọc đầu tiên.

Bước 3 — Mô tả màn hình đích và state mong muốn. Không chỉ nói "mở màn hình sản phẩm", mà nói "mở màn hình sản phẩm, cuộn sẵn tới phần đánh giá nếu tham số scrollTo=reviews". Càng cụ thể, engineer càng ít phải đoán.

Bước 4 — Viết bảng preconditions và fallback. Đây là phần quan trọng nhất. Lập bảng các tình huống: chưa đăng nhập, token hết hạn, không có quyền truy cập, nội dung không tồn tại, tham số sai định dạng, chưa cài app. Với MỖI tình huống, ghi rõ app phải làm gì. Đây là nơi phân biệt spec nghiệp dư và spec chuyên nghiệp.

Bước 5 — Định nghĩa back stack behavior. Khi người dùng vào màn hình đích qua deeplink rồi bấm Back, họ đi đâu? Về trang chủ? Thoát app? Về màn hình cha hợp lý (ví dụ từ chi tiết đơn hàng Back về danh sách đơn hàng)? Quy ước phổ biến và thân thiện nhất là "dựng lại back stack hợp lý" để người dùng cảm thấy như họ đã đi vào màn hình đó một cách tự nhiên.

Bước 6 — Đặc tả deferred deeplink (nếu cần). Nếu nguồn là link chia sẻ hoặc quảng cáo, nêu rõ hành vi khi người dùng chưa cài app: web fallback hiển thị gì, sau khi cài app có giữ context không, context lưu trong bao lâu (thường giới hạn vài giờ tới vài ngày).

Bước 7 — Viết acceptance criteria và bảng test. Liệt kê từng kịch bản dưới dạng "Cho... Khi... Thì..." để QA test được. Ví dụ: "Cho người dùng chưa đăng nhập, khi bấm deeplink /order/123, thì hiện login, và sau khi đăng nhập thành công thì mở /order/123."

Lỗi thường gặp & mẹo

Lỗi 1 — Quên trạng thái chưa đăng nhập. Đây là lỗi số một. PM mặc định người dùng đã login. Mẹo: luôn tự hỏi "nếu người này vừa cài app và chưa login thì sao?" cho mọi deeplink.

Lỗi 2 — Để màn hình loading vô hạn khi API lỗi. Như ví dụ Shopee. Mọi deeplink gọi API đều phải có nhánh xử lý 404, lỗi mạng, và timeout. Không bao giờ để màn hình trắng.

Lỗi 3 — Sau khi login/cài app thì văng về trang chủ. Người dùng bấm link để xem thứ A, nhưng cuối hành trình lại thấy trang chủ. Luôn spec rõ "preserve và resume deeplink gốc sau khi vượt qua rào cản".

Lỗi 4 — Bỏ quên back stack. Người dùng bấm Back và thoát thẳng app, gây hụt hẫng. Hãy spec back stack có chủ đích.

Lỗi 5 — Dùng custom scheme thay vì Universal Link cho link công khai. Link chia sẻ ra ngoài mà dùng app://... thì người chưa cài app bấm vào sẽ gặp lỗi. Với mọi link đi ra khỏi app, hãy dùng https://.

Lỗi 6 — Không nghĩ tới link cũ/hết hạn. Email và tin nhắn sống rất lâu. Một link có thể được bấm sau 6 tháng. Spec phải bền với thời gian: nội dung bị xóa, khuyến mãi hết hạn, tài khoản đổi — tất cả cần fallback.

Mẹo vàng: Viết một bảng ma trận với hai trục — trục dọc là các tình huống người dùng (đã login / chưa login / token hết hạn / chưa cài app), trục ngang là các loại deeplink. Điền vào từng ô. Khi mọi ô đều có câu trả lời, spec của bạn gần như không còn lỗ hổng. Engineer và QA sẽ yêu bạn vì bảng này.

Bài tập thực hành

Hãy chọn một tính năng quen thuộc và tự viết deeplink spec hoàn chỉnh. Gợi ý: tính năng "nhắc giỏ hàng" của một app thương mại điện tử — người dùng bỏ quên sản phẩm trong giỏ, hệ thống gửi push notification "Bạn còn 2 món chưa thanh toán" sau 3 giờ.

Yêu cầu spec của bạn phải trả lời đầy đủ:

  • URL pattern và bảng tham số là gì? (gợi ý: cần đưa người dùng tới giỏ hàng, có thể kèm sản phẩm cụ thể)
  • Màn hình đích và state mong muốn ra sao?
  • Bảng preconditions/fallback cho ít nhất 4 tình huống: chưa đăng nhập, token hết hạn, một sản phẩm trong giỏ đã hết hàng, người dùng đã thanh toán xong giữa lúc đó (giỏ trống).
  • Bấm Back từ giỏ hàng thì về đâu?
  • Viết 3 dòng acceptance criteria dạng "Cho... Khi... Thì..." để QA test.
Làm xong, hãy thử đưa cho một người bạn làm engineer hoặc QA đọc và hỏi: "Có chỗ nào em vẫn phải đoán không?" Mỗi câu hỏi họ đặt ra chính là một lỗ hổng trong spec mà bạn cần lấp.

Tóm tắt

Deeplink là cây cầu đưa người dùng từ email, push notification, hay link chia sẻ đi thẳng vào đúng điểm họ cần bên trong app — và đó là quyết định sản phẩm, không phải chi tiết kỹ thuật để engineer tự lo. Một deeplink spec tốt mô tả mỗi link như một bản hợp đồng đầy đủ: URL pattern, bảng tham số, màn hình đích với state cụ thể, và quan trọng nhất là bảng preconditions/fallback cho mọi tình huống — chưa đăng nhập, token hết hạn, nội dung không tồn tại, chưa cài app. Đừng quên back stack behavior và deferred deeplink cho link chia sẻ. Ba ví dụ thực tế cho thấy: thiếu một nhánh fallback có thể khiến cả nghìn người dùng gặp màn hình trắng, làm tăng tỷ lệ hủy chuyến tới 40%, hoặc ngược lại — làm đúng deferred deeplink có thể biến link chia sẻ thành đòn bẩy tăng trưởng thật sự. Mẹo cốt lõi: lập bảng ma trận tình huống × loại deeplink, điền kín mọi ô, và bạn sẽ có một spec không lỗ hổng.