Mình đã triển khai multi-tenant như thế nào trong dự án NextJS?
Yêu cầu của bài toán
Yêu cầu đặt ra là:
- phát triển nhiều website
- các website có khả năng kế thừa và customize từ 1 template
- phát triển trên một app NextJS
Hướng giải quyết thứ nhất
Như bạn biết, hệ thống routing trong NextJS dựa trên hệ thống quản lí file trên server.
Ví dụ như trang web của chúng ta như sau
/ --> homepage
/products/1 --> chi tiết sản phẩm có id là 1
/products --> danh sách sản phẩm
do đó, chúng ta phải tạo thư mục pages trong ứng dụng NextJS như sau:
└── pages
├── index.tsx --> /
└── products
├── [id].tsx --> /products/1
└── index.tsx --> /products
Với yêu cầu thứ 1, chúng ta đơn giản chỉ tạo thêm một layer là website trong thư mục pages như sau
Source code sẽ trông như thế này:
.
└── pages
├── template
│ ├── index.tsx
│ └── products
│ ├── [id].tsx
│ └── index.tsx
├── website-A
│ ├── index.tsx
│ └── products
│ ├── [id].tsx
│ └── index.tsx
└── website-B
├── index.tsx
└── products
├── [id].tsx
└── index.tsx
Bạn có thể thấy, chúng ta có một thư mục template và các thư mục kế thừ từ nó. Nếu website-A cần customize, chúng ta chỉ cần thêm file tương ứng trong template. Trong trường hợp không muốn customizes, chúng ta đơn giản chỉ re-export component có trong template.
Đến đây, website của chúng ta sẽ có dạng như sau:
server.com/website-A/products/1
server.com/website-B/products
Vậy nếu chúng ta muốn người dùng truy cập từ domain: website-A.com hay website-B.com lần lượt tương ứng với các folder của từng website như website-A và website-B thì sao?
NextJS cung cấp cho chúng ta giải pháp là custom server. Tại đây, chúng ta có thể nhận toàn bộ request từ người dùng và xử lí theo ý của chúng ta bao gồm việc dispatch cho ứng dụng NextJS xử lí.
import { parse } from "url";
function handleRequest(req, res) {
const parsedUrl = parse(req.url, true);
const { pathname, query } = parsedUrl;
const tenant = req.hostname.split(".")[0];
app.render(req, res, `${tenant}${pathname}`, query);
}
Đoạn code trên sẽ chuyển domain xuống pathname của ứng dụng NextJS.
Vậy vấn đề của giải pháp này là gì?
Như bạn có thể thấy, việc tạo thêm những page mà page đó không cần customize nó sẽ rất vô nghĩa và lãng phí tài nguyên.
Lấy ví dụ trên, chúng ta không cần customize pages /products/[id]
của website-A nhưng trong thư mục của website-A vẫn phải có file [id].tsx
mặc dù chúng chỉ có tác dụng re-export từ template.
Điều này dẫn tới 2 điều sau:
- nếu app của chúng ta có 20 website và template có 50 pages => app chúng ta phải handle 1000 route khác nhau => có 1000 file chúng ta phải xử lí trong quá trình dev và deploy.
- NextJS sẽ sinh ra hai file build-manifest và route-manifest cho client, các file này chứa thông tin của toàn bộ route mà app có, bạn có thể xem source ở đây. Nếu số lượng route lớn, việc download, parser và lookup file đó ở browser sẽ cực kì chậm.
Giải pháp tốt hơn
Bản chất của giải pháp đầu tiên là clone toàn bộ template cho từng website cụ thể và modify nó.
Thay vì clone, chúng ta có thể sửa phần routing để nó có thể trỏ về template nếu như page đó không cần customize. Đây chính là ý tưởng chính của giải pháp này.
Ở đây chúng ta chỉ cần tạo ra các file cần được customize cho từng website cụ thể