How It Works
Understand the architecture and key concepts behind Better Stack.
Here's a high-level overview of how Better Stack works:

Server Side
The server handles database operations, API endpoints, data prefetching, routing, and server-side rendering.
betterStack manages the backend layer:
- API Router: Routes incoming requests to the appropriate plugin handlers. Returns a handler function that you mount at your API path.
- DB Adapter: Translates Better Stack's database operations to your ORM (Prisma, Drizzle, Kysely, MongoDB). Plugins define schemas that get merged and passed to the adapter.
stackClient manages the rendering layer:
- Data Fetching: Plugins can prefetch data server-side into React Query cache before rendering, enabling instant page loads with hydrated state.
- Page Router: Matches URLs to plugin routes and returns the appropriate page component, loader, and metadata.
- SSR: Server-side renders pages with prefetched data, then hydrates on the client.
Client Side
After server-side rendering, the client takes over for interactivity.
React Hydration
Server-rendered HTML is hydrated with client-side React. The React Query cache—prefetched during SSR—transfers seamlessly, so components render instantly without loading states or refetching.
SPA Navigation (If using in an SPA)
After the initial page load, stackClient's router handles client-side navigation. Clicking links doesn't trigger full page reloads—React Query fetches data in the background while the UI updates immediately.
State Management
First party plugins use React Query under the hood for all data operations:
- Queries: Hooks like
usePosts(),usePost(slug), anduseTags()(examples from the blog plugin) fetch and cache data with automatic background refetching - Mutations: Hooks like
useCreatePost(),useUpdatePost(), anduseDeletePost()(examples from the blog plugin) handle writes with automatic cache invalidation - Suspense: Suspense variants (
useSuspensePosts,useSuspensePost) integrate with React Suspense boundaries for streaming SSR and other advanced features
Note: 3rd party plugins may use a different state management library.
Context & Overrides
The BetterStackProvider wraps your pages and injects framework-specific components via React Context. Plugin components access these overrides through usePluginOverrides(), allowing them to use your framework's Link, Image, and navigation without tight coupling and to avoid breaking the client/server boundary in frameworks like Next.js.
Plugins
Plugins are the building blocks of Better Stack. Each feature (like Blog) ships as two separate plugins—one for the backend, one for the client—that you register independently:
// Backend: lib/better-stack.ts
import { betterStack } from "@btst/stack"
import { blogBackendPlugin } from "@btst/stack/plugins/blog/api"
const { handler } = betterStack({
plugins: {
blog: blogBackendPlugin({ /* config */ })
},
// ...
})
// Client: lib/better-stack-client.tsx
import { createStackClient } from "@btst/stack/client"
import { blogClientPlugin } from "@btst/stack/plugins/blog/client"
const stackClient = createStackClient({
plugins: {
blog: blogClientPlugin({ /* config */ })
}
})Backend plugins (registered in betterStack):
- Define database schemas (tables, columns, relations)
- Register API route handlers for CRUD operations
- Provide hooks for authorization and custom logic
Client plugins (registered in stackClient):
- Define page routes and components
- Provide loaders for server-side data prefetching
- Export components, hooks, and utilities for state management
- Generate SEO metadata and sitemaps
This separation keeps server-only code (database schemas, API handlers) out of your client bundle, and allows each plugin to be configured independently for its context.
Overrides
Framework-specific components injected into plugins at runtime:
- Link: Use Next.js
Link, React RouterLink, or TanStackLinkfor optimized navigation - Image: Use Next.js
Imagefor automatic optimization - navigate: Programmatic navigation function for your framework
- apiBaseURL/apiBasePath: Configure where your API is mounted