Linear đã đưa tôi đi vào con hố thỏ local-first
(bytemash.net)- Việc dùng Linear đã làm thay đổi đáng kể cách nhìn của tôi về cách phát triển ứng dụng web
- Linear hoạt động theo cách tiếp cận local-first, mang lại phản hồi tức thì và tương tác không có độ trễ mạng
- Cách tiếp cận này cho phép client có cơ sở dữ liệu độc lập, và các thay đổi được đồng bộ với máy chủ theo cách bất đồng bộ
- Tuy nhiên, đồng bộ trong môi trường phân tán, giải quyết xung đột, xử lý ngoại tuyến và các phần tương tự có độ khó triển khai rất cao
- Hệ sinh thái local-first có nhiều giải pháp mới như Jazz, Electric SQL, Zero đang xuất hiện, và trải nghiệm của nhà phát triển cũng ngày càng được cải thiện
Tổng quan
Khi dùng Linear, một công cụ quản lý dự án, tôi thấy được cách tiếp cận local-first mang lại tốc độ và trải nghiệm người dùng vượt trội. Việc gần như không hề cảm nhận được độ trễ mạng, trạng thái loading, hoặc refresh trang—những điều vốn rất quen thuộc ở ứng dụng web truyền thống—là điều ấn tượng nhất. Nhờ trải nghiệm này, tôi đã đi sâu hơn vào nguyên lý kỹ thuật cùng các ví dụ áp dụng thực tế của local-first.
Đi sâu vào con đường rabbit hole
Khi tìm hiểu bí mật kỹ thuật của Linear, tôi phát hiện họ đang tận dụng IndexedDB của trình duyệt như một cơ sở dữ liệu thật sự. Mọi thay đổi đều được xử lý trước tiên ngay tại local và sau đó đồng bộ nền bằng GraphQL và Websockets.
- Thuật ngữ local-first có thể được diễn giải theo nhiều cách, từ chiến lược UX (đáp ứng tức thì) đến triết lý lưu trữ dữ liệu tại local rồi đồng bộ
- Trong ứng dụng web truyền thống, máy chủ là nguồn dữ liệu duy nhất, nhưng trong kiến trúc local-first, mỗi client đều có cơ sở dữ liệu riêng
- Khi vị trí cơ sở dữ liệu được đưa về gần người dùng hơn, độ trễ mạng trong tương tác người dùng được loại bỏ hầu như hoàn toàn
Thách thức: Điều này không hề đơn giản
Khi thử triển khai trực tiếp cách làm của Linear, tôi nhận ra mức độ phức tạp là rất lớn.
- Xử lý chuyển đổi offline/online
- Giải quyết xung đột giữa các client phân tán
- Đồng bộ một phần (để tránh phải tải về toàn bộ dữ liệu)
- Di chuyển schema dữ liệu cache
- Bảo mật và kiểm soát truy cập trong môi trường phân tán
- Đây là những mảng cần đầu tư lượng thời gian và công sức kỹ thuật lớn
Hệ sinh thái local-first năm 2025
Tính đến năm 2025, hệ sinh thái local-first có nhiều giải pháp mạnh.
- Electric SQL: cơ chế đồng bộ dựa trên Postgres
- PowerSync: giải pháp theo hướng doanh nghiệp
- Jazz: công cụ giúp xây dựng ứng dụng local-first dễ như thao tác cập nhật trạng thái
- Replicache: một giải pháp chủ lực trước đây (đã đóng phát triển)
- Zero: hướng đi mới của đội ngũ Replicache
- Triplit: đồng bộ dựa trên TripleStore
- Instant: tập trung vào trải nghiệm nhà phát triển
- LiveStore: cung cấp lớp đồng bộ thời gian thực
Khám phá sâu: Jazz
Jazz gây chú ý nhờ cam kết đặc biệt: “tạo ứng dụng local-first dễ như cập nhật state”.
Mô hình tư duy
Jazz giới thiệu Collaborative Values (CoValues), một cấu trúc hợp tác thời gian thực.
- Định nghĩa schema bằng Jazz và Zod: định nghĩa này không đơn thuần là kiểu dữ liệu mà hoạt động như đối tượng sống tự động đồng bộ
- Không cần route API riêng, logic request/response hay DTO; chỉ cần thay đổi trạng thái của object là đồng bộ tự động
Jazz đạt được điều này như thế nào
- Bảo đảm tính duy nhất: gán tự động ID duy nhất cho từng dữ liệu
- Event sourcing: lưu lịch sử thay đổi dưới dạng sự kiện, tối ưu hóa hiệu quả đồng bộ thời gian thực
- Mã hóa đầu cuối: dữ liệu được mã hóa trên client trước khi đồng bộ; máy chủ chỉ thấy blob đã mã hóa
- Thiết kế quyền theo nhóm: thay vì ACL truyền thống, quyền được chia theo nhóm với quyền sở hữu được tách biệt rõ ràng
Các đánh đổi
Cấu trúc này đặc biệt hiệu quả cho prototyping và phát triển UI nhanh. Tuy nhiên, nó có một số điểm cần cân nhắc.
Máy chủ của bạn là mù
Vì mã hóa đầu cuối nên máy chủ không thể đọc dữ liệu người dùng. Nếu không định nghĩa rõ ràng trước dữ liệu nào cần server truy cập, việc quản lý như giám sát hoặc ngăn chặn lưu trữ độc hại sẽ bị hạn chế
Time Travel là bắt buộc
Nhờ event sourcing, mọi thay đổi đều được lưu lại vĩnh viễn. Điều này làm Undo/Redo rất tiện lợi, nhưng khi phải tuân thủ yêu cầu pháp lý như GDPR thì việc xóa dữ liệu lại khó khăn
Bộ nhớ cứ tăng
Việc không xóa khiến dung lượng lưu trữ tăng dần. Với dự án nhỏ thì ổn, nhưng với SaaS quy mô lớn chi phí lưu trữ có thể tăng đáng kể
Local Dev vẫn còn quirks
Xác thực dựa trên Passkeys là mặc định. Với tự triển khai hoặc môi trường local, những phiền toái ban đầu như HTTPS, quản lý chứng chỉ và chuyển khóa có thể xuất hiện. Tuy nhiên, các cải tiến như tích hợp Better Auth đang được lên kế hoạch
Nhưng thành thật mà nói? Vẫn đáng làm
Dù có các ràng buộc này, trải nghiệm phát triển và năng suất của Jazz là rất ấn tượng. Mặc dù hiện vẫn ở giai đoạn đầu, dự kiến các vấn đề khác nhau sẽ dần được giải quyết
Khám phá: Electric SQL và Zero
Khác với Jazz, Electric SQL và Zero theo cách tiếp cận từng bước.
- Có thể dùng trực tiếp các bảng Postgres hiện có
- Electric SQL cho phép đồng bộ giao diện người dùng bằng cách subscribe một phần bảng dưới dạng reactive query (Shape)
- Cách xử lý mutation của Electric SQL khác với Jazz, và có nhiều lựa chọn như tích hợp LiveStore
- Zero tương tự Electric nhưng có sẵn hỗ trợ đồng bộ hóa thay đổi
Khi nào local-first là phù hợp?
Phù hợp:
- Công cụ sáng tạo (thiết kế, viết lách, âm nhạc,...)
- Ứng dụng hỗ trợ cộng tác
- Ứng dụng di động cần hỗ trợ ngoại tuyến
- Công cụ dành cho nhà phát triển
- Ứng dụng năng suất cá nhân
Thách thức:
- Logic nghiệp vụ phía máy chủ quy mô lớn
- Yêu cầu kiểm toán (audit) nghiêm ngặt
- Hệ thống phân tích dữ liệu khối lượng lớn
- Hệ thống tích hợp sâu với các nền tảng sẵn có
- Hệ thống thường xuyên từ chối yêu cầu tại server
Nhìn về phía trước
Local-first hàm ý một cuộc chuyển đổi trong kiểu kiến trúc phát triển web. Linear đã chứng minh hiệu quả rõ rệt trên trải nghiệm người dùng. Nhà phát triển cần đánh giá xem các đánh đổi kiến trúc này có phù hợp với dự án của mình hay không.
Tôi đang tự làm ứng dụng cá nhân bằng Jazz để trực tiếp trải nghiệm những ưu điểm, nhược điểm và giới hạn trừu tượng hóa. Hệ sinh thái vẫn còn ở giai đoạn đầu, và kỳ vọng công cụ cùng pattern sẽ ngày càng trưởng thành, cải thiện dần. Tuy nhiên, lợi thế của việc để dữ liệu ở local là rất rõ ràng và có vẻ không biến mất.
Nếu có thể chấp nhận ràng buộc trong dự án mới, thì việc trải nghiệm local-first là đáng giá. Trường hợp tệ nhất, bạn học được một mô hình kiến trúc mới; trường hợp tốt nhất, bạn có thể xây dựng trải nghiệm người dùng nhanh đến mức gần như không thể làm được trước đây. Trong cuộc đua đáp ứng trong 300ms, đây là lợi thế lớn.
1 bình luận
Ý kiến Hacker News
Zero cũng cung cấp tính năng tương tự Electric và hỗ trợ trực tiếp việc xử lý mutation Điểm khác biệt cốt lõi của Zero là đồng bộ theo truy vấn Bạn có thể thiết kế app theo đơn vị truy vấn và không cần quyết định hoặc cấu hình trước những gì cần đồng bộ; chỉ cần chạy query là đồng bộ đúng lượng dữ liệu cần thiết hoặc ít hơn Nếu client chưa có dữ liệu cần thiết, query sẽ tự động chuyển lên server, dữ liệu được đồng bộ và sẵn sàng dùng ngay cho các truy vấn tiếp theo Cách làm này thực tế và rất hữu dụng
Mình thích SSR Mình nghĩ client chỉ nên mang trạng thái là session token, URL hiện tại và DOM Mạng và server vẫn đang ngày càng nhanh Tốc độ ánh sáng không đổi, nhưng chúng ta chưa tận dụng hết Sắp tới sẽ có công nghệ như Hollow core fiber có thể hạ latency toàn internet xuống tới 30% Dù RTT là 500ms, nếu render xong trang bằng SSR trong 16ms thì nó sẽ vẫn cảm giác nhanh hơn hầu hết website online hiện nay Mình nghĩ không thể để server render phản hồi HTML lâu hơn một frame 60Hz Trên core Zen5 có thể tuần tự hóa 30~40MB JSON trong khoảng thời gian đó Với server, đó chỉ là một chuỗi UTF-8 fancy Mình cho rằng nên đo những phần này bằng micro giây, không phải mili giây Latency cao không phải là lý do để dùng CPU một cách lười biếng Mình thoát khỏi chiếc "millisecond jail" nhờ dùng SQLite Dịch vụ SQL hosting là một sợi dây trói khi nhắm latency dưới 1ms Chuẩn của browser cũng có thể giải quyết một phần vấn đề trễ khi navigate Speculation Rules API tham khảo
Việc mạng và server sẽ nhanh hơn không phải là luận cứ phổ quát cho SSR Nói cách khác, đây là những trường hợp ngoại lệ khi cần SSR vì server có nhiều sức mạnh tính toán hơn hoặc khó tách logic Thực ra có không ít tình huống client-side rendering còn nhanh hơn Rất nhiều khi logic render phức tạp hơn nhiều so với kích thước dữ liệu, và cộng tất cả tài nguyên client có thể lớn hơn server Nếu SSR là lựa chọn duy nhất thì cũng khó giải thích được sự hứng khởi hiện tại với Web Assembly Bài viết về local computation cũng đáng đọc Cuối cùng thì không thể biết trước bên nào tốt hơn; cần có khả năng chọn theo thời điểm yêu cầu
Right Tool For The Right Job! Ví dụ, có thể làm việc với các tài liệu cộng tác như Google Docs, Sheets chỉ bằng SSR không? Nếu chỉ bám sát SSR, khi user chỉnh sửa giữa chừng thì người khác sẽ có thể đè nội dung lên Với các tool kiểu này, tác động thay đổi nhỏ, người dùng ngay lập tức nhận ra đang cộng tác và nhận cập nhật thời gian thực nên hoạt động tốt Ngược lại, dịch vụ đặt phòng khách sạn chẳng cần đồng bộ realtime Ngoài ra, app enterprise bị đồng bộ chập chờn giữa chừng hoặc thiết kế trước thiếu chuẩn thì sau này việc giữ tính nhất quán sẽ khá khó vì domain và độ phức tạp hệ thống
Nếu mình có thể dễ dàng thêm thư viện kiểu sau vào app SSR hiện tại thì sẽ dùng:
Đây là happy-pass engineering Với người không nằm trong happy-pass, nó khá bế tắc và thất vọng
Công dụng chính hiện tại và tương lai của SSR là load trang đầu tiên, đặc biệt là mobile Sau đó chỉ cần client cập nhật state nên mọi thứ phải nhanh hơn Nếu kỹ năng kỹ thuật kém, SSR vẫn không mang lại nhiều ý nghĩa
Mình đã phát triển phần mềm quản lý tasks mã nguồn mở theo kiến trúc local-first dựa trên CRDT Mình xây dựng vì không cần cộng tác và công cụ như Linear quá phức tạp cho mục đích sử dụng của mình Ưu điểm của kiến trúc này:
Mình rất ấn tượng với Jazz DX rất tốt, nên code gần như imperative/synchronous nên phát triển rất vui Cảm giác phản hồi tức thì, lại có thể làm việc offline nên UX tuyệt vời Vấn đề lớn nhất là deploy và tính bền vững dài hạn, với data tăng dần (mình không quá bận tâm vì hầu như client không cần xem hết) Vấn đề lớn nhất là thiếu giải pháp tốt cho public index hay thay đổi liên tục Về lý thuyết list of ids public readable là có thể, nhưng theo trao đổi gần đây với Anselm thì đang cố gắng giải quyết Tổng thể, local-first cũng tốn chi phí, nhưng nếu Jazz giải được nhược điểm lớn của mô hình server trung tâm truyền thống thì gần như có thể thay thế Firestore ở gần như mọi mặt
ElectricSQL và TanStack DB đều tuyệt vời, nhưng mình tò mò vì sao lại tập trung quá nhiều vào local first trên web Mình nghĩ trên mobile thì local first cần thiết hơn do offline Với trình duyệt, hầu hết thời gian internet đã có kết nối Hơn nữa, dù local-first nghe rất hấp dẫn về mặt lý thuyết, nếu giải quyết conflict không tốt thì trên thực tế dễ hỏng Đây là bài học khi làm mobile app theo local-first, và vì vậy mình dùng CRDT
Làm local-first app bằng web tech, so với native, khó hơn rất nhiều App native mặc định cho phép offline ngay sau khi cài Web phải ‘vá’ được offline bằng AppManifest, ServiceWorker v.v. Native thì cứ 30 năm code C đọc/ghi file disk là xong Trên web, IndexedDB là ác mộng, localStorage không ổn khi dữ liệu lớn, OriginPrivateFileSystem cũng có giới hạn Nếu người dùng chưa vào site trong một tháng thì Safari sẽ xóa trạng thái local browser Dù cố làm bằng JavaScript hoặc build wasm với Emscripten, cuối cùng vẫn phải xoay qua xoay lại với async web API để xử lý Apple đã có giải pháp hoàn chỉnh local+sync bằng CoreData + CloudKit từ 2015 Google thì Firebase có lẽ là gần giống nhất
Là dev của Replicache và Zero, mình đã suy nghĩ khá nhiều về vì sao lại tập trung vào web Mình chưa có đáp án rõ, nhưng chia sẻ vài ý:
Mobile có khả năng offline native mạnh hơn web Nhưng phần lớn productivity/collab thực sự diễn ra trên web, và web là môi trường khắc nghiệt hơn Có vấn đề sync state giữa các tab, xóa storage, v.v. Vì vậy local first chủ yếu phát triển theo hướng web
Các sync engine hướng web trước tiên vì công nghệ còn non và cập nhật nhanh Update app mobile đau đầu, còn webapp thì update dễ hơn nhiều Mình nghĩ năng lực PWA giờ đã khá tốt PWA trên iOS có push notification rồi; webapp trông như app mobile cũng đã đủ cho mình
Ở đây trọng tâm không phải ‘dùng được chức năng gì’ mà là cảm giác ‘vui’ khi product có tốc độ và responsiveness đáng kinh ngạc Vì vậy mình theo đuổi local-first
Mình cảm giác khía cạnh collaboration của local-first/sync-engine chưa được bàn sâu lắm Mình đang làm project thay thế Google Sheet business của bạn bằng Zero Làm việc realtime với nhau trên Google Meet mở cùng một Sheet Trước đây mình không tưởng tượng được triển khai kiểu này thành web app Mình đã làm nhiều UI realtime bằng WebSocket, nhưng mapping incoming data về đúng vị trí và quản lý nó rất phức tạp Nếu có hàng trăm, hàng nghìn ô dữ liệu thì sự rối ren ấy khó lường Với Zero, query chọn data và mutator update thì mọi người đồng bộ ngay lập tức cùng một lúc Nhờ đó việc phát triển thật sự thú vị
Mình nhớ khi Google Docs mới ra mắt (khi còn 12 tuổi), thấy realtime sync và collaborative cursors nên nghĩ tương lai web sẽ theo hướng collaborative kiểu này Khi đó cloud computing là từ khóa, mình tin sai rằng realtime collaboration là định nghĩa của cloud computing Cuối cùng tương lai đó không thành hiện thực 20 năm đã trôi qua mà đa số sản phẩm web vẫn là trải nghiệm CRUD (kể cả trang này) Mình nghĩ cách mạng mới sắp đến mãi, từng kỳ vọng Meteor.js, React, ... nhưng vẫn chưa phải mặc định Mình vẫn chờ thứ đó thành mainstream, và nghĩ một ngày nào đó sẽ thành hiện thực Chỉ là khó hơn tưởng tượng và tooling chưa đủ rẻ để áp dụng mọi tình huống
Ví dụ realtime collaboration là Discord (về bản chất gần như IRC thập niên 90), Zoom (mọi cuộc họp video) Việc site như HN là CRUD app là điểm cộng Cập nhật realtime đôi khi làm tản tâm nên mình không thích
Tốc độ ánh sáng là giới hạn thật Ánh sáng đi nửa vòng trái đất mất 56ms; tín hiệu thực tế đi đường vòng hơn, cộng thêm overhead chuyển đổi và rào cản khác (load balancer, DDOS protection, v.v.) khiến còn chậm hơn Latency nhiều khi còn tệ hơn trước
Mình cũng từng cảm thấy phần ma thuật ban đầu của realtime nhưng tương lai đó vẫn cứ như còn ở đâu đó Dẫu vậy, vẫn đang xây dựng một công cụ nhắn tin realtime theo phong cách Google Docs thật sự (không có ô input/send): kraa.io/hackernews
Mình tin vào tương lai của Local-First và Sync Engine Bảng tổng hợp landscape của local-first framework thật bổ ích Cá nhân mình thấy Triplit.dev hay nhất, và có thể kết hợp với TanStack DB PowerSync, NextGraph cũng là option muốn tìm hiểu Gần đây mình xem một bài NextGraph tại LocalFirst Conf khá hay: YouTube
Mình tò mò các công cụ này hỗ trợ migration DB như thế nào Khi lâu dài cần hỗ trợ client không kết nối server, việc rollback/roll-forward migration từ schema quá cũ có thể là một trở ngại lớn Khi debugging bug frontend một user riêng lẻ đã khó, nếu phải nghĩ đến cả schema state thì chắc phức tạp hơn tưởng tượng rất nhiều
Nhắc mình tới Meteor
Những cách này cũng là hướng đã có từ trước
Sẽ phải thử Triplit, và tò mò bạn đã từng dùng InstantDB chưa Mình cũng quan tâm, nhưng chưa dùng được
Mình thích cách tiếp cận của Electric nên đã theo dõi rất lâu Electric khá gọn vì đẩy phần phức tạp viết code sang cho developer và API Các giải pháp đồng bộ hai chiều thường chỉ vận hành tốt ở mức app Todo đơn giản Khi thêm quyền, phát triển business logic, migration, tăng trưởng sản phẩm thì durability sẽ là câu hỏi Electric chỉ hỗ trợ đồng bộ đọc thông qua view, còn write vẫn xử lý trong API/REST/RPC hiện có nên dễ đưa vào project đã có
Tại sao couchdb / pouchdb không có trong list? Đây vẫn là tools khá ổn