TanStack Start và Lovable
Nối @belvador/tanstack-start cho cả trình duyệt và máy chủ Cloudflare Workers: router, server entry và middleware.
Trong bài này
@belvador/tanstack-start báo lỗi từ cả hai nửa của một ứng dụng TanStack Start: trình duyệt và máy chủ chạy trên Cloudflare Workers. Ứng dụng làm bằng Lovable là ứng dụng TanStack Start chạy trên Workers, nên đây cũng là hướng dẫn cho chúng.
Một gói, nhưng nối vào ba chỗ: router (trình duyệt), server entry và middleware. Mỗi phần báo những gì chỉ nó mới thấy.
Trước khi bắt đầu#
Một project có nền tảng TanStack Start / Lovable và DSN của nó. Phần trình duyệt và phần máy chủ dùng chung project.
@tanstack/react-routervà@tanstack/react-starttừ 1.168 trở lên, React 18 hoặc 19.Máy chủ chạy trên Cloudflare Workers với cờ tương thích
nodejs_compat. Ứng dụng Lovable đã có sẵn.Một server entry riêng:
vite.config.tsđặttanstackStart: { server: { entry: "server" } }. Ứng dụng Lovable đã có sẵn.
1. Cài đặt#
npm install @belvador/tanstack-start@0.6.12. Trình duyệt — src/instrument.ts và src/router.tsx#
Tạo src/instrument.ts. File này khởi động Belvador trên trình duyệt:
import { init } from "@belvador/tanstack-start/client";// Imported first by src/start.ts and src/router.tsx. Runs only in the browser.if (typeof window !== "undefined") { init({ dsn: "https://<public-key>@ingest.belvador.dev/<project-id>", release: import.meta.env.VITE_APP_VERSION, // your build or commit id environment: import.meta.env.MODE, });}Import nó ở dòng đầu tiên của src/router.tsx, trước route tree:
import "./instrument"; // first line, before the route treeimport { withBelvadorRouter } from "@belvador/tanstack-start/client";// In getRouter(): wrap the options you already pass to createRouter.// createRouter(withBelvadorRouter({ routeTree, ...yourOptions }))// It reports route errors, calls your own defaultOnCatch after that, and adds// TanStack's error component only if you set no defaultErrorComponent.initchỉ chạy trên trình duyệt (typeof window !== "undefined"); phần máy chủ cấu hình riêng ở dưới.Các import chạy trước phần còn lại của file. Nếu
initnằm trong thânsrc/router.tsx, lỗi xảy ra lúc một module route hoặcsrc/start.tsđang nạp sẽ đến trướcinitvà không bao giờ được báo; trang vẫn hiện nhờ server render nhưng không bấm được gì. Bước 4 import cùng file này ở đầusrc/start.ts, vì trình duyệt nạp file đó trước router.withBelvadorRouter(options)bọc các tuỳ chọn bạn đang truyền chocreateRouter. Hàm này đặtdefaultOnCatchđể báo lỗi mà error boundary của route bắt được, rồi gọidefaultOnCatchcủa bạn nếu có. TanStack Router chỉ gọidefaultOnCatchở nơi có error component (lớp dự phòng ngoài cùng không gọi), nên khi bạn chưa đặtdefaultErrorComponent, helper dùngErrorComponentcó sẵn của TanStack.errorComponentriêng của từng route vẫn được ưu tiên. Trong thí nghiệm của mình, một route bị crash trong ứng dụng không có error component nào được báo 0/3 lần chỉ vớidefaultOnCatch: belvadorOnCatch, và 3/3 lần với helper.Trước 0.5.0, cách cài là
defaultOnCatch: belvadorOnCatchcộng một error component; cách đó vẫn chạy vàbelvadorOnCatchvẫn được export.Lỗi chưa bắt và promise bị từ chối không xử lý trên trình duyệt được bắt ngay khi
initchạy.Đừng đặt tên file là
instrument.client.ts: cơ chế import protection của TanStack Start chặn file.clientđược import từsrc/router.tsx, vì file đó cũng chạy trên server.
3. Máy chủ — src/server.ts#
import handler from "@tanstack/react-start/server-entry";import { withBelvador } from "@belvador/tanstack-start/server";// Requires Cloudflare Workers with nodejs_compat enabled.// If src/server.ts already exists, wrap its default export instead of `handler`.// vite.config.ts needs tanstackStart: { server: { entry: "server" } } (Lovable apps have it).export default withBelvador( (env: { BELVADOR_DSN?: string; BELVADOR_RELEASE?: string }) => ({ dsn: env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>", release: env.BELVADOR_RELEASE, }), handler,);Nếu ứng dụng đã có src/server.ts — ứng dụng Lovable có sẵn — hãy bọc default export của file đó thay cho handler, và giữ nguyên mọi thứ nó đang làm, ví dụ:
import { withBelvador } from "@belvador/tanstack-start/server";const app = { async fetch(request: Request, env: unknown, ctx: unknown) { // …server entry hiện có, giữ nguyên… },};export default withBelvador( (env: { BELVADOR_DSN?: string; BELVADOR_RELEASE?: string }) => ({ dsn: env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>", release: env.BELVADOR_RELEASE, }), app,);withBelvador cho mỗi request một phạm vi riêng, báo lỗi rồi ném lại lỗi, và trả về response của bạn không đổi. Việc gửi hoàn tất sau khi trả response, trong waitUntil.
Cấu hình phía máy chủ lấy từ biến của Worker nếu có: BELVADOR_DSN, BELVADOR_RELEASE (nếu không có thì dùng CF_VERSION_METADATA.id), BELVADOR_ENVIRONMENT (mặc định production) và BELVADOR_DISABLED=1. SDK không bao giờ đọc biến SENTRY_*.
4. Middleware — src/start.ts#
import "./instrument"; // first line: the browser loads start.ts before the routerimport { createStart } from "@tanstack/react-start";import { belvadorFunctionMiddleware, belvadorRequestMiddleware } from "@belvador/tanstack-start";export const startInstance = createStart(() => ({ // Last in each list, after your own middleware. requestMiddleware: [/* …your middleware, */ belvadorRequestMiddleware], functionMiddleware: [/* …your middleware, */ belvadorFunctionMiddleware],}));Dòng đầu tiên import ./instrument, giống bước 2: trên trình duyệt, src/start.ts và các import của nó nạp trước router. Đặt cả hai middleware cuối cùng trong danh sách — gần handler nhất — để chúng thấy lỗi trước khi middleware trang lỗi biến lỗi thành response. Nếu đã có src/start.ts, nối thêm vào các mảng hiện có và giữ phần còn lại.
5. CSP#
connect-src 'self' https://ingest.belvador.dev;Gộp vào chính sách hiện có của ứng dụng — ví dụ nơi tạo header bảo mật — thay vì thay cả chính sách.
Những gì được báo#
| Ở đâu | Được báo | Không được báo |
|---|---|---|
| Trình duyệt | Lỗi chưa bắt, promise bị từ chối không xử lý, error boundary của route (defaultOnCatch) | Lỗi code của bạn đã bắt và xử lý; AbortError của request bị huỷ |
| Server function | Lỗi được ném ra, kèm stack phía máy chủ | redirect(), notFound(), lỗi có mã trạng thái dưới 500 (kể cả 499), request người dùng đã huỷ |
| Server route | Lỗi được ném ra, kèm stack phía máy chủ | Response bạn tự trả về, kể cả mã 500 — chỉ lỗi được ném ra mới được thấy; request người dùng đã huỷ |
| Loader của route | Lỗi được ném ra — khi render trên máy chủ thì báo từ máy chủ, kèm stack máy chủ; sau khi chuyển trang trên trình duyệt thì báo từ trình duyệt. Mỗi lỗi một lần | redirect(), notFound() và các lỗi khác như với server function |
Request bị huỷ là khi người dùng đóng tab hoặc chuyển trang: một AbortError, hoặc bất kỳ lỗi nào ném ra sau khi client đã ngắt kết nối. Đây không phải lỗi của app nên từ 0.2.0 chúng được bỏ qua; đặt captureAborted: true để báo lại.
Việc báo lỗi loader cần withBelvadorRouter({ routeTree, ... }) ở bước 2 (từ 0.5.1) và nửa máy chủ ở bước 3. Nếu chưa có nửa máy chủ, lỗi loader khi render trên máy chủ vẫn được báo một lần — từ trình duyệt, không có stack máy chủ. Nếu bạn tự đặt dehydrate/hydrate, hãy truyền chúng bên trong withBelvadorRouter: SDK thêm một mã băm ngắn cho mỗi lỗi máy chủ đã báo (không bao giờ gửi nội dung lỗi), và hydrate của bạn nhận đúng những gì dehydrate của bạn trả về. Các tích hợp bọc chúng sau createRouter, như tích hợp SSR của TanStack Query, vẫn chạy bình thường.
Kiểm tra#
Chạy phép kiểm kết nối cho cả hai nửa — tab Kết nối của project hiện từng nửa chuyển sang Đã xác minh.
Trình duyệt, một lần (ví dụ từ một nút debug):
import { verifyIntegration } from "@belvador/tanstack-start/client";// Run once after setup or deploy (for example behind a debug flag), never on every page load.// The console shows one expected "Belvador verification (uncaught)" error — that is the test.const result = await verifyIntegration();// result.outcome: "verified" | "problem" | "unconfirmed"; the console prints one summary line.Máy chủ — một route tạm phải chạy trong middleware:
import { createFileRoute } from "@tanstack/react-router";import { verifyIntegration } from "@belvador/tanstack-start/server";// Temporary: open /api/verify-belvador once after deploying, then delete this file.// It must run inside belvadorRequestMiddleware (src/start.ts) — that is part of the check.export const Route = createFileRoute("/api/verify-belvador")({ server: { handlers: { GET: async () => Response.json(await verifyIntegration()), }, },});Sau đó đọc lại kết quả, hoặc xem tab Kết nối:
curl -s "https://ingest.belvador.dev/api/<project-id>/verification/bv1_<id printed by your app>/?sentry_key=<public-key>"Xoá code tạm sau đó. Mọi kết quả và mã đều được giải thích trong Kiểm tra kết nối.
Ứng dụng Lovable#
Nhờ agent của Lovable cài giúp: trên trang project trong Belvador, sao chép mục Dán vào AI agent và gửi vào khung chat Lovable. Lời nhắc chứa đúng các bước ở trên kèm DSN của bạn. Hướng dẫn cho AI agent liệt kê những gì agent phải giữ nguyên.
Khắc phục nhanh#
Chỉ thấy lỗi trình duyệt — kiểm tra middleware nằm cuối cả hai danh sách,
src/server.tsbọc đúng entry thật, và đã bậtnodejs_compat.Lỗi về hai lần — gỡ mọi cài đặt
@sentry/*cũ và lời gọiSentry.init.Thêm ở Khắc phục sự cố.
Bài viết về chủ đề này
Tất cả bài viết- Lỗi loader trong TanStack Start: vì sao stack lúc SSR lại chỉ vào trang HTMLVì sao lỗi loader TanStack Start lúc SSR có stack từ trang HTML, vì sao báo từ server có thể bị đếm đôi, và cách để lỗi chỉ được báo một lần.
- Prompt cài theo dõi lỗi cho app Lovable, và cách kiểm tra nó chạyPrompt cài theo dõi lỗi cho app Lovable: sửa file nào, theo thứ tự nào, giữ lại gì, chứng minh nó chạy ra sao. Thứ tự cũ báo 0/12 lỗi, bản sửa 12/12.
- Giám sát ứng dụng vibe coding: thứ gì hỏng sau khi publishApp vibe coding hỏng gì sau khi publish, vì sao uptime check vẫn thấy 200 OK, và ba lớp kiểm tra bắt được lỗi. Thử thật trên năm bản build hỏng.
Thấy chỗ sai hoặc thiếu? Gửi cho chúng tôi: hello@belvador.dev