Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 33 additions & 30 deletions docs/content/docs/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -644,35 +644,36 @@ In order to use BTST, your application must meet the following requirements:
<Step>
### Set Up Layout Provider

Wrap your BTST pages with the `StackProvider` to enable framework-specific overrides:
Wrap your BTST pages with the `StackProvider`. The framework router preset (`router` prop) wires `Link`, `Image`, `navigate`, and `refresh` for every plugin at once, and the `api` prop sets `apiBaseURL`/`apiBasePath` for all plugins. The `overrides` prop is then only needed for genuinely plugin-specific values:

<Tabs groupId="frameworks" items={["next-js", "react-router", "tanstack"]} persist>
<Tab value="next-js">
```tsx title="app/pages/[[...all]]/layout.tsx"
"use client"
import { StackProvider } from "@btst/stack/context"
import { nextRouter } from "@btst/stack/next"
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
}

const getBaseURL = () =>
typeof window !== "undefined"
? window.location.origin
: process.env.BASE_URL || "http://localhost:3000"

export default function Layout({ children }) {
const router = useRouter()

return (
<StackProvider<PluginOverrides>
basePath="/pages"
router={nextRouter()}
api={{ baseURL: getBaseURL(), basePath: "/api/data" }}
overrides={{
example: {
Link: (props) => <Link {...props} />,
Image: (props) => <Image {...props} />,
navigate: (path) => router.push(path),
// Add other plugin overrides here
// Only plugin-specific overrides needed here
}
// Add other plugins here
}}
Expand All @@ -686,8 +687,9 @@ In order to use BTST, your application must meet the following requirements:

<Tab value="react-router">
```tsx title="app/routes/pages/_layout.tsx"
import { Outlet, Link, useNavigate } from "react-router"
import { Outlet } from "react-router"
import { StackProvider } from "@btst/stack/context"
import { reactRouter } from "@btst/stack/react-router"
import type { ExamplePluginOverrides } from "@btst/stack/plugins/example/client"

// Define the shape of all plugin overrides
Expand All @@ -696,21 +698,20 @@ In order to use BTST, your application must meet the following requirements:
// Add other plugins here
}

const getBaseURL = () =>
typeof window !== "undefined"
? window.location.origin
: process.env.BASE_URL || "http://localhost:3000"

export default function Layout() {
const navigate = useNavigate()

return (
<StackProvider<PluginOverrides>
basePath="/pages"
router={reactRouter()}
api={{ baseURL: getBaseURL(), basePath: "/api/data" }}
overrides={{
example: {
navigate: (href) => navigate(href),
Link: ({ href, children, className, ...props }) => (
<Link to={href || ""} className={className} {...props}>
{children}
</Link>
)
// Add other plugin overrides here
// Only plugin-specific overrides needed here
}
// Add other plugins here
}}
Expand All @@ -725,37 +726,38 @@ In order to use BTST, your application must meet the following requirements:
<Tab value="tanstack">
```tsx title="src/routes/pages/route.tsx"
import { StackProvider } from "@btst/stack/context"
import { tanstackRouter } from "@btst/stack/tanstack"
import { QueryClientProvider } from "@tanstack/react-query"
import type { ExamplePluginOverrides } from "@btst/stack/plugins/example/client"
import { Link, useRouter, Outlet, createFileRoute } from "@tanstack/react-router"
import { Outlet, createFileRoute } from "@tanstack/react-router"

// Define the shape of all plugin overrides
type PluginOverrides = {
example: ExamplePluginOverrides
// Add other plugins here
}

const getBaseURL = () =>
typeof window !== "undefined"
? window.location.origin
: process.env.BASE_URL || "http://localhost:3000"

export const Route = createFileRoute('/pages')({
component: Layout
})

function Layout() {
const router = useRouter()
const context = Route.useRouteContext()

return (
<QueryClientProvider client={context.queryClient}>
<StackProvider<PluginOverrides>
basePath="/pages"
router={tanstackRouter()}
api={{ baseURL: getBaseURL(), basePath: "/api/data" }}
overrides={{
example: {
navigate: (href) => router.navigate({ href }),
Link: ({ href, children, className, ...props }) => (
<Link to={href} className={className} {...props}>
{children}
</Link>
)
// Add other plugin overrides here
// Only plugin-specific overrides needed here
}
// Add other plugins here
}}
Expand All @@ -771,7 +773,8 @@ In order to use BTST, your application must meet the following requirements:

<Callout type="info">
**Understanding Overrides:**
- **Purpose**: Injects framework-specific components via React Context. Plugin components access these overrides through `usePluginOverrides()` hook, 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.
- **Purpose**: Injects framework-specific components via React Context. Plugin components access these overrides through `usePluginOverrides()` hook, 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.
- **Resolution order**: per-plugin `overrides` → top-level `router`/`api` → plugin defaults. Per-plugin `Link`/`navigate`/`Image`/`apiBaseURL` values always take precedence over the router preset, so you can still override framework wiring for a single plugin as an escape hatch.
- **Type Safety**: Each plugin exports its override type (e.g., `ExamplePluginOverrides`)
</Callout>
</Step>
Expand Down
11 changes: 11 additions & 0 deletions packages/stack/build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ export default defineBuildConfig({
"@vercel/blob/client",
"@aws-sdk/client-s3",
"@aws-sdk/s3-request-presigner",
// optional peerDependencies (framework router presets)
"next",
"next/link",
"next/image",
"next/navigation",
"react-router",
"@tanstack/react-router",
// test/build-time deps kept external
"vitest",
"@vitest/runner",
Expand All @@ -66,6 +73,10 @@ export default defineBuildConfig({
"./src/client/index.ts",
"./src/context/index.ts",
"./src/client/components/index.tsx",
// framework router presets
"./src/next/index.tsx",
"./src/react-router/index.tsx",
"./src/tanstack/index.tsx",
// plugin development entries
"./src/plugins/api/index.ts",
"./src/plugins/client/index.ts",
Expand Down
8 changes: 7 additions & 1 deletion packages/stack/knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"src/client/index.ts",
"src/context/index.ts",
"src/client/components/index.tsx",
"src/next/index.tsx",
"src/react-router/index.tsx",
"src/tanstack/index.tsx",
"src/plugins/api/index.ts",
"src/plugins/client/index.ts",
"src/plugins/blog/api/index.ts",
Expand Down Expand Up @@ -76,6 +79,9 @@
"@tailwindcss/typography",
"@aws-sdk/client-s3",
"@aws-sdk/s3-request-presigner",
"@vercel/blob"
"@vercel/blob",
"next",
"react-router",
"@tanstack/react-router"
]
}
55 changes: 55 additions & 0 deletions packages/stack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,36 @@
"default": "./dist/context/index.cjs"
}
},
"./next": {
"import": {
"types": "./dist/next/index.d.ts",
"default": "./dist/next/index.mjs"
},
"require": {
"types": "./dist/next/index.d.cts",
"default": "./dist/next/index.cjs"
}
},
"./react-router": {
"import": {
"types": "./dist/react-router/index.d.ts",
"default": "./dist/react-router/index.mjs"
},
"require": {
"types": "./dist/react-router/index.d.cts",
"default": "./dist/react-router/index.cjs"
}
},
"./tanstack": {
"import": {
"types": "./dist/tanstack/index.d.ts",
"default": "./dist/tanstack/index.mjs"
},
"require": {
"types": "./dist/tanstack/index.d.cts",
"default": "./dist/tanstack/index.cjs"
}
},
"./plugins/api": {
"import": {
"types": "./dist/plugins/api/index.d.ts",
Expand Down Expand Up @@ -605,6 +635,15 @@
"context": [
"./dist/context/index.d.ts"
],
"next": [
"./dist/next/index.d.ts"
],
"react-router": [
"./dist/react-router/index.d.ts"
],
"tanstack": [
"./dist/tanstack/index.d.ts"
],
"plugins/api": [
"./dist/plugins/api/index.d.ts"
],
Expand Down Expand Up @@ -774,6 +813,7 @@
"@radix-ui/react-switch": ">=1.1.0",
"@tailwindcss/typography": ">=0.5.0",
"@tanstack/react-query": "^5.0.0",
"@tanstack/react-router": ">=1.0.0",
"@vercel/blob": ">=0.14.0",
"ai": ">=5.0.0",
"better-call": ">=1.3.5",
Expand All @@ -784,9 +824,11 @@
"highlight.js": ">=11.9.0",
"katex": ">=0.16.0",
"lucide-react": ">=0.469.0",
"next": ">=15.0.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"react-error-boundary": ">=4.0.0",
"react-router": ">=7.0.0",
"react-hook-form": ">=7.55.0",
"react-markdown": ">=9.1.0",
"rehype-highlight": ">=7.0.0",
Expand All @@ -808,6 +850,15 @@
},
"@aws-sdk/s3-request-presigner": {
"optional": true
},
"next": {
"optional": true
},
"react-router": {
"optional": true
},
"@tanstack/react-router": {
"optional": true
}
},
"devDependencies": {
Expand All @@ -816,16 +867,20 @@
"@aws-sdk/s3-request-presigner": "^3.1011.0",
"@btst/adapter-memory": "2.2.2",
"@btst/yar": "1.3.0",
"@tanstack/react-router": "1.168.10",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/slug": "^5.0.9",
"@vercel/blob": "^0.27.3",
"@workspace/ui": "workspace:*",
"ai": "^5.0.94",
"better-call": "catalog:",
"knip": "^5.61.2",
"next": "16.0.10",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-error-boundary": "^4.1.2",
"react-router": "^7.13.1",
"rollup-plugin-preserve-directives": "0.4.0",
"rollup-plugin-visualizer": "^5.12.0",
"tsx": "catalog:",
Expand Down
Loading
Loading