better-push
Integrate your stack

TanStack Start

Mount better-push in a TanStack Start app with a server route.

better-push works with TanStack Start exactly as it does with Next.js: the core is a framework-agnostic (request: Request) => Promise<Response> handler, and the integration is a thin map onto TanStack Start's server routes. Everything else in the docs - the schema, push.notify(), VAPID setup, the client SDK, the React hook - is identical.

Fastest path

npx @better-push/cli init detects TanStack Start and writes the route below for you. See the CLI page.

Mount the endpoints

TanStack Start server routes are web-standard: each method handler receives a { request } and returns a Response. toTanStackStartHandler returns the GET/POST/PUT/PATCH/DELETE map to drop into a splat route. Every method the router serves is exported, so the router itself decides what is allowed on a path and new endpoints never require touching this file.

src/routes/api/push/$.ts
import { createFileRoute } from "@tanstack/react-router";
import { push } from "@/push";
import { toTanStackStartHandler } from "@better-push/core/tanstack";

export const Route = createFileRoute("/api/push/$")({
  server: {
    handlers: toTanStackStartHandler(push),
  },
});

The splat route /api/push/$ catches every better-push endpoint (/devices, /devices/:id, /notifications, /notifications/unread-count, /notifications/:id/read, /notifications/read-all, and /preferences). No @tanstack/* types leak into the library - the integration is dependency-free and typed structurally.

Everything else is the same

The push.ts config, the bp_* Drizzle schema, push.notify(...), the public/sw.js service worker, and the browser client are framework-agnostic. Follow the Quickstart for those steps; only the route file above differs from the Next.js setup.

// Configure once (identical to the Next.js setup)
import { betterPush } from "@better-push/core";
import { drizzleAdapter } from "@better-push/core/adapters/drizzle";
import { webPush } from "@better-push/core/providers/web-push";
import { db } from "@/db";

export const push = betterPush({
  database: drizzleAdapter(db),
  providers: [webPush({ vapid: { /* ... */ } })],
  session: async (request) => {
    // Plug in TanStack Start's auth / session here.
    return null;
  },
});

Client

The browser SDK and the usePushRegistration React hook from @better-push/core/react are the same in a TanStack Start app - they only depend on browser APIs and your endpoints, not on the server framework. Pass your VAPID_PUBLIC_KEY as applicationServerKey and serve public/sw.js from your origin.

On this page