
Trong nhiều năm, Vercel là nơi mặc định để chúng tôi triển khai gần như mọi thứ trên web tại WebCatalog. Phần lớn các dự án đó dùng Next.js, nên việc kết hợp hai nền tảng là điều tự nhiên. Các trang marketing, giao diện sản phẩm, phần cài đặt tài khoản, bảng điều khiển dành cho nhà phát triển, hệ thống xác thực, công cụ quản trị và API của chúng tôi đều dùng những bộ công nghệ gần như giống nhau.
Cách làm đó hoạt động tốt trong một thời gian dài. Vercel giúp việc triển khai trở nên đơn giản, Next.js cung cấp một framework full-stack giúp làm việc hiệu quả, và chúng tôi hiếm khi phải nghĩ đến hạ tầng bên dưới.
Cuối cùng, sự tiện lợi ấy không còn phù hợp với đặc điểm sản phẩm của chúng tôi.
Các trang marketing vẫn hưởng lợi từ SSR (kết xuất phía máy chủ), nhưng phần lớn ứng dụng web khác thì không. Chúng có thể chỉ là những SPA (ứng dụng một trang) thông thường. API của chúng tôi cần một môi trường Node.js bình thường, có hỗ trợ các thư viện phụ thuộc native và những tiến trình chạy lâu dài. Gần như toàn bộ phần còn lại của bộ công nghệ frontend đã dùng Vite. Đồng thời, chi phí hạ tầng ngày càng khó bỏ qua, đặc biệt khi lưu lượng truy cập công khai và tự động tăng lên.
Ban đầu, chúng tôi cố tối ưu những gì mình đang có. Chúng tôi cân nhắc giữ Next.js và chuyển sang Cloudflare Workers thông qua một adapter. Chúng tôi thử Astro cho các trang marketing. Chúng tôi cũng có một thời gian ngắn chạy API trên Cloudflare Containers.
Cuối cùng, chúng tôi chọn một phương án đơn giản hơn: phần lớn ứng dụng web hiện dùng Vite + TanStack Router trên Cloudflare Workers, các trang marketing dùng TanStack Start với SSR trên Cloudflare Workers, còn API dùng Hono + tRPC trên Railway dưới dạng các container Node.js chạy lâu dài.
Quá trình chuyển đổi giúp giảm khoảng 80% chi phí hạ tầng web. Sau khi chúng tôi siết chặt thêm các quy tắc bảo mật của Cloudflare và chặn nhiều lưu lượng tự động độc hại hơn ngay tại biên mạng, tổng mức tiết kiệm tăng lên khoảng 90%.
Phần lớn công việc triển khai chuyển đổi cũng do các tác nhân AI viết mã thực hiện, chủ yếu là Claude Fable 5 và Claude Opus 5. Các kỹ sư quyết định kiến trúc, xác định ràng buộc, rà soát thay đổi và kiểm chứng kết quả.
Chúng tôi không bắt đầu bằng việc rời Vercel
Phản ứng đầu tiên của chúng tôi là tối ưu hệ thống hiện có.
webcatalog.io là điểm khởi đầu rõ ràng nhất vì nhận nhiều lưu lượng truy cập công khai, trong khi phần lớn các trang của nó tương đối ít thay đổi. Chúng tôi nhận thấy có quá nhiều phần của trang web vẫn được kết xuất động, nên đã sửa những trường hợp kết xuất động ngoài ý muốn, thêm ISR (tái tạo tĩnh tăng dần) cho các tuyến có nhiều lượt truy cập, giúp nhiều trang có thể được lưu bộ nhớ đệm hơn và tối ưu quy trình tạo sitemap tốn nhiều tài nguyên.
Công việc đó có ích, nhưng cũng làm vấn đề cốt lõi trở nên rõ ràng hơn. Chúng tôi ngày càng dành nhiều thời gian để tìm hiểu vì sao nội dung tương đối tĩnh lại được kết xuất động, hành vi nào của framework đã gây ra điều đó, và phải dùng cơ chế nào của framework để nội dung có thể được lưu bộ nhớ đệm trở lại.
Trong khi đó, nhiều ứng dụng khác của chúng tôi lại gặp vấn đề ngược lại. Phần cài đặt tài khoản, bảng điều khiển dành cho nhà phát triển, các ứng dụng quản trị và phần lớn giao diện sản phẩm hoàn toàn không cần kết xuất phía máy chủ. Chúng là các ứng dụng chạy trong trình duyệt và giao tiếp với API.
Đến lúc ấy, câu hỏi không còn là “Làm sao để giảm hóa đơn Vercel?” mà trở thành “Nếu những ứng dụng này vốn không được xây dựng bằng Next.js, chúng ta sẽ xây dựng chúng như thế nào?”
Chúng tôi đã cân nhắc Next.js trên Cloudflare
Phương án ít gây xáo trộn nhất là giữ Next.js và chuyển từ Vercel sang Cloudflare Workers.
Chúng tôi đã nghiêm túc cân nhắc phương án này vì Next.js có thể chạy trên Cloudflare thông qua các lớp adapter, cho phép giữ lại phần lớn cấu trúc ứng dụng hiện có.
Nhưng chúng tôi không xem Next.js trên Cloudflare là tương đương với Next.js trên Vercel. Next.js được tích hợp chặt chẽ nhất với môi trường chạy, hệ thống triển khai, cơ chế lưu bộ nhớ đệm và các tính năng nền tảng của Vercel. Trên Cloudflare, một adapter phải chuyển những giả định đó sang một môi trường chạy khác.
Cách đó có thể hoạt động tốt, nhưng nếu mục tiêu là đơn giản hóa bộ công nghệ, việc thêm một lớp tương thích nữa không có vẻ lý tưởng.
Còn có vấn đề rộng hơn về công cụ. Gần như mọi thứ khác mà chúng tôi xây dựng ở frontend đều đã dùng Vite: ứng dụng máy tính, tiện ích mở rộng trình duyệt, SPA và các dự án phía máy khách khác.
Next.js đã chuyển mạnh sang Turbopack, và Turbopack đã cải thiện rất nhiều. Đây không phải lời phàn nàn về hiệu năng. Với chúng tôi, khác biệt lớn hơn nằm ở hệ sinh thái. Vite đã là nền tảng chung của phần lớn mã nguồn frontend, với hệ sinh thái plugin phong phú hơn và nhiều cấu hình có thể tái sử dụng giữa các dự án hơn.
Vì vậy, giữ Next.js trên Cloudflare sẽ giải quyết được một phần vấn đề lưu trữ, nhưng vẫn duy trì sự không đồng nhất về framework và một hệ sinh thái công cụ build riêng biệt.
Thế là chúng tôi lùi lại một bước và xem từng loại ứng dụng thực sự cần gì.
Chúng tôi tách bộ công nghệ theo từng loại ứng dụng
Khi không còn tìm kiếm một giải pháp thay thế Next.js duy nhất cho tất cả, kiến trúc trở nên đơn giản hơn nhiều.
Các trang marketing cần SSR vì chúng có những trang công khai được bản địa hóa, metadata, URL canonical, sitemap và lưu lượng truy cập từ công cụ tìm kiếm.
Phần lớn ứng dụng web khác không cần SSR và có thể đơn giản là ứng dụng Vite dùng TanStack Router.
API thì hoàn toàn không cần framework React và phù hợp hơn với một môi trường chạy Node.js thông thường.
Cách phân chia cuối cùng như sau:
Ứng dụng web
Vite + TanStack Router
│
└── Cloudflare Workers
Trang marketing
TanStack Start + SSR
│
└── Cloudflare Workers
API
Hono + tRPC
│
└── Railway / container Node.js
Nguyên tắc trở nên rõ ràng: dùng SSR ở nơi nó mang lại giá trị thực sự, dùng SPA thông thường ở nơi không cần SSR, và chạy các dịch vụ backend trong một môi trường chạy dành cho backend.
Phần lớn ứng dụng web trở thành SPA
Chuyển các ứng dụng sang SPA là phần dễ nhất.
Ứng dụng web WebCatalog được chuyển đặc biệt nhanh: chúng tôi tạo bộ khung thay thế bằng Vite + TanStack Router, chuyển giao diện sang, đổi nơi triển khai sang Cloudflare Workers và xóa phiên bản Next.js cũ, tất cả trong cùng một buổi sáng.
Chúng tôi lặp lại cách làm đó với ứng dụng web Lexibird, phần cài đặt tài khoản, bảng điều khiển dành cho nhà phát triển, hệ thống xác thực và các ứng dụng quản trị. Từ lúc tạo bộ khung SPA đầu tiên đến khi xóa ứng dụng Next.js cuối cùng mất khoảng 19 ngày.
Với những ứng dụng này, Cloudflare Workers được chủ đích dùng theo cách rất đơn giản. Vite build các tài nguyên tĩnh, Cloudflare phân phối chúng, còn các tuyến của ứng dụng thì chuyển về index.html khi không khớp. Không có môi trường chạy SSR vì chẳng có gì cần kết xuất phía máy chủ.
Phần khó hơn là giữ lại những hành vi đã tích lũy quanh các ứng dụng đó theo thời gian. Một số logic xác thực phải được chuyển khỏi các tuyến máy chủ của Next.js sang API, và các phiên bản cũ của ứng dụng máy tính WebCatalog vẫn phụ thuộc vào những endpoint cũ mà chúng tôi phải duy trì hoạt động.
Chuyển giao diện React thì dễ.
Giữ nguyên các giao ước tương thích thì khó hơn.
Chúng tôi thử Astro rồi xóa nó sau năm ngày
Các trang marketing khó hơn.
Lựa chọn đầu tiên của chúng tôi là Astro, có vẻ rất phù hợp với webcatalog.io và lexibird.com vì cả hai đều là trang công khai có nhiều nội dung, còn Astro tích hợp tốt với Cloudflare.
Chúng tôi bắt đầu chuyển cả hai trang, rồi dừng lại sau năm ngày.
Không có một điểm yếu chí mạng nào. Thay vào đó, các bất cập nhỏ cứ tích tụ dần. Một số tuyến cần truy cập cơ sở dữ liệu khi kết xuất trước, trong khi môi trường build của chúng tôi được chủ ý không cấp thông tin đăng nhập cơ sở dữ liệu production. Các React island khiến việc chia sẻ trạng thái ứng dụng kém tự nhiên hơn. Chúng tôi cũng gặp khác biệt giữa các tuyến được kết xuất trước và các tuyến được kết xuất khi chạy, cùng một số vướng mắc về công cụ trong kho mã nguồn.
Không vấn đề nào trong số đó là không thể giải quyết. Chính vì vậy mà tiếp tục lại nguy hiểm. Chúng tôi rất dễ dành thêm vài tuần để sửa hết vấn đề này đến vấn đề khác.
Thay vào đó, chúng tôi xóa phần triển khai ấy và bắt đầu lại.
Các tác nhân AI đã thay đổi bài toán chi phí của quyết định đó. Phần lớn công việc chuyển mã mang tính cơ học không tiêu tốn hàng tuần làm việc của kỹ sư, nên chi phí đã bỏ ra thấp hơn và chúng tôi dễ thừa nhận rằng mình thích một kiến trúc khác hơn.
TanStack Start phù hợp hơn
Chúng tôi bắt đầu lại việc chuyển các trang marketing từ mã Next.js gốc, lần này dùng TanStack Start.
Nó phù hợp tự nhiên hơn nhiều vì chúng tôi đã dùng TanStack Router trong các ứng dụng SPA. Do đó, định tuyến, loader, tham số tìm kiếm và điều hướng đều tuân theo những khái niệm tương tự. Mã nguồn vẫn là React và TypeScript thông thường, còn hệ thống build dùng Vite.
Chúng tôi chuyển lexibird.com sang TanStack Start trong một ngày. webcatalog.io được chuyển sau đó.
Ưu điểm không phải là TanStack Start giải quyết mọi vấn đề một cách thần kỳ, mà là nó phù hợp với hướng đi sẵn có của phần còn lại trong bộ công nghệ.
Trong một monorepo, điều đó rất quan trọng. Dùng chung công cụ build giúp tái sử dụng nhiều plugin và cấu hình hơn, giảm các trường hợp đặc biệt khi gỡ lỗi, giúp nhà phát triển ít phải chuyển đổi ngữ cảnh hơn, và giảm số quy ước riêng của từng dự án mà các tác nhân viết mã phải tự tìm hiểu.
Cloudflare rẻ hơn nhiều đối với lưu lượng truy cập của chúng tôi
Kiến trúc chỉ là một phần lý do hóa đơn giảm. Cách định giá của Cloudflare là yếu tố quan trọng, đặc biệt là băng thông.
Gói Workers Paid của Cloudflare hiện chủ yếu tính phí theo số yêu cầu và mức sử dụng CPU, và không tính thêm phí truyền dữ liệu hoặc băng thông cho Workers. (developers.cloudflare.com) Điều đó rất quan trọng với các trang web công khai, vì một yêu cầu có thể dùng rất ít CPU nhưng vẫn truyền HTML, JavaScript, hình ảnh, phông chữ và các tài nguyên khác.
Mô hình định giá của Vercel cũng có các hạng mục sử dụng và hạn mức liên quan đến mạng, bao gồm Fast Data Transfer và Fast Origin Transfer. (vercel.com) Với đặc điểm lưu lượng truy cập của chúng tôi, Cloudflare tiết kiệm hơn đáng kể.
Khoản tiết kiệm không chỉ đến từ việc đổi nhà cung cấp. Chúng tôi cũng chuyển nhiều ứng dụng sang các bản build Vite tĩnh, giảm SSR không cần thiết, quy định rõ hơn việc lưu bộ nhớ đệm và chuyển API sang một môi trường chạy phù hợp hơn. Nhưng cách định giá của Cloudflare, đặc biệt về băng thông, là yếu tố đóng góp lớn vào mức giảm khoảng 80% mà chúng tôi ghi nhận.
Con số đó chỉ đúng với khối lượng công việc của chúng tôi. Nó không có nghĩa là mọi ứng dụng chuyển từ Vercel sang Cloudflare đều sẽ tiết kiệm 80%.
Các API cuối cùng được đưa lên Railway
API là một bài toán riêng.
Dù là các dự án Next.js, chúng vốn đã phần lớn độc lập với Next.js. API của WebCatalog có khoảng 34.000 dòng mã, nhưng chỉ bảy tệp import next/server. tRPC đã dùng Fetch adapter, còn Inngest hỗ trợ Hono.
Chúng tôi thay lớp HTTP của Next.js bằng Hono. Phần việc đó ít đến bất ngờ.
Câu hỏi khó hơn là chạy nó ở đâu. Workers thông thường không phù hợp vì API dùng Node.js và các thư viện phụ thuộc native như sharp.
Ban đầu chúng tôi thử Cloudflare Containers, nhờ đó nhanh chóng rời Vercel. Nhưng sau khi vận hành cấu hình đó trong production, chúng tôi nhận thấy mô hình vận hành đòi hỏi điều chỉnh dung lượng thủ công nhiều hơn mức mong muốn lúc bấy giờ.
Vì thế, chúng tôi lại chuyển API một lần nữa.
Hiện nay, chúng chạy trên Railway dưới dạng các dịch vụ container Node.js thông thường, hoạt động lâu dài. CI (tích hợp liên tục) build các Docker image rồi đẩy lên registry của chúng tôi, còn Railway xử lý việc tự động mở rộng theo chiều ngang.
Không phải thứ gì cũng cần chạy ở biên mạng.
Rời Vercel đồng nghĩa với việc tự gánh vác nhiều hơn
Chi phí thấp hơn đi kèm một sự đánh đổi.
Vercel và Next.js trước đây đã xử lý nhiều hành vi hạ tầng ở mức trừu tượng cao hơn. Với TanStack Start và Workers, chúng tôi chuyển sang cơ chế lưu bộ nhớ đệm HTTP rõ ràng hơn. Một trang được kết xuất, chúng tôi trả về các header điều khiển bộ nhớ đệm, rồi Cloudflare lưu phản hồi vào bộ nhớ đệm. Việc lưu bộ nhớ đệm ở trình duyệt và ở biên mạng có thể áp dụng các chính sách khác nhau, bao gồm cơ chế stale-while-revalidate tại biên mạng.
Chúng tôi thích việc những quyết định đó trở nên rõ ràng, nhưng khi tự quản lý hạ tầng một cách tường minh, bạn cũng phải tự chịu trách nhiệm về các sai sót.
Chúng tôi đã mắc một vài lỗi. Một quy tắc lưu bộ nhớ đệm trong production vô tình khớp với các phản hồi từ server function của TanStack dành riêng cho từng khách truy cập; một cấu hình lưu bộ nhớ đệm khác lại tương tác không tốt với cơ chế chuyển về trang dự phòng của SPA. Những lỗi đó nhắc chúng tôi rằng không thể xác nhận toàn bộ tính đúng đắn của quá trình chuyển đổi chỉ bằng cách xem kho mã nguồn. Hạ tầng production cũng là một phần của hệ thống.
Các tác nhân AI đã thay đổi cách chúng tôi chuyển đổi
Phần lớn công việc triển khai được thực hiện bởi các tác nhân viết mã, chủ yếu là Claude Fable 5 và Claude Opus 5.
Việc chuyển đổi framework đặc biệt phù hợp với các tác nhân vì phần lớn công việc đã có một phiên bản hiện hữu để tham chiếu rõ ràng. Chuyển tuyến này, giữ nguyên URL này, thay API này của framework, giữ nguyên metadata, chạy kiểm tra kiểu, sửa lỗi, rồi so sánh kết quả với phiên bản cũ.
Với webcatalog.io, chúng tôi duy trì một bảng theo dõi quá trình chuyển đổi, chia công việc thành các giai đoạn như nền tảng, trang sản phẩm, trang danh mục, tìm kiếm, blog, giá, chuyển hướng, sitemap và chuyển đổi vận hành. Thay vì yêu cầu một tác nhân “chuyển webcatalog.io sang TanStack Start”, chúng tôi giao những nhiệm vụ có phạm vi rõ ràng cùng các điều kiện bắt buộc phải giữ nguyên.
Ứng dụng hiện có trở thành bản đặc tả. Công việc của tác nhân là tái tạo hành vi bằng kiến trúc mới, chứ không phải thiết kế lại sản phẩm.
Điều đó chuyển công sức của con người từ việc tự tay chuyển mã sang những câu hỏi như: Trang này có thực sự cần SSR không? Hành vi nào là có chủ đích? Nội dung nào được phép lưu bộ nhớ đệm dùng chung? Những URL nào phải giữ nguyên? Việc xác thực nên diễn ra ở đâu? Khi nào nên ngừng cố làm cho một phương án hoạt động?
Chúng tôi vẫn rà soát kết quả, chạy build và kiểm tra kiểu, đồng thời xác minh thủ công những phần rủi ro cao hơn như xác thực, SEO (tối ưu hóa công cụ tìm kiếm), chuyển hướng và lưu bộ nhớ đệm.
Thay đổi quan trọng không phải là AI viết mã thay chúng tôi, mà là AI giúp việc thử nghiệm trở nên ít tốn kém hơn.
Việc thử Astro rồi xóa nó sau năm ngày trở nên dễ chấp nhận hơn nhiều. Chuyển API một lần rồi quyết định rằng một nền tảng khác phù hợp hơn cũng ít gây tổn thất hơn. Công việc mang tính cơ học ít tốn kém hơn, nên chúng tôi có thể đổi hướng khi nhận ra kiến trúc không đúng, thay vì tiếp tục chỉ vì đã đầu tư quá nhiều vào nó.
Các tác nhân giúp nguồn lực triển khai bớt khan hiếm.
Điều đó khiến kiến trúc, các ràng buộc, việc rà soát và xác minh càng trở nên quan trọng.
Từ 80% lên khoảng 90%
Sau khi chuyển đổi, chi phí hạ tầng web của chúng tôi thấp hơn khoảng 80%.
Sau đó, chúng tôi bắt đầu chú ý hơn đến việc những yêu cầu nào thực sự đến được ứng dụng.
Một lượng đáng kể lưu lượng truy cập trên internet công khai là lưu lượng tự động: công cụ tìm kiếm, trình thu thập dữ liệu của AI, tác nhân, công cụ quét thu thập dữ liệu, công cụ rà quét lỗ hổng và các bot kém thân thiện hơn. Một phần lưu lượng đó hữu ích, phần khác thì không.
Khi các ứng dụng được đặt trực tiếp sau Cloudflare, chúng tôi siết chặt các quy tắc bảo mật để lưu lượng tự động độc hại bị từ chối ngay tại biên mạng, trước khi nó kích hoạt xử lý của ứng dụng hoặc truy vấn cơ sở dữ liệu.
Sau đó, tổng mức tiết kiệm của chúng tôi đạt khoảng 90% so với hệ thống cũ.
Mức giảm cuối cùng đến từ sự kết hợp của nhiều yếu tố: giá Cloudflare thấp hơn, đặc biệt về băng thông; ít xử lý phía máy chủ hơn; môi trường chạy phù hợp hơn cho API; cơ chế lưu bộ nhớ đệm rõ ràng hơn; và ít yêu cầu không cần thiết đến được ứng dụng hơn.
Hệ thống hiện tại của chúng tôi
Không còn ứng dụng Next.js nào trong kho mã nguồn. Phần lớn ứng dụng web hiện là SPA dùng Vite + TanStack Router trên Cloudflare Workers. webcatalog.io và lexibird.com dùng TanStack Start trên Cloudflare Workers vì các trang này thực sự hưởng lợi từ SSR. API của chúng tôi dùng Hono + tRPC trên Railway, chạy dưới dạng các container Node.js thông thường. Gần như tất cả dự án frontend hiện thuộc hệ sinh thái Vite.
Chi phí hạ tầng giảm khoảng 80%, trong đó lợi thế chi phí đáng kể của Cloudflare đối với lưu lượng truy cập của chúng tôi, đặc biệt là băng thông, đóng góp rất lớn. Việc lọc lưu lượng tự động độc hại ngay tại biên mạng đẩy tổng mức tiết kiệm lên khoảng 90%.
Nhưng kết quả chúng tôi quan tâm nhất nằm ở kiến trúc. Trang marketing được dùng SSR, mọi ứng dụng có thể là SPA đều là SPA, và API chạy trên máy chủ thực sự khi đó là giải pháp đơn giản hơn.
Chúng tôi bắt đầu bằng việc cố giảm chi phí Vercel.
Cuối cùng, chúng tôi nhận ra mình không cần phần lớn kiến trúc mà mình đang trả tiền để sử dụng.