### React Router Dynamic Page Handling with Loaders Source: https://www.better-stack.ai/docs/installation This React Router example (`app/routes/pages/index.tsx`) demonstrates dynamic page handling using loaders for data prefetching and meta generation. It creates a `QueryClient` for each request and utilizes `dehydrate` for client-side hydration. Key dependencies include `@tanstack/react-router`, `@tanstack/react-query`, and `@btst/stack/client`. ```typescript import type { Route } from "./+types/index" import { useLoaderData } from "react-router" import { dehydrate, HydrationBoundary, QueryClient, useQueryClient } from "@tanstack/react-query" import { getStackClient } from "~/lib/better-stack-client" import { normalizePath } from "@btst/stack/client" export async function loader({ params }: Route.LoaderArgs) { const path = normalizePath(params["*"]) // Create QueryClient for this request with consistent config const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, refetchOnMount: false, retry: false } } }) const stackClient = getStackClient(queryClient) const route = stackClient.router.getRoute(path) if (route?.loader) await route.loader() // Include errors so client doesn't refetch on error const dehydratedState = dehydrate(queryClient) return { path, dehydratedState, meta: route?.meta?.() } } export function meta({ loaderData }: Route.MetaArgs) { return loaderData.meta } export default function PagesIndex() { const { path, dehydratedState } = useLoaderData() const queryClient = useQueryClient() const route = getStackClient(queryClient).router.getRoute(path) const Page = route && route.PageComponent ? :
Route not found
return dehydratedState ? ( {Page} ) : Page } ``` -------------------------------- ### Configure QueryClientProvider in App (Next.js App Router) Source: https://www.better-stack.ai/docs/installation This example demonstrates how to configure the `QueryClientProvider` in a Next.js application using the App Router. It imports the `getOrCreateQueryClient` utility and passes the created query client to the provider, wrapping the application's children. ```jsx import { QueryClientProvider } from "@tanstack/react-query" import { getOrCreateQueryClient } from "@/lib/query-client" export default function RootLayout({ children }) { const queryClient = getOrCreateQueryClient() return ( {children} ) } ``` -------------------------------- ### Configure QueryClientProvider in App (React Router) Source: https://www.better-stack.ai/docs/installation This example shows how to set up the `QueryClientProvider` within a React application using React Router. It utilizes the `getOrCreateQueryClient` utility and integrates it with the root component, wrapping the `Outlet` which renders the current route's component. ```jsx import { QueryClientProvider } from "@tanstack/react-query" import { getOrCreateQueryClient } from "~/lib/query-client" import { Outlet } from "react-router" export default function App() { const queryClient = getOrCreateQueryClient() return ( ) } ``` -------------------------------- ### Client Configuration Example Source: https://www.better-stack.ai/docs/plugins/blog Example of configuring the blog client plugin. This includes setting up required API and site base URLs, a query client, and optional SEO and hook configurations. ```typescript blog: blogClientPlugin({ // Required configuration apiBaseURL: baseURL, apiBasePath: "/api/data", siteBaseURL: baseURL, siteBasePath: "/pages", queryClient: queryClient, // Optional SEO configuration seo: { siteName: "My Awesome Blog", author: "John Doe", twitterHandle: "@johndoe", locale: "en_US", defaultImage: `${baseURL}/og-image.png`, }, }) ``` -------------------------------- ### Configure Prisma Backend Instance Source: https://www.better-stack.ai/docs/installation Sets up a Better Stack backend instance using Prisma as the ORM. It initializes PrismaClient and creates a Prisma adapter for database operations. Ensure you have '@btst/adapter-prisma' and '@prisma/client' installed. ```typescript import { betterStack } from "@btst/stack" import { createPrismaAdapter } from "@btst/adapter-prisma" import { PrismaClient } from "@prisma/client" const prisma = new PrismaClient() const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { // Add your backend plugins here }, adapter: (db) => createPrismaAdapter(prisma, db, { provider: "postgresql" // or "mysql", "sqlite", "cockroachdb", "mongodb" }) }) export { handler, dbSchema } ``` -------------------------------- ### Backend Authorization Hooks Example Source: https://www.better-stack.ai/docs/plugins/blog Example of implementing backend authorization hooks for the blog plugin. This allows customization of access control for API endpoints like listing, creating, updating, and deleting posts. ```typescript import { blogBackendPlugin, type BlogBackendHooks } from "@btst/stack/plugins/blog/api" const blogHooks: BlogBackendHooks = { // Authorization hooks - return false to deny access onBeforeListPosts(filter, context) { if(filter.published === false) { return isBlogAdmin(context.headers as Headers) } return true }, onBeforeCreatePost(data, context) { return isBlogAdmin(context.headers as Headers) }, onBeforeUpdatePost(postId, data, context) { return isBlogAdmin(context.headers as Headers) }, onBeforeDeletePost(postId, context) { return isBlogAdmin(context.headers as Headers) }, // ... other hooks } const { handler, dbSchema } = betterStack({ plugins: { blog: blogBackendPlugin(blogHooks) }, // ... }) ``` -------------------------------- ### Client Blog Plugin Installation and Configuration (TypeScript/React) Source: https://www.better-stack.ai/docs/plugins/blog Registers the blog client plugin for Better Stack applications. This setup involves creating a client instance using `createStackClient` and configuring the blog plugin with essential API and site URLs, a React Query client, and optional SEO settings. The configuration is crucial for server-side data prefetching. ```typescript import { createStackClient } from "@btst/stack/client" import { blogClientPlugin } from "@btst/stack/plugins/blog/client" import { QueryClient } from "@tanstack/react-query" const getBaseURL = () => (process.env.BASE_URL || "http://localhost:3000") export const getStackClient = (queryClient: QueryClient) => { const baseURL = getBaseURL() return createStackClient({ plugins: { blog: blogClientPlugin({ // Required configuration apiBaseURL: baseURL, apiBasePath: "/api/data", siteBaseURL: baseURL, siteBasePath: "/pages", queryClient: queryClient, // Optional: SEO configuration seo: { siteName: "My Blog", author: "Your Name", twitterHandle: "@yourhandle", locale: "en_US", defaultImage: `${baseURL}/og-image.png`, }, }) } }) } ``` -------------------------------- ### Create Catch-all API Route for Better Stack Requests Source: https://www.better-stack.ai/docs/installation Handles Better Stack requests for paths like `/api/data/*`. Ensure the `basePath` in the `betterStack` config matches your chosen route path. Supports GET, POST, PUT, and DELETE methods. ```typescript import { handler } from "@/lib/better-stack" export const GET = handler export const POST = handler export const PUT = handler export const DELETE = handler ``` ```typescript import { handler } from "~/lib/better-stack" import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node" export async function loader({ request }: LoaderFunctionArgs) { return handler(request) } export async function action({ request }: ActionFunctionArgs) { return handler(request) } ``` ```typescript import { createFileRoute } from '@tanstack/react-router' import { handler } from '@/lib/better-stack' export const Route = createFileRoute('/api/data/$')({ server: { handlers: { GET: async ({ request }) => { return handler(request) }, POST: async ({ request }) => { return handler(request) }, PUT: async ({ request }) => { return handler(request) }, DELETE: async ({ request }) => { return handler(request) }, }, }, }) ``` -------------------------------- ### Install Better Stack Core Package Source: https://www.better-stack.ai/docs/installation Installs the core Better Stack package and the @tanstack/react-query library, which is essential for data fetching. Ensure you have Node.js and a package manager (npm, pnpm, or yarn) installed. ```bash npm install @btst/stack @tanstack/react-query ``` ```bash pnpm add @btst/stack @tanstack/react-query ``` ```bash yarn add @btst/stack @tanstack/react-query ``` -------------------------------- ### Configure MongoDB Backend Instance Source: https://www.better-stack.ai/docs/installation Integrates Better Stack with MongoDB using the official MongoDB driver. It initializes a MongoClient and creates a MongoDB adapter. Ensure '@btst/adapter-mongodb' and 'mongodb' are installed. ```typescript import { betterStack } from "@btst/stack" import { createMongodbAdapter } from "@btst/adapter-mongodb" import { MongoClient } from "mongodb" const client = new MongoClient(process.env.MONGODB_URI!) const mongoDb = client.db() const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { // Add your backend plugins here // blog: blogBackendPlugin() }, adapter: (db) => createMongodbAdapter(mongoDb, db, {}) }) export { handler, dbSchema } ``` -------------------------------- ### Set Up Sitemap Generation - TanStack Router Source: https://www.better-stack.ai/docs/installation Implements sitemap generation for TanStack Router, creating a dedicated route for 'sitemap.xml'. It defines a `GET` handler that utilizes the Better Stack client to generate sitemap entries and returns them as an XML response with appropriate caching headers. ```typescript // Note: [.] syntax in TanStack Router creates a route for "sitemap.xml" import { createFileRoute } from "@tanstack/react-router" import { QueryClient } from "@tanstack/react-query" import { getStackClient } from "@/lib/better-stack-client" import { sitemapEntryToXmlString } from "@btst/stack/client" export const Route = createFileRoute("/sitemap.xml")({ server: { handlers: { GET: async () => { const queryClient = new QueryClient() const stackClient = getStackClient(queryClient) const entries = await stackClient.generateSitemap() const xml = sitemapEntryToXmlString(entries) return new Response(xml, { headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400", }, }) }, }, }, }) ``` -------------------------------- ### Configure Memory Backend Instance (Development) Source: https://www.better-stack.ai/docs/installation Utilizes the memory adapter for Better Stack, suitable for development and testing purposes. It does not require external database connections. Import '@btst/adapter-memory'. ```typescript // IMPORTANT: Memory adapter is used for development and testing only import { betterStack } from "@btst/stack" import { createMemoryAdapter } from "@btst/adapter-memory" const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { // Add your backend plugins here }, adapter: (db) => createMemoryAdapter(db)({}) }) export { handler, dbSchema } ``` -------------------------------- ### Configure Kysely Backend Instance Source: https://www.better-stack.ai/docs/installation Sets up a Better Stack backend instance using Kysely ORM with PostgreSQL dialect. It initializes Kysely with a `pg` pool and creates a Kysely adapter. Requires '@btst/adapter-kysely', 'kysely', and 'pg' packages. ```typescript import { betterStack } from "@btst/stack" import { createKyselyAdapter } from "@btst/adapter-kysely" import { Kysely, PostgresDialect } from "kysely" import { Pool } from "pg" const kyselyDb = new Kysely({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL }) }) }) const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { // Add your backend plugins here }, adapter: (db) => createKyselyAdapter(kyselyDb, db, {}) }) export { handler, dbSchema } ``` -------------------------------- ### Install Kysely Database Adapter for Better Stack Source: https://www.better-stack.ai/docs/installation Installs the Kysely adapter for Better Stack, facilitating integration with the Kysely query builder. This package is required for Better Stack to interact with your database when using Kysely. Available for npm, pnpm, and yarn. ```bash npm install @btst/adapter-kysely ``` ```bash pnpm add @btst/adapter-kysely ``` ```bash yarn add @btst/adapter-kysely ``` -------------------------------- ### TanStack Router Layout with BetterStackProvider Source: https://www.better-stack.ai/docs/installation Implements the BetterStackProvider in a TanStack Router setup (`route.tsx`), integrating it with `@tanstack/react-query`. This example demonstrates how to provide custom navigation and Link components tailored for TanStack Router, ensuring type-safe overrides for plugins. It leverages `createFileRoute` for defining the layout component. ```tsx import { BetterStackProvider } from "@btst/stack/context" import { QueryClientProvider } from "@tanstack/react-query" import type { ExamplePluginOverrides } from "@btst/stack/plugins/example/client" import { Link, useRouter, Outlet, createFileRoute } from "@tanstack/react-router" // Define the shape of all plugin overrides type PluginOverrides = { example: ExamplePluginOverrides // Add other plugins here } export const Route = createFileRoute('/pages')({ component: Layout }) function Layout() { const router = useRouter() const context = Route.useRouteContext() return ( basePath="/pages" overrides={{ example: { navigate: (href) => router.navigate({ href }), Link: ({ href, children, className, ...props }) => ( {children} ) // Add other plugin overrides here } // Add other plugins here }} > ) } ``` -------------------------------- ### Install In-Memory Database Adapter for Better Stack Source: https://www.better-stack.ai/docs/installation Installs the in-memory adapter for Better Stack, suitable for development and testing environments. This adapter allows Better Stack to operate without a persistent database. Available for npm, pnpm, and yarn. ```bash npm install @btst/adapter-memory ``` ```bash pnpm add @btst/adapter-memory ``` ```bash yarn add @btst/adapter-memory ``` -------------------------------- ### Create Better Stack Client Instance Source: https://www.better-stack.ai/docs/installation Initializes a client instance for Better Stack using `createStackClient`. This client handles routing to plugin pages, prefetching data on the server, and client-side hydration. It requires a `QueryClient` instance, which can vary between server and client contexts. ```typescript import { createStackClient } from "@btst/stack/client" import { QueryClient } from "@tanstack/react-query" export const getStackClient = (queryClient: QueryClient) => { return createStackClient({ plugins: { // Add your client plugins here } }) } ``` -------------------------------- ### Install MongoDB Database Adapter for Better Stack Source: https://www.better-stack.ai/docs/installation Installs the MongoDB adapter for Better Stack, enabling integration with MongoDB databases. This package is needed for Better Stack to connect to and manage data in MongoDB. Supports npm, pnpm, and yarn. ```bash npm install @btst/adapter-mongodb ``` ```bash pnpm add @btst/adapter-mongodb ``` ```bash yarn add @btst/adapter-mongodb ``` -------------------------------- ### Install Prisma Database Adapter for Better Stack Source: https://www.better-stack.ai/docs/installation Installs the Prisma adapter for Better Stack, enabling integration with Prisma ORM. This package is required for Better Stack to interact with your database using Prisma. Available for npm, pnpm, and yarn. ```bash npm install @btst/adapter-prisma ``` ```bash pnpm add @btst/adapter-prisma ``` ```bash yarn add @btst/adapter-prisma ``` -------------------------------- ### Setup TanStack Router with SSR Query Integration (TypeScript) Source: https://www.better-stack.ai/docs/installation This TypeScript code sets up a TanStack Router, integrating it with SSR query functionality. It retrieves a `QueryClient` using `getOrCreateQueryClient` and configures the router with context and scroll restoration. The `setupRouterSsrQueryIntegration` function is crucial for bridging the router and query client for SSR. ```typescript import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' import { QueryClient } from '@tanstack/react-query' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' import { getOrCreateQueryClient } from '@/lib/query-client' export interface MyRouterContext { queryClient: QueryClient } export function getRouter() { const queryClient = getOrCreateQueryClient() const router = createRouter({ routeTree, scrollRestoration: true, defaultPreload: false, context: { queryClient, }, notFoundMode: "root", }) setupRouterSsrQueryIntegration({ router, queryClient, }) return router } declare module '@tanstack/react-router' { interface Register { router: ReturnType } } ``` -------------------------------- ### Next.js Catch-All Route Handler for Better Stack Pages Source: https://www.better-stack.ai/docs/installation This Next.js code sets up a catch-all route (`[[...all]]/page.tsx`) to dynamically handle Better Stack pages. It pre-fetches data using React Query and generates metadata server-side, ensuring proper hydration on the client. Dependencies include `@tanstack/react-query` and `@btst/stack/client`. ```typescript import { dehydrate, HydrationBoundary } from "@tanstack/react-query" import { notFound } from "next/navigation" import { getOrCreateQueryClient } from "@/lib/query-client" import { getStackClient } from "@/lib/better-stack-client" import { metaElementsToObject, normalizePath } from "@btst/stack/client" import { Metadata } from "next" export default async function Page({ params }: { params: Promise<{ all: string[] }> }) { const pathParams = await params const path = normalizePath(pathParams?.all) const queryClient = getOrCreateQueryClient() const stackClient = getStackClient(queryClient) const route = stackClient.router.getRoute(path) // Prefetch data server-side if the route has a loader if (route?.loader) await route.loader() // Serialize React Query cache for client hydration const dehydratedState = dehydrate(queryClient) return ( {route && route.PageComponent ? : notFound()} ) } export async function generateMetadata({ params }: { params: Promise<{ all: string[] }> }) { const pathParams = await params const path = normalizePath(pathParams?.all) const queryClient = getOrCreateQueryClient() const stackClient = getStackClient(queryClient) const route = stackClient.router.getRoute(path) if (!route) return notFound() if (route?.loader) await route.loader() // Convert plugin meta elements to Next.js Metadata format return route.meta ? metaElementsToObject(route.meta()) satisfies Metadata : { title: "No meta" } } ``` -------------------------------- ### Backend Blog Plugin Installation (TypeScript) Source: https://www.better-stack.ai/docs/plugins/blog Installs and registers the blog backend plugin within the Better Stack framework. It involves importing the plugin and including it in the main `betterStack` configuration. The `blogBackendPlugin()` function can accept optional hooks for customization. ```typescript import { betterStack } from "@btst/stack" import { blogBackendPlugin } from "@btst/stack/plugins/blog/api" // ... your adapter imports const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { blog: blogBackendPlugin() }, adapter: (db) => createPrismaAdapter(prisma, db, { provider: "postgresql" }) }) export { handler, dbSchema } ``` -------------------------------- ### Install Drizzle Database Adapter for Better Stack Source: https://www.better-stack.ai/docs/installation Installs the Drizzle adapter for Better Stack, allowing integration with the Drizzle ORM. This package is necessary for Better Stack to communicate with your database when using Drizzle. Supports npm, pnpm, and yarn. ```bash npm install @btst/adapter-drizzle ``` ```bash pnpm add @btst/adapter-drizzle ``` ```bash yarn add @btst/adapter-drizzle ``` -------------------------------- ### Configure Drizzle Backend Instance Source: https://www.better-stack.ai/docs/installation Configures a Better Stack backend instance with Drizzle ORM for PostgreSQL. It initializes Drizzle with a `postgres-js` client and creates a Drizzle adapter. Dependencies include '@btst/adapter-drizzle', 'drizzle-orm/postgres-js', and 'postgres'. ```typescript import { betterStack } from "@btst/stack" import { createDrizzleAdapter } from "@btst/adapter-drizzle" import { drizzle } from "drizzle-orm/postgres-js" // or "drizzle-orm/mysql2", "drizzle-orm/better-sqlite3", etc. import postgres from "postgres" const client = postgres(process.env.DATABASE_URL!) const drizzleDb = drizzle(client) const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { // Add your backend plugins here }, adapter: (db) => createDrizzleAdapter(drizzleDb, db, {}) }) export { handler, dbSchema } ``` -------------------------------- ### Client Plugin Configuration Source: https://www.better-stack.ai/docs/plugins/blog Configuration options for the blog client plugin, including API URLs and optional SEO settings. ```APIDOC ## Client Plugin (`@btst/stack/plugins/blog/client`) ### Description The client plugin accepts a configuration object with required fields and optional SEO settings. ### Configuration Options - **apiBaseURL** (string) - Required - The base URL for the API. - **apiBasePath** (string) - Required - The base path for the blog API endpoints. - **siteBaseURL** (string) - Required - The base URL for the site. - **siteBasePath** (string) - Required - The base path for the blog pages. - **queryClient** (object) - Required - The query client instance (e.g., from React Query). - **seo?** (object) - Optional - SEO settings for the blog. - **siteName** (string) - The name of the site. - **author** (string) - The author's name. - **twitterHandle** (string) - The Twitter handle. - **locale** (string) - The locale (e.g., "en_US"). - **defaultImage** (string) - Default image URL for SEO. - **hooks?** (object) - Optional - Client-side hooks. - **headers?** (object) - Optional - Custom headers for API requests. ### Example Usage ```typescript blog: blogClientPlugin({ // Required configuration apiBaseURL: baseURL, apiBasePath: "/api/data", siteBaseURL: baseURL, siteBasePath: "/pages", queryClient: queryClient, // Optional SEO configuration seo: { siteName: "My Awesome Blog", author: "John Doe", twitterHandle: "@johndoe", locale: "en_US", defaultImage: `${baseURL}/og-image.png`, }, }) ``` ``` -------------------------------- ### Install Better Stack Core and Prisma Adapter Source: https://www.better-stack.ai/docs/databases/adapters Installs the core Better Stack package and the Prisma adapter. Ensure you have npm or yarn installed. ```bash npm install @btst/stack npm install @btst/adapter-prisma ``` -------------------------------- ### Install Better Stack CLI Source: https://www.better-stack.ai/docs/cli Installs the Better Stack CLI as a development dependency using npm, pnpm, or yarn. This is the first step before using the CLI for schema generation or migration. ```bash npm install -D @btst/cli ``` ```bash pnpm add -D @btst/cli ``` ```bash yarn add -D @btst/cli ``` -------------------------------- ### Set Up Sitemap Generation - React Router Source: https://www.better-stack.ai/docs/installation Sets up a sitemap route for automatic sitemap generation in a React Router application. It defines a `loader` function that fetches sitemap entries using the Better Stack client and formats them as an XML response. ```typescript import type { Route } from "./+types/sitemap.xml" import { QueryClient } from "@tanstack/react-query" import { getStackClient } from "~/lib/better-stack-client" import { sitemapEntryToXmlString } from "@btst/stack/client" export async function loader({}: Route.LoaderArgs) { const queryClient = new QueryClient() const stackClient = getStackClient(queryClient) const entries = await stackClient.generateSitemap() const xml = sitemapEntryToXmlString(entries) return new Response(xml, { headers: { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "public, max-age=0, s-maxage=3600, stale-while-revalidate=86400", }, }) } ``` -------------------------------- ### Configure Better Stack with Prisma Adapter Source: https://www.better-stack.ai/docs/databases/adapters Sets up Better Stack using the Prisma adapter. This example initializes PrismaClient and configures the adapter to work with PostgreSQL, MySQL, SQLite, CockroachDB, or MongoDB. ```typescript import { betterStack } from "@btst/stack" import { createPrismaAdapter } from "@btst/adapter-prisma" import { PrismaClient } from "@prisma/client" const prisma = new PrismaClient() const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { // Your plugins here }, // The adapter receives the merged db schema from all plugins adapter: (db) => createPrismaAdapter(prisma, db, { provider: "postgresql" // or "mysql", "sqlite", "cockroachdb", "mongodb" }) }) export { handler, dbSchema } ``` -------------------------------- ### Set Up Sitemap Generation - Next.js Source: https://www.better-stack.ai/docs/installation Configures automatic sitemap generation for SEO in a Next.js application. It uses the `generateSitemap()` method from the Better Stack client to collect URLs from registered plugins and returns them in the required `MetadataRoute.Sitemap` format. ```typescript import type { MetadataRoute } from "next" import { QueryClient } from "@tanstack/react-query" import { getStackClient } from "@/lib/better-stack-client" export const dynamic = "force-dynamic" export default async function sitemap(): Promise { const queryClient = new QueryClient() const stackClient = getStackClient(queryClient) return stackClient.generateSitemap() } ``` -------------------------------- ### Tags API Endpoint Source: https://www.better-stack.ai/docs/plugins/blog Endpoint for listing all available tags. ```APIDOC ## GET /tags ### Description Lists all available tags. ### Method GET ### Endpoint /tags ### Response #### Success Response (200) - **tags** (array) - An array of tag strings. #### Response Example ```json { "tags": ["tech", "blog", "development"] } ``` ``` -------------------------------- ### Create Query Client Utility (TypeScript) Source: https://www.better-stack.ai/docs/installation This utility function, `getOrCreateQueryClient`, creates and manages a `QueryClient` instance. It ensures a new client is created on the server for each request and a singleton client is used on the browser to prevent recreation during React Suspense. It also configures default options for queries and dehydration to optimize SSR hydration. ```typescript import { QueryClient, isServer } from "@tanstack/react-query" import { cache } from "react" function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: isServer ? 60 * 1000 : 0, refetchOnMount: false, refetchOnWindowFocus: false, retry: false }, dehydrate: { // Include both successful and error states to avoid refetching on the client // This prevents loading states when there's an error in prefetched data shouldDehydrateQuery: (query) => { return true } } } }) } let browserQueryClient: QueryClient | undefined = undefined export function getOrCreateQueryClient() { if (isServer) { // Server: always make a new query client return makeQueryClient(); } else { // Browser: make a new query client if we don't already have one // This is very important, so we don't re-make a new client if React // suspends during the initial render. This may not be needed if we // have a suspense boundary BELOW the creation of the query client if (!browserQueryClient) browserQueryClient = makeQueryClient(); return browserQueryClient; } } ``` -------------------------------- ### Backend Plugin Hooks Source: https://www.better-stack.ai/docs/plugins/blog Lifecycle hooks for customizing backend behavior, including authorization and error handling. ```APIDOC ## Backend Hooks (`@btst/stack/plugins/blog/api`) ### Description Customize backend behavior with optional lifecycle hooks. All hooks are optional and allow you to add authorization, logging, and custom behavior. ### Available Hooks - **onBeforeListPosts?** (function) - Hook before listing posts. - **onBeforeCreatePost?** (function) - Hook before creating a post. - **onBeforeUpdatePost?** (function) - Hook before updating a post. - **onBeforeDeletePost?** (function) - Hook before deleting a post. - **onPostsRead?** (function) - Hook after posts are read. - **onPostCreated?** (function) - Hook after a post is created. - **onPostUpdated?** (function) - Hook after a post is updated. - **onPostDeleted?** (function) - Hook after a post is deleted. - **onListPostsError?** (function) - Hook on list posts error. - **onCreatePostError?** (function) - Hook on create post error. - **onUpdatePostError?** (function) - Hook on update post error. - **onDeletePostError?** (function) - Hook on delete post error. ### Example Usage ```typescript import { blogBackendPlugin, type BlogBackendHooks } from "@btst/stack/plugins/blog/api" const blogHooks: BlogBackendHooks = { // Authorization hooks - return false to deny access onBeforeListPosts(filter, context) { if(filter.published === false) { return isBlogAdmin(context.headers as Headers) } return true }, onBeforeCreatePost(data, context) { return isBlogAdmin(context.headers as Headers) }, // ... other hooks } const { handler, dbSchema } = betterStack({ plugins: { blog: blogBackendPlugin(blogHooks) }, // ... }) ``` ### BlogApiContext - **body?** (TBody) - Request body. - **params?** (TParams) - Path parameters. - **query?** (TQuery) - Query parameters. - **request?** (object) - The original request object. - **headers?** (object) - Request headers. ``` -------------------------------- ### Generate Database Schema CLI Source: https://www.better-stack.ai/docs/plugins/blog Generates the database schema for the blog plugin using the Better Stack CLI. This command requires specifying the ORM, configuration file, and output schema file. ```bash npx @btst/cli generate --orm prisma --config lib/better-stack.ts --output prisma/schema.prisma ``` -------------------------------- ### TanStack Router Dynamic Route with SSR and Meta Handling Source: https://www.better-stack.ai/docs/installation This TanStack Router configuration (`src/routes/pages/$.tsx`) defines a dynamic route (`/$`) that supports Server-Side Rendering (SSR) and meta tag generation. It uses `createFileRoute` to manage route logic, including a loader for data fetching and a `head` function for SEO metadata. Dependencies include `@tanstack/react-router` and `@btst/stack/client`. ```typescript import { createFileRoute, notFound } from "@tanstack/react-router" import { getStackClient } from "@/lib/better-stack-client" import { normalizePath } from "@btst/stack/client" export const Route = createFileRoute("/pages/$")({ ssr: true, component: Page, loader: async ({ params, context }) => { const routePath = normalizePath(params._splat) const stackClient = getStackClient(context.queryClient) const route = stackClient.router.getRoute(routePath) if (!route) throw notFound() if (route?.loader) await route.loader() return { meta: await route?.meta?.() } }, head: ({ loaderData }) => { return loaderData?.meta && Array.isArray(loaderData.meta) ? { meta: loaderData.meta } : { meta: [{ title: "No Meta" }], title: "No Meta" } }, notFoundComponent: () =>

This page doesn't exist!

}) function Page() { const context = Route.useRouteContext() const { _splat } = Route.useParams() const routePath = normalizePath(_splat) const route = getStackClient(context.queryClient).router.getRoute(routePath) return route && route.PageComponent ? :
Route not found
} ``` -------------------------------- ### Blog Posts API Endpoints Source: https://www.better-stack.ai/docs/plugins/blog Endpoints for listing, creating, updating, and deleting blog posts. ```APIDOC ## GET /posts ### Description Lists blog posts with optional filtering by published status, tag, or search query. ### Method GET ### Endpoint /posts ### Query Parameters - **published** (boolean) - Optional - Filter by published status. - **tag** (string) - Optional - Filter by tag. - **search** (string) - Optional - Search query for post content. ### Response #### Success Response (200) - **posts** (array) - An array of post objects. #### Response Example ```json { "posts": [ { "id": "1", "title": "First Post", "content": "...", "published": true, "tags": ["tech", "blog"], "createdAt": "2023-01-01T12:00:00Z" } ] } ``` ``` ```APIDOC ## POST /posts ### Description Creates a new blog post. ### Method POST ### Endpoint /posts ### Request Body - **title** (string) - Required - The title of the post. - **content** (string) - Required - The content of the post. - **published** (boolean) - Optional - Whether the post is published. - **tags** (array of strings) - Optional - Tags for the post. ### Request Example ```json { "title": "My New Post", "content": "This is the content of my new post.", "published": true, "tags": ["new", "featured"] } ``` ### Response #### Success Response (200) - **post** (object) - The created post object. #### Response Example ```json { "post": { "id": "2", "title": "My New Post", "content": "This is the content of my new post.", "published": true, "tags": ["new", "featured"], "createdAt": "2023-10-27T10:00:00Z" } } ``` ``` ```APIDOC ## PUT /posts/:id ### Description Updates an existing blog post. ### Method PUT ### Endpoint /posts/:id ### Path Parameters - **id** (string) - Required - The ID of the post to update. ### Request Body - **title** (string) - Optional - The updated title of the post. - **content** (string) - Optional - The updated content of the post. - **published** (boolean) - Optional - The updated published status. - **tags** (array of strings) - Optional - The updated tags. ### Request Example ```json { "content": "Updated content for the post.", "published": false } ``` ### Response #### Success Response (200) - **post** (object) - The updated post object. #### Response Example ```json { "post": { "id": "1", "title": "First Post", "content": "Updated content for the post.", "published": false, "tags": ["tech"], "createdAt": "2023-01-01T12:00:00Z" } } ``` ``` ```APIDOC ## DELETE /posts/:id ### Description Deletes a blog post. ### Method DELETE ### Endpoint /posts/:id ### Path Parameters - **id** (string) - Required - The ID of the post to delete. ### Response #### Success Response (200) - **message** (string) - A confirmation message. #### Response Example ```json { "message": "Post deleted successfully." } ``` ``` ```APIDOC ## GET /posts/next-previous ### Description Retrieves the previous and next blog posts relative to a specified date. ### Method GET ### Endpoint /posts/next-previous ### Query Parameters - **date** (string) - Required - The reference date (ISO 8601 format). ### Response #### Success Response (200) - **previousPost** (object | null) - The previous post object or null. - **nextPost** (object | null) - The next post object or null. #### Response Example ```json { "previousPost": { "id": "1", "title": "Previous Post", "slug": "previous-post", "createdAt": "2023-10-26T10:00:00Z" }, "nextPost": { "id": "3", "title": "Next Post", "slug": "next-post", "createdAt": "2023-10-28T10:00:00Z" } } ``` ``` -------------------------------- ### Register Backend Plugins (TypeScript) Source: https://www.better-stack.ai/docs/plugins/development Shows how to register backend plugins in the Better Stack configuration. This example includes registering custom plugins and configuring backend hooks for authorization and post-creation events. ```typescript import { betterStack } from "@btst/stack" import { createMemoryAdapter } from "@btst/adapter-memory" import { todosBackendPlugin } from "./plugins/todo/api/backend" import { blogBackendPlugin } from "@btst/stack/plugins/blog/api" const { handler, dbSchema } = betterStack({ basePath: "/api/data", plugins: { todos: todosBackendPlugin, blog: blogBackendPlugin({ // Backend hooks for authorization onBeforeCreatePost: async (data, context) => { // Check authentication return true }, onPostCreated: async (post) => { console.log("Post created:", post.id) } }) }, adapter: (db) => createMemoryAdapter(db)({}) }) export { handler, dbSchema } ``` -------------------------------- ### Customize Blog Client Fetching with Hooks (TypeScript) Source: https://www.better-stack.ai/docs/plugins/blog Allows customization of client-side blog data fetching behavior using lifecycle hooks. These hooks are invoked during both Server-Side Rendering (SSR) and Client-Side Rendering (CSR). ```typescript import { blogClientPlugin } from "@btst/stack/plugins/blog/client"; // Assuming isAdmin and redirect are defined elsewhere blog: blogClientPlugin({ // ... rest of the config headers: options?.headers, hooks: { beforeLoadPosts: async (filter, context) => { // only allow loading draft posts for admin if (!filter.published) { return isAdmin(context.headers) } return true }, afterLoadPost: async (post, slug, context) => { // only allow loading draft post for admin const isEditRoute = context.path?.includes('/edit'); if (post?.published === false || isEditRoute) { return isAdmin(context.headers) } return true }, onLoadError(error, context) { //handle error during prefetching redirect("/auth/sign-in") }, // ... other hooks } }) ``` -------------------------------- ### Configure Blog Plugin Overrides (TypeScript) Source: https://www.better-stack.ai/docs/plugins/blog Enables configuration of framework-specific overrides and route lifecycle hooks for the blog plugin. All lifecycle hooks are optional. ```typescript const baseURL = "http://localhost:3000"; // Example baseURL const router = { push: (path: string) => console.log(`Navigating to: ${path}`) }; // Mock router // ... other configurations const overrides = { blog: { // Required overrides apiBaseURL: baseURL, apiBasePath: "/api/data", navigate: (path: string) => router.push(path), uploadImage: async (file: File) => { // Implement your image upload logic console.log("Uploading file:", file); return "https://example.com/uploads/image.jpg"; }, // Optional lifecycle hooks onBeforePostsPageRendered: (context: any) => { // Check if user can view posts list. This is helpful for SPA. Dont need for SSR as you should check auth in the loader. console.log("Rendering posts page with context:", context); return true; }, // ... other hooks } } ``` -------------------------------- ### Generate Kysely Schema with --database-url flag (SQLite) Source: https://www.better-stack.ai/docs/installation Generates a Kysely schema file for SQLite using the `--database-url` flag. This provides an alternative way to specify the database connection string. ```bash npx @btst/cli generate --config=lib/better-stack.ts --orm=kysely --output=migrations/schema.sql --database-url=sqlite:./dev.db ``` -------------------------------- ### Generate Kysely Schema with --database-url flag (PostgreSQL) Source: https://www.better-stack.ai/docs/installation Generates a Kysely schema file for PostgreSQL using the `--database-url` flag. This command specifies a PostgreSQL connection string for database introspection. ```bash npx @btst/cli generate --config=lib/better-stack.ts --orm=kysely --output=migrations/schema.sql --database-url=postgres://user:pass@localhost:5432/db ``` -------------------------------- ### Next.js Layout with BetterStackProvider Source: https://www.better-stack.ai/docs/installation Sets up the BetterStackProvider in a Next.js layout file (`layout.tsx`) to inject framework-specific overrides for plugins. It uses Next.js's Link and Image components and its router for navigation. Type safety is ensured by defining the shape of plugin overrides. ```tsx import { BetterStackProvider } from "@btst/stack/context" import type { ExamplePluginOverrides } from "@btst/stack/plugins/example/client" import Link from "next/link" import Image from "next/image" import { useRouter } from "next/navigation" // Define the shape of all plugin overrides for type safety type PluginOverrides = { example: ExamplePluginOverrides // Add other plugins here } export default function Layout({ children }) { const router = useRouter() return ( basePath="/pages" overrides={{ example: { Link: (props) => , Image: (props) => , navigate: (path) => router.push(path), // Add other plugin overrides here } // Add other plugins here }} > {children} ) } ``` -------------------------------- ### Convert Better Stack Handler to Node.js Handler (Express/Fastify) Source: https://www.better-stack.ai/docs/installation Converts the web API handler provided by Better Stack into a Node.js-compatible handler using `toNodeHandler`. This allows integration with Node.js server frameworks like Express or Fastify. The handler is then mounted at the specified base path. ```typescript import express from "express" import { handler } from "./lib/better-stack" import { toNodeHandler } from "@btst/stack/api" const app = express() // Convert Web API handler to Node.js handler const nodeHandler = toNodeHandler(handler) // Mount at your basePath app.use("/api/data", nodeHandler) app.listen(3000, () => { console.log("Server running on http://localhost:3000") }) ``` ```typescript import express from "express" import { handler } from "./lib/better-stack" import { toNodeHandler } from "@btst/stack/api" const app = express() app.use(express.json()) // Parse JSON bodies // Convert and mount Better Stack handler app.all("/api/data/*", toNodeHandler(handler)) app.listen(3000) ```