- SaaS · Multi-tenant
- B2B · Nhật Bản
- 59 PR đã merge
Benerio
Full-stack developer trên nền tảng SaaS B2B multi-tenant cho doanh nghiệp nhiều chi nhánh tại Nhật: xây dựng các tính năng Google Business Profile cốt lõi, công cụ cho agency đối tác và cơ chế gắn tài khoản theo gói kèm phân quyền theo chi nhánh, qua 600+ commit và 59 pull request.
Xem sản phẩm thực tế (mở trong tab mới)- Vai trò
- Full-stack
- Team
- ~3 người
- Thời gian
- 06/2025 – 12/2025
- Trạng thái
- Đang vận hành
600+
Commit (459 commit code)
59
Pull request đã merge
~60
Ticket phản hồi từ khách hàng
144
RLS policy của nền tảng
Next.js
TypeScript
Supabase
PostgreSQL
Tailwind CSS
Vercel
Bối cảnh
Benerio là nền tảng SaaS B2B multi-tenant cho thị trường Nhật Bản, giúp doanh nghiệp có nhiều chi nhánh quản lý hồ sơ Google Business Profile và tài khoản Instagram / Facebook / Threads từ một bảng điều khiển, có tích hợp AI. Các dịch vụ chạy trên nền tảng như những module riêng: GBP Manager, SNS Manager, công cụ cho agency đối tác quản lý hộ nhiều khách hàng, và một số module theo ngành.
Hệ thống dùng Next.js 14 (App Router) + TypeScript + Supabase, triển khai trên Vercel, giao diện tiếng Nhật và nội dung doanh nghiệp Nhật / Anh / Trung. Đây là một codebase lớn có khoảng 15 người đóng góp từ cuối 2024; trong giai đoạn tôi tham gia, thường có khoảng 3 developer làm cùng lúc. Tôi làm việc trên Benerio tại Protean Studios từ tháng 6 đến tháng 12/2025.
Vấn đề
- Doanh nghiệp nhiều chi nhánh phải đăng bài, trả lời đánh giá, cập nhật thông tin và theo dõi số liệu trên từng hồ sơ Google và từng tài khoản mạng xã hội riêng lẻ.
- Agency quản lý hộ nhiều khách hàng cần làm cùng những việc đó cho tất cả khách hàng của mình, nhưng chỉ trên những chi nhánh được giao.
- Nền tảng phải cách ly dữ liệu của từng công ty, phân quyền theo công ty và theo chi nhánh, và tính phí theo gói dịch vụ và mức sử dụng.
Vai trò của tôi
Tôi làm Full-stack trên toàn bộ tầng: migration và Row Level Security trong PostgreSQL, API route phía server và giao diện React. Bốn mảng chính:
- GBP Manager: chỉnh sửa thông tin doanh nghiệp, quản lý review, bài đăng có lên lịch, phân tích và báo cáo PDF, dịch bằng AI, cron đồng bộ.
- Công cụ cho agency đối tác: quản lý khách hàng, đăng bài GBP có nháp và lên lịch, xử lý bài lỗi, quản lý media.
- Gói dịch vụ và mức sử dụng: tài khoản Google gắn theo gói, phân quyền theo từng chi nhánh, RLS cho chi nhánh được agency hỗ trợ.
- Đồng bộ với Google: luồng OAuth liên kết / gỡ liên kết GBP, đồng bộ bài đăng và review.
Trong năm 2025: 619 commit (459 commit code), 59 pull request đã merge và khoảng 60 ticket phản hồi từ khách hàng Nhật — làm theo quy trình feature branch, pull request và code review, giao tiếp bằng tiếng Anh và tiếng Nhật.
Ràng buộc
- Codebase lớn, nhiều người cùng sửa (~185.000 dòng TypeScript): mọi thay đổi phải theo kiến trúc module và quy trình migration có sẵn.
- Cách ly dữ liệu phải đúng ngay cả khi code phía app sai: một công ty không bao giờ được thấy dữ liệu của công ty khác.
- Agency làm việc thay khách hàng: quyền phải đi xuyên tenant nhưng chỉ trong phạm vi chi nhánh được hỗ trợ.
- API bên ngoài: token OAuth của Google hết hạn, có quota và rate limit; dữ liệu trên Google có thể bị sửa hoặc xoá ngoài hệ thống.
- Khách hàng Nhật gửi phản hồi theo ticket đánh số; ba môi trường local / staging / production, production cần xác nhận tay khi deploy.
Kiến trúc
- Next.js 14 trên Vercel là lớp trung gian duy nhất: người dùng và cron job đều đi qua nó trước khi chạm Supabase hoặc API của Google, Meta và các nhà cung cấp LLM.
- Dịch vụ là plugin (do nền tảng xây): mỗi module khai báo route trong
manifest.json; một catch-all route chỉ nạp module khi dịch vụ đó được bật cho công ty. - Dữ liệu phân cấp Công ty → Chi nhánh → Người dùng; một người có thể thuộc nhiều công ty với nhóm quyền khác nhau. Cấu hình tích hợp nằm ở danh mục dịch vụ và bảng dịch vụ được bật cho từng công ty.
- Bảo vệ ở tầng database: 71 bảng, 144 RLS policy và 73 hàm PostgreSQL cách ly dữ liệu theo công ty và nhóm quyền.
- Tác vụ nền: 10 Vercel cron job — đăng bài đã lên lịch mỗi 5 phút, đồng bộ số liệu GBP và SNS hằng ngày / hằng tuần.
Trong sơ đồ, các khối được tô màu là phần tôi trực tiếp xây dựng; phần còn lại là nền tảng do cả đội phát triển.
Quyết định kỹ thuật chính
Gói dịch vụ và mức sử dụng nằm trong mô hình dữ liệu
- Vấn đề: tài khoản Google / mạng xã hội mà khách liên kết phải được tính theo gói đang dùng, và tính năng chỉ mở khi chi nhánh có gói phù hợp.
- Lựa chọn: mô hình
integrated_service_usagegắn từng tài khoản đã liên kết vào gói đang dùng; luồng gắn / gỡ tài khoản GBP theo gói; kiểm tra gói theo chi nhánh trước khi dùng tính năng; hàm RPC để ngắt kết nối dịch vụ gọn trong một lần gọi; trang theo dõi mức dùng AI. - Lý do: giới hạn của gói được áp ở dữ liệu, không phụ thuộc giao diện có ẩn nút hay không.
- Trade-off: nhiều bước kiểm tra hơn mỗi khi mở tính năng, và việc gỡ liên kết phải dọn dữ liệu nhất quán.
Hai lớp phân quyền: giao diện cho trải nghiệm, database cho bảo mật
- Lựa chọn: quyền truy cập theo từng chi nhánh cho mỗi người dùng; một hook kiểm tra quyền phía frontend để ẩn / hiện sidebar và thao tác; còn RLS trong PostgreSQL là lớp chặn thật.
- Lý do: kiểm tra phía giao diện giúp người dùng không thấy thao tác họ không được làm; RLS đảm bảo lỗi ở tầng app cũng không làm lộ dữ liệu.
- Trade-off: hai lớp phải giữ cùng một quy tắc.
RLS cho chi nhánh được agency hỗ trợ
- Vấn đề: agency cần thao tác trên chi nhánh của khách hàng — tức là đi xuyên tenant.
- Lựa chọn: RLS policy chỉ cấp quyền trên những chi nhánh được hỗ trợ, kèm trang quản lý khách hàng của agency và luồng chuyển quyền truy cập.
- Lý do: mô hình agency vẫn giữ được nguyên tắc cách ly ở tầng database.
Đồng bộ bài đăng với Google: upsert và dọn bài đã bị xoá
- Vấn đề: bài đăng có thể được tạo, sửa hoặc xoá trực tiếp trên Google, ngoài hệ thống.
- Lựa chọn: cron đồng bộ upsert bài đăng từ Google và xoá bài không còn tồn tại trên Google; gắn cron với gói dịch vụ.
- Lý do: chạy lại bao nhiêu lần cũng không tạo bản trùng, và không còn "bài ma" trong hệ thống.
Vòng đời bài đăng rõ ràng cho agency
- Lựa chọn: bài GBP có các trạng thái nháp → lên lịch → đã đăng / CANCELLED / lỗi, có xử lý bài đăng lỗi, media lưu trên Supabase Storage, cùng CRUD từ khoá, hashtag và mẫu bài đăng.
- Lý do: agency chuẩn bị nội dung trước cho nhiều khách hàng và cần thấy ngay bài nào chưa đăng được.
Đi theo kiến trúc sẵn có
- Lựa chọn: refactor các API GBP vào module dịch vụ theo kiến trúc plugin; mọi thay đổi database đi qua migration và sinh lại TypeScript types từ schema.
- Lý do: trong codebase nhiều người cùng làm, thêm tính năng không được làm hỏng routing lõi hay lệch kiểu dữ liệu giữa DB và code.
Đánh đổi
- Hai lớp phân quyền (giao diện + RLS): trải nghiệm tốt và an toàn, đổi lại quy tắc bị lặp ở hai nơi.
- Đồng bộ bằng cron thay vì webhook: đơn giản và dễ chạy lại, đổi lại dữ liệu có độ trễ tới lần chạy kế tiếp.
- Giới hạn gói áp ở dữ liệu: chắc chắn hơn, đổi lại thêm kiểm tra cho mỗi thao tác.
- Codebase lớn của cả đội: đi theo kiến trúc và quy trình review có sẵn chậm hơn làm riêng, đổi lại hệ thống nhất quán.
Điểm nổi bật khi triển khai
- Thông tin doanh nghiệp trên GBP: danh mục phụ, địa chỉ, khu vực phục vụ, giờ mở cửa; quản lý review gồm danh sách, lọc, trả lời, xoá câu trả lời và đồng bộ review.
- Bài đăng GBP: màn hình bài đăng có upload ảnh, huỷ / xoá bài đã lên lịch; dịch nội dung bằng AI.
- Phân tích và báo cáo: từ khoá tìm kiếm hàng đầu, số review, điểm sao, ghim bản đồ; cài đặt và xuất báo cáo PDF (logo, bảng từ khoá).
- Nền tảng dùng chung: bộ chọn công ty / chi nhánh, upload file lên Supabase Storage, màn hình chi tiết dịch vụ tích hợp, module hỗ trợ dùng chung cho các công cụ agency.
- Tích hợp Google: luồng OAuth GBP, liên kết / gỡ liên kết tài khoản, ghi log lỗi Google API.
- Database: tự viết migration (thêm cột, bỏ unique constraint, RLS, hàm RPC) và sinh lại TypeScript types.
- Làm việc với khách hàng: khoảng 60 ticket phản hồi từ khách hàng Nhật; sửa theo góp ý code review.
Kết quả
- Bàn giao bốn mảng tính năng trong năm 2025: GBP Manager, công cụ cho agency, gói dịch vụ và phân quyền theo chi nhánh, đồng bộ với Google.
- 619 commit (459 commit code), khoảng +116.000 / −45.000 dòng, 413 file mới và 59 pull request đã merge.
- Xử lý khoảng 60 phản hồi trực tiếp từ khách hàng Nhật Bản.
Bài học
- Cách ly dữ liệu giữa các khách hàng phải nằm ở database; kiểm tra phía app chỉ để phục vụ trải nghiệm.
- Gói dịch vụ, đo mức dùng và bật / tắt tính năng là bài toán mô hình dữ liệu trước khi là bài toán giao diện.
- Đồng bộ với API bên ngoài cần upsert, dọn dữ liệu đã bị xoá, và xử lý token hết hạn cùng giới hạn gọi API.
- Trong đội lớn, đi đúng kiến trúc và quy trình (module, migration, review) quan trọng hơn một giải pháp riêng thông minh.