From d2fe0463ebf3c4235b0c0a33b7e598c4ae09ff12 Mon Sep 17 00:00:00 2001 From: Jonathan Samines Date: Thu, 5 Mar 2026 20:16:10 -0600 Subject: [PATCH] docs: add background tasks (#8395) --- docs/content/docs/concepts/hooks.mdx | 46 +++++++++++++++++ .../guides/optimizing-for-performance.mdx | 25 ++++++++++ docs/content/docs/reference/options.mdx | 49 +++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/docs/content/docs/concepts/hooks.mdx b/docs/content/docs/concepts/hooks.mdx index bcc96c132c..c107f35d7a 100644 --- a/docs/content/docs/concepts/hooks.mdx +++ b/docs/content/docs/concepts/hooks.mdx @@ -250,6 +250,52 @@ This may be useful to use instead of using your db directly to get access to `da You can use `ctx.context.generateId` to generate Id for various reasons. +#### runInBackground + +Schedules a task to run after the response is sent. Use for fire-and-forget operations (cleanup, analytics, rate limit counter updates). Configure the handler in [advanced.backgroundTasks](/docs/reference/options#backgroundtasks). + +```ts title="auth.ts" +import { betterAuth } from "better-auth"; +import { createAuthMiddleware } from "better-auth/api"; + +export const auth = betterAuth({ + hooks: { + after: createAuthMiddleware(async (ctx) => { + if (ctx.path.startsWith("/sign-up")) { + const newSession = ctx.context.newSession; + if (newSession) { + ctx.context.runInBackground(sendAnalyticsEvent(newSession.user.id)); + } + } + }), + }, +}); +``` + +#### runInBackgroundOrAwait + +Defers the task when a handler is configured, otherwise awaits it. Use for operations that must complete (e.g. sending emails) but benefit from not blocking when a handler exists. Configure the handler in [advanced.backgroundTasks](/docs/reference/options#backgroundtasks). + +```ts title="auth.ts" +import { betterAuth } from "better-auth"; +import { createAuthMiddleware } from "better-auth/api"; + +export const auth = betterAuth({ + hooks: { + after: createAuthMiddleware(async (ctx) => { + if (ctx.path.startsWith("/sign-up")) { + const newSession = ctx.context.newSession; + if (newSession) { + await ctx.context.runInBackgroundOrAwait( + sendWelcomeEmail(newSession.user) + ); + } + } + }), + }, +}); +``` + ## Reusable Hooks If you need to reuse a hook across multiple endpoints, consider creating a plugin. Learn more in the [Plugins Documentation](/docs/concepts/plugins). diff --git a/docs/content/docs/guides/optimizing-for-performance.mdx b/docs/content/docs/guides/optimizing-for-performance.mdx index 553b7dcf7b..f2bb486b55 100644 --- a/docs/content/docs/guides/optimizing-for-performance.mdx +++ b/docs/content/docs/guides/optimizing-for-performance.mdx @@ -121,6 +121,31 @@ Here are examples of how you can do caching in different frameworks and environm +## Background Tasks + +On serverless platforms (Vercel, Cloudflare Workers), you can improve response times by deferring non-critical work—cleanup, analytics, rate limit updates, email sending—to run after the response is sent. Configure [`advanced.backgroundTasks`](/docs/reference/options#backgroundtasks) and use `ctx.context.runInBackground` or `ctx.context.runInBackgroundOrAwait` in hooks. + +```ts title="auth.ts" +import { betterAuth } from "better-auth"; +import { createAuthMiddleware } from "better-auth/api"; +import { waitUntil } from "@vercel/functions"; + +export const auth = betterAuth({ + advanced: { + backgroundTasks: { handler: waitUntil }, + }, + hooks: { + after: createAuthMiddleware(async (ctx) => { + if (ctx.path === "/sign-up/email") { + ctx.context.runInBackground(logSignUp(ctx.context.newSession?.user.id)); + } + }), + }, +}); +``` + +See the [Context documentation](/docs/concepts/hooks#runinbackground) for more examples. + ## SSR Optimizations If you're using a framework that supports server-side rendering, it's usually best to pre-fetch the user session on the server and use it as a fallback on the client. diff --git a/docs/content/docs/reference/options.mdx b/docs/content/docs/reference/options.mdx index 69e1663c78..3bf63fa2bd 100644 --- a/docs/content/docs/reference/options.mdx +++ b/docs/content/docs/reference/options.mdx @@ -585,6 +585,9 @@ export const auth = betterAuth({ defaultFindManyLimit: 100, experimentalJoins: false, }, + backgroundTasks: { + handler: (promise) => { /* e.g. waitUntil(promise) */ } + }, skipTrailingSlashes: true }, }) @@ -599,9 +602,55 @@ export const auth = betterAuth({ - `defaultCookieAttributes`: Default attributes for all cookies - `cookiePrefix`: Prefix for cookies - `database`: Database configuration options +- `backgroundTasks`: Configure background task handling for deferred operations - `skipTrailingSlashes`: Skip trailing slash validation in route matching. (default: `false`) - OAuth state configuration options (`storeStateStrategy`, `skipStateCookieCheck`) are now part of the `account` option +### `backgroundTasks` + +Configure background task handling for deferred operations. Background tasks allow non-critical operations (like cleanup, analytics, timing-attack mitigation, or rate limit counter updates) to run after the response is sent. This can significantly improve response times on serverless platforms. + +Some examples are: `waitUntil` from `@vercel/functions` on Vercel, or `ctx.waitUntil` on Cloudflare Workers. See the examples below for more details. + + + Enabling this introduces eventual consistency where the response returns optimistic data before the database is updated. Only enable if your application can tolerate this trade-off for improved latency. + + + + + ```ts + import { betterAuth } from "better-auth"; + import { waitUntil } from "@vercel/functions"; + + export const auth = betterAuth({ + advanced: { + backgroundTasks: { + handler: waitUntil, + }, + }, + }); + ``` + + + ```ts + import { betterAuth } from "better-auth"; + import { AsyncLocalStorage } from "node:async_hooks"; + + const execCtxStorage = new AsyncLocalStorage(); + + export const auth = betterAuth({ + advanced: { + backgroundTasks: { + handler: (p) => execCtxStorage.getStore()?.waitUntil(p), + }, + }, + }); + + // In your request handler, wrap with execCtxStorage.run(ctx, ...) + ``` + + + ## `logger` Logger configuration for Better Auth.