Bỏ qua tới nội dung
Mục lục

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.

Bản Markdown
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-router và @tanstack/react-start từ 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 đặt tanstackStart: { server: { entry: "server" } }. Ứng dụng Lovable đã có sẵn.

1. Cài đặt#

Terminal
npm install @belvador/tanstack-start@0.6.1

2. 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:

src/instrument.ts
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:

src/router.tsx
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.
  • init chỉ 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 init nằm trong thân src/router.tsx, lỗi xảy ra lúc một module route hoặc src/start.ts đang nạp sẽ đến trước init và 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 ở đầu src/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 cho createRouter. Hàm này đặt defaultOnCatch để báo lỗi mà error boundary của route bắt được, rồi gọi defaultOnCatch của bạn nếu có. TanStack Router chỉ gọi defaultOnCatch ở 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 đặt defaultErrorComponent, helper dùng ErrorComponent có sẵn của TanStack. errorComponent riê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ới defaultOnCatch: belvadorOnCatch, và 3/3 lần với helper.

  • Trước 0.5.0, cách cài là defaultOnCatch: belvadorOnCatch cộng một error component; cách đó vẫn chạy và belvadorOnCatch vẫ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 init chạ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#

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ụ:

TypeScript
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#

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#

Text
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áoKhông được báo
Trình duyệtLỗ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 functionLỗ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 routeLỗ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 routeLỗ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ầnredirect(), 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):

TypeScript
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:

src/routes/api/verify-belvador.ts
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:

Terminal
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.ts bọc đúng entry thật, và đã bật nodejs_compat.

  • Lỗi về hai lần — gỡ mọi cài đặt @sentry/* cũ và lời gọi Sentry.init.

  • Thêm ở Khắc phục sự cố.

Bài viết về chủ đề này

Tất cả bài viết

Thấy chỗ sai hoặc thiếu? Gửi cho chúng tôi: hello@belvador.dev