@btst/stack 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +156 -709
  2. package/dist/api/index.cjs +2 -1
  3. package/dist/api/index.d.cts +4 -3
  4. package/dist/api/index.d.mts +4 -3
  5. package/dist/api/index.d.ts +4 -3
  6. package/dist/api/index.mjs +1 -1
  7. package/dist/client/components/compose.cjs +68 -0
  8. package/dist/client/components/compose.mjs +65 -0
  9. package/dist/client/components/error-boundary.cjs +24 -0
  10. package/dist/client/components/error-boundary.mjs +22 -0
  11. package/dist/client/components/index.cjs +10 -0
  12. package/dist/client/components/index.d.cts +52 -0
  13. package/dist/client/components/index.d.mts +52 -0
  14. package/dist/client/components/index.d.ts +52 -0
  15. package/dist/client/components/index.mjs +2 -0
  16. package/dist/client/index.cjs +24 -5
  17. package/dist/client/index.d.cts +125 -8
  18. package/dist/client/index.d.mts +125 -8
  19. package/dist/client/index.d.ts +125 -8
  20. package/dist/client/index.mjs +21 -4
  21. package/dist/client/meta-utils.cjs +162 -0
  22. package/dist/client/meta-utils.mjs +160 -0
  23. package/dist/client/path-utils.cjs +15 -0
  24. package/dist/client/path-utils.mjs +13 -0
  25. package/dist/client/sitemap-utils.cjs +14 -0
  26. package/dist/client/sitemap-utils.mjs +12 -0
  27. package/dist/context/index.cjs +6 -63
  28. package/dist/context/index.d.cts +21 -24
  29. package/dist/context/index.d.mts +21 -24
  30. package/dist/context/index.d.ts +21 -24
  31. package/dist/context/index.mjs +1 -61
  32. package/dist/context/provider.cjs +51 -0
  33. package/dist/context/provider.mjs +46 -0
  34. package/dist/index.cjs +2 -3
  35. package/dist/index.d.cts +3 -2
  36. package/dist/index.d.mts +3 -2
  37. package/dist/index.d.ts +3 -2
  38. package/dist/index.mjs +1 -2
  39. package/dist/plugins/api/index.cjs +13 -0
  40. package/dist/plugins/api/index.d.cts +40 -0
  41. package/dist/plugins/api/index.d.mts +40 -0
  42. package/dist/plugins/api/index.d.ts +40 -0
  43. package/dist/plugins/api/index.mjs +8 -0
  44. package/dist/plugins/blog/api/index.cjs +11 -0
  45. package/dist/plugins/blog/api/index.d.cts +7 -0
  46. package/dist/plugins/blog/api/index.d.mts +7 -0
  47. package/dist/plugins/blog/api/index.d.ts +7 -0
  48. package/dist/plugins/blog/api/index.mjs +2 -0
  49. package/dist/plugins/blog/api/plugin.cjs +569 -0
  50. package/dist/plugins/blog/api/plugin.mjs +565 -0
  51. package/dist/plugins/blog/client/components/forms/image-field.cjs +133 -0
  52. package/dist/plugins/blog/client/components/forms/image-field.mjs +131 -0
  53. package/dist/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  54. package/dist/plugins/blog/client/components/forms/markdown-editor.cjs +106 -0
  55. package/dist/plugins/blog/client/components/forms/markdown-editor.mjs +104 -0
  56. package/dist/plugins/blog/client/components/forms/post-forms.cjs +401 -0
  57. package/dist/plugins/blog/client/components/forms/post-forms.mjs +398 -0
  58. package/dist/plugins/blog/client/components/forms/tags-multiselect.cjs +71 -0
  59. package/dist/plugins/blog/client/components/forms/tags-multiselect.mjs +65 -0
  60. package/dist/plugins/blog/client/components/index.cjs +17 -0
  61. package/dist/plugins/blog/client/components/index.d.cts +22 -0
  62. package/dist/plugins/blog/client/components/index.d.mts +22 -0
  63. package/dist/plugins/blog/client/components/index.d.ts +22 -0
  64. package/dist/plugins/blog/client/components/index.mjs +12 -0
  65. package/dist/plugins/blog/client/components/loading/form-page-skeleton.cjs +62 -0
  66. package/dist/plugins/blog/client/components/loading/form-page-skeleton.mjs +60 -0
  67. package/dist/plugins/blog/client/components/loading/index.cjs +20 -0
  68. package/dist/plugins/blog/client/components/loading/index.mjs +16 -0
  69. package/dist/plugins/blog/client/components/loading/list-page-skeleton.cjs +26 -0
  70. package/dist/plugins/blog/client/components/loading/list-page-skeleton.mjs +24 -0
  71. package/dist/plugins/blog/client/components/loading/page-header-skeleton.cjs +13 -0
  72. package/dist/plugins/blog/client/components/loading/page-header-skeleton.mjs +11 -0
  73. package/dist/plugins/blog/client/components/loading/post-card-skeleton.cjs +22 -0
  74. package/dist/plugins/blog/client/components/loading/post-card-skeleton.mjs +20 -0
  75. package/dist/plugins/blog/client/components/loading/post-page-skeleton.cjs +56 -0
  76. package/dist/plugins/blog/client/components/loading/post-page-skeleton.mjs +54 -0
  77. package/dist/plugins/blog/client/components/pages/404-page.cjs +19 -0
  78. package/dist/plugins/blog/client/components/pages/404-page.mjs +17 -0
  79. package/dist/plugins/blog/client/components/pages/edit-post-page.cjs +41 -0
  80. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.cjs +57 -0
  81. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.mjs +55 -0
  82. package/dist/plugins/blog/client/components/pages/edit-post-page.mjs +39 -0
  83. package/dist/plugins/blog/client/components/pages/home-page.cjs +41 -0
  84. package/dist/plugins/blog/client/components/pages/home-page.internal.cjs +61 -0
  85. package/dist/plugins/blog/client/components/pages/home-page.internal.mjs +59 -0
  86. package/dist/plugins/blog/client/components/pages/home-page.mjs +39 -0
  87. package/dist/plugins/blog/client/components/pages/new-post-page.cjs +37 -0
  88. package/dist/plugins/blog/client/components/pages/new-post-page.internal.cjs +53 -0
  89. package/dist/plugins/blog/client/components/pages/new-post-page.internal.mjs +51 -0
  90. package/dist/plugins/blog/client/components/pages/new-post-page.mjs +35 -0
  91. package/dist/plugins/blog/client/components/pages/post-page.cjs +39 -0
  92. package/dist/plugins/blog/client/components/pages/post-page.internal.cjs +101 -0
  93. package/dist/plugins/blog/client/components/pages/post-page.internal.mjs +99 -0
  94. package/dist/plugins/blog/client/components/pages/post-page.mjs +37 -0
  95. package/dist/plugins/blog/client/components/pages/tag-page.cjs +39 -0
  96. package/dist/plugins/blog/client/components/pages/tag-page.internal.cjs +61 -0
  97. package/dist/plugins/blog/client/components/pages/tag-page.internal.mjs +59 -0
  98. package/dist/plugins/blog/client/components/pages/tag-page.mjs +37 -0
  99. package/dist/plugins/blog/client/components/shared/better-blog-attribution.cjs +24 -0
  100. package/dist/plugins/blog/client/components/shared/better-blog-attribution.mjs +22 -0
  101. package/dist/plugins/blog/client/components/shared/default-error.cjs +18 -0
  102. package/dist/plugins/blog/client/components/shared/default-error.mjs +16 -0
  103. package/dist/plugins/blog/client/components/shared/defaults.cjs +13 -0
  104. package/dist/plugins/blog/client/components/shared/defaults.mjs +10 -0
  105. package/dist/plugins/blog/client/components/shared/empty-list.cjs +21 -0
  106. package/dist/plugins/blog/client/components/shared/empty-list.mjs +19 -0
  107. package/dist/plugins/blog/client/components/shared/error-placeholder.cjs +24 -0
  108. package/dist/plugins/blog/client/components/shared/error-placeholder.mjs +22 -0
  109. package/dist/plugins/blog/client/components/shared/highlight-text.cjs +53 -0
  110. package/dist/plugins/blog/client/components/shared/highlight-text.mjs +51 -0
  111. package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  112. package/dist/plugins/blog/client/components/shared/markdown-content.cjs +324 -0
  113. package/dist/plugins/blog/client/components/shared/markdown-content.mjs +315 -0
  114. package/dist/plugins/blog/client/components/shared/on-this-page.cjs +161 -0
  115. package/dist/plugins/blog/client/components/shared/on-this-page.mjs +158 -0
  116. package/dist/plugins/blog/client/components/shared/page-header.cjs +40 -0
  117. package/dist/plugins/blog/client/components/shared/page-header.mjs +38 -0
  118. package/dist/plugins/blog/client/components/shared/page-layout.cjs +24 -0
  119. package/dist/plugins/blog/client/components/shared/page-layout.mjs +22 -0
  120. package/dist/plugins/blog/client/components/shared/page-wrapper.cjs +23 -0
  121. package/dist/plugins/blog/client/components/shared/page-wrapper.mjs +21 -0
  122. package/dist/plugins/blog/client/components/shared/post-card.cjs +279 -0
  123. package/dist/plugins/blog/client/components/shared/post-card.mjs +277 -0
  124. package/dist/plugins/blog/client/components/shared/post-navigation.cjs +74 -0
  125. package/dist/plugins/blog/client/components/shared/post-navigation.mjs +72 -0
  126. package/dist/plugins/blog/client/components/shared/posts-list.cjs +48 -0
  127. package/dist/plugins/blog/client/components/shared/posts-list.mjs +46 -0
  128. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.cjs +59 -0
  129. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.mjs +57 -0
  130. package/dist/plugins/blog/client/components/shared/search-input.cjs +136 -0
  131. package/dist/plugins/blog/client/components/shared/search-input.mjs +117 -0
  132. package/dist/plugins/blog/client/components/shared/search-modal.cjs +135 -0
  133. package/dist/plugins/blog/client/components/shared/search-modal.mjs +116 -0
  134. package/dist/plugins/blog/client/components/shared/tags-list.cjs +22 -0
  135. package/dist/plugins/blog/client/components/shared/tags-list.mjs +20 -0
  136. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.cjs +50 -0
  137. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.mjs +48 -0
  138. package/dist/plugins/blog/client/hooks/blog-hooks.cjs +380 -0
  139. package/dist/plugins/blog/client/hooks/blog-hooks.mjs +368 -0
  140. package/dist/plugins/blog/client/hooks/index.cjs +17 -0
  141. package/dist/plugins/blog/client/hooks/index.d.cts +150 -0
  142. package/dist/plugins/blog/client/hooks/index.d.mts +150 -0
  143. package/dist/plugins/blog/client/hooks/index.d.ts +150 -0
  144. package/dist/plugins/blog/client/hooks/index.mjs +1 -0
  145. package/dist/plugins/blog/client/hooks/use-debounce.cjs +16 -0
  146. package/dist/plugins/blog/client/hooks/use-debounce.mjs +14 -0
  147. package/dist/plugins/blog/client/index.cjs +7 -0
  148. package/dist/plugins/blog/client/index.d.cts +414 -0
  149. package/dist/plugins/blog/client/index.d.mts +414 -0
  150. package/dist/plugins/blog/client/index.d.ts +414 -0
  151. package/dist/plugins/blog/client/index.mjs +1 -0
  152. package/dist/plugins/blog/client/localization/blog-card.cjs +7 -0
  153. package/dist/plugins/blog/client/localization/blog-card.mjs +5 -0
  154. package/dist/plugins/blog/client/localization/blog-common.cjs +10 -0
  155. package/dist/plugins/blog/client/localization/blog-common.mjs +8 -0
  156. package/dist/plugins/blog/client/localization/blog-forms.cjs +40 -0
  157. package/dist/plugins/blog/client/localization/blog-forms.mjs +38 -0
  158. package/dist/plugins/blog/client/localization/blog-list.cjs +18 -0
  159. package/dist/plugins/blog/client/localization/blog-list.mjs +16 -0
  160. package/dist/plugins/blog/client/localization/blog-post.cjs +13 -0
  161. package/dist/plugins/blog/client/localization/blog-post.mjs +11 -0
  162. package/dist/plugins/blog/client/localization/index.cjs +17 -0
  163. package/dist/plugins/blog/client/localization/index.mjs +15 -0
  164. package/dist/plugins/blog/client/plugin.cjs +462 -0
  165. package/dist/plugins/blog/client/plugin.mjs +460 -0
  166. package/dist/plugins/blog/client.css +3 -0
  167. package/dist/plugins/blog/db.cjs +90 -0
  168. package/dist/plugins/blog/db.mjs +88 -0
  169. package/dist/plugins/blog/query-keys.cjs +181 -0
  170. package/dist/plugins/blog/query-keys.d.cts +530 -0
  171. package/dist/plugins/blog/query-keys.d.mts +530 -0
  172. package/dist/plugins/blog/query-keys.d.ts +530 -0
  173. package/dist/plugins/blog/query-keys.mjs +179 -0
  174. package/dist/plugins/blog/schemas.cjs +39 -0
  175. package/dist/plugins/blog/schemas.mjs +35 -0
  176. package/dist/plugins/blog/style.css +22 -0
  177. package/dist/plugins/blog/utils.cjs +97 -0
  178. package/dist/plugins/blog/utils.mjs +87 -0
  179. package/dist/plugins/client/index.cjs +15 -0
  180. package/dist/plugins/client/index.d.cts +57 -0
  181. package/dist/plugins/client/index.d.mts +57 -0
  182. package/dist/plugins/client/index.d.ts +57 -0
  183. package/dist/plugins/client/index.mjs +9 -0
  184. package/dist/{shared/stack.3OUyGp_E.mjs → plugins/utils.mjs} +1 -1
  185. package/dist/shared/{stack.DORw_1ps.d.cts → stack.ByOugz9d.d.cts} +17 -1
  186. package/dist/shared/{stack.DORw_1ps.d.mts → stack.ByOugz9d.d.mts} +17 -1
  187. package/dist/shared/{stack.DORw_1ps.d.ts → stack.ByOugz9d.d.ts} +17 -1
  188. package/dist/shared/stack.CoPoHVfV.d.cts +76 -0
  189. package/dist/shared/stack.CoPoHVfV.d.mts +76 -0
  190. package/dist/shared/stack.CoPoHVfV.d.ts +76 -0
  191. package/package.json +102 -14
  192. package/src/__tests__/plugins.test.tsx +539 -0
  193. package/src/__tests__/sitemap.test.ts +60 -0
  194. package/src/api/index.ts +75 -0
  195. package/src/client/components/compose.tsx +116 -0
  196. package/src/client/components/error-boundary.tsx +30 -0
  197. package/src/client/components/index.tsx +2 -0
  198. package/src/client/index.ts +109 -0
  199. package/src/client/meta-utils.ts +228 -0
  200. package/src/client/path-utils.ts +38 -0
  201. package/src/client/sitemap-utils.ts +46 -0
  202. package/src/context/index.ts +1 -0
  203. package/src/context/provider.tsx +157 -0
  204. package/src/index.ts +1 -0
  205. package/src/plugins/api/index.ts +50 -0
  206. package/src/plugins/blog/api/index.ts +2 -0
  207. package/src/plugins/blog/api/plugin.ts +759 -0
  208. package/src/plugins/blog/client/components/forms/image-field.tsx +165 -0
  209. package/src/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  210. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +136 -0
  211. package/src/plugins/blog/client/components/forms/post-forms.tsx +531 -0
  212. package/src/plugins/blog/client/components/forms/tags-multiselect.tsx +79 -0
  213. package/src/plugins/blog/client/components/index.tsx +11 -0
  214. package/src/plugins/blog/client/components/loading/form-page-skeleton.tsx +75 -0
  215. package/src/plugins/blog/client/components/loading/index.tsx +27 -0
  216. package/src/plugins/blog/client/components/loading/list-page-skeleton.tsx +38 -0
  217. package/src/plugins/blog/client/components/loading/page-header-skeleton.tsx +10 -0
  218. package/src/plugins/blog/client/components/loading/post-card-skeleton.tsx +30 -0
  219. package/src/plugins/blog/client/components/loading/post-page-skeleton.tsx +75 -0
  220. package/src/plugins/blog/client/components/pages/404-page.tsx +23 -0
  221. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +60 -0
  222. package/src/plugins/blog/client/components/pages/edit-post-page.tsx +40 -0
  223. package/src/plugins/blog/client/components/pages/home-page.internal.tsx +71 -0
  224. package/src/plugins/blog/client/components/pages/home-page.tsx +42 -0
  225. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +59 -0
  226. package/src/plugins/blog/client/components/pages/new-post-page.tsx +36 -0
  227. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +142 -0
  228. package/src/plugins/blog/client/components/pages/post-page.tsx +38 -0
  229. package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +74 -0
  230. package/src/plugins/blog/client/components/pages/tag-page.tsx +38 -0
  231. package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +19 -0
  232. package/src/plugins/blog/client/components/shared/default-error.tsx +20 -0
  233. package/src/plugins/blog/client/components/shared/defaults.tsx +9 -0
  234. package/src/plugins/blog/client/components/shared/empty-list.tsx +25 -0
  235. package/src/plugins/blog/client/components/shared/error-placeholder.tsx +20 -0
  236. package/src/plugins/blog/client/components/shared/highlight-text.tsx +80 -0
  237. package/src/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  238. package/src/plugins/blog/client/components/shared/markdown-content.tsx +448 -0
  239. package/src/plugins/blog/client/components/shared/on-this-page.tsx +234 -0
  240. package/src/plugins/blog/client/components/shared/page-header.tsx +35 -0
  241. package/src/plugins/blog/client/components/shared/page-layout.tsx +23 -0
  242. package/src/plugins/blog/client/components/shared/page-wrapper.tsx +32 -0
  243. package/src/plugins/blog/client/components/shared/post-card.tsx +308 -0
  244. package/src/plugins/blog/client/components/shared/post-navigation.tsx +98 -0
  245. package/src/plugins/blog/client/components/shared/posts-list.tsx +67 -0
  246. package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +79 -0
  247. package/src/plugins/blog/client/components/shared/search-input.tsx +146 -0
  248. package/src/plugins/blog/client/components/shared/search-modal.tsx +162 -0
  249. package/src/plugins/blog/client/components/shared/tags-list.tsx +34 -0
  250. package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +68 -0
  251. package/src/plugins/blog/client/hooks/blog-hooks.tsx +623 -0
  252. package/src/plugins/blog/client/hooks/index.tsx +1 -0
  253. package/src/plugins/blog/client/hooks/use-debounce.ts +43 -0
  254. package/src/plugins/blog/client/index.ts +9 -0
  255. package/src/plugins/blog/client/localization/blog-card.ts +3 -0
  256. package/src/plugins/blog/client/localization/blog-common.ts +7 -0
  257. package/src/plugins/blog/client/localization/blog-forms.ts +45 -0
  258. package/src/plugins/blog/client/localization/blog-list.ts +14 -0
  259. package/src/plugins/blog/client/localization/blog-post.ts +9 -0
  260. package/src/plugins/blog/client/localization/index.ts +15 -0
  261. package/src/plugins/blog/client/overrides.ts +123 -0
  262. package/src/plugins/blog/client/plugin.tsx +672 -0
  263. package/src/plugins/blog/client.css +3 -0
  264. package/src/plugins/blog/db.ts +90 -0
  265. package/src/plugins/blog/query-keys.ts +267 -0
  266. package/src/plugins/blog/schemas.ts +39 -0
  267. package/src/plugins/blog/style.css +22 -0
  268. package/src/plugins/blog/types.ts +37 -0
  269. package/src/plugins/blog/utils.ts +144 -0
  270. package/src/plugins/client/index.ts +53 -0
  271. package/src/plugins/index.ts +0 -0
  272. package/src/plugins/utils.ts +35 -0
  273. package/src/types.ts +209 -0
  274. package/dist/plugins/index.cjs +0 -15
  275. package/dist/plugins/index.d.cts +0 -64
  276. package/dist/plugins/index.d.mts +0 -64
  277. package/dist/plugins/index.d.ts +0 -64
  278. package/dist/plugins/index.mjs +0 -11
  279. package/dist/shared/stack.DrUAVfIH.d.cts +0 -17
  280. package/dist/shared/stack.DrUAVfIH.d.mts +0 -17
  281. package/dist/shared/stack.DrUAVfIH.d.ts +0 -17
  282. /package/dist/{shared/stack.CktCg4PJ.cjs → plugins/utils.cjs} +0 -0
package/README.md CHANGED
@@ -1,791 +1,238 @@
1
- # Better Stack
1
+ # @BTST - Better Stack
2
2
 
3
3
  <div align="center">
4
4
 
5
- **A composable, plugin-based framework for building full-stack TypeScript applications**
5
+ **Composable full-stack plugin system for React frameworks**
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@btst/stack.svg)](https://www.npmjs.com/package/@btst/stack)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
9
 
10
+ [📖 Documentation](https://www.better-stack.ai) • [🐛 Issues](https://github.com/olliethedev/better-stack/issues)
11
+
10
12
  </div>
11
13
 
12
- ## Overview
14
+ ---
13
15
 
14
- Better Stack is a modern, type-safe framework for building full-stack applications with a plugin architecture. It seamlessly integrates:
16
+ ## What Problem Does This Solve?
15
17
 
16
- - **🔌 Plugin Architecture** - Build modular, reusable features as standalone plugins
17
- - **🔄 Full-Stack Type Safety** - End-to-end TypeScript with automatic type inference, no casts needed
18
- - **🗄️ Schema-First Database** - Define your data models once using Better DB with typed adapters
19
- - **🚀 Production Ready** - Built on proven libraries (Better Call, Better DB, Yar Router)
20
- - **⚡ Zero Config** - Works out of the box with sensible defaults
21
- - **✨ Developer Experience** - Helper functions preserve route keys, hook names, and endpoint types
18
+ Your app needs a blog. Or a scheduling system. Or user feedback collection. Or an AI assistant. These are **horizontal features**—capabilities that cut across your entire app, not specific to your core domain.
22
19
 
23
- ## Installation
20
+ Building them from scratch means weeks of work: routes, API endpoints, database schemas, authentication, SSR, metadata, hooks, forms, error handling...
24
21
 
25
- ```bash
26
- npm install @btst/stack
27
- # or
28
- pnpm add @btst/stack
29
- # or
30
- yarn add @btst/stack
31
- ```
22
+ Better Stack lets you **add these features in minutes** as composable plugins that work across any React framework.
32
23
 
33
- ### Dependencies
24
+ - **Composable architecture** - Mix and match features like LEGO blocks. Add blog + scheduling + feedback + newsletters, all working together seamlessly
25
+ - **Framework agnostic** - One feature works with Next.js App Router, React Router, TanStack Router, Remix—switch frameworks without rewriting
26
+ - **Plugin overrides** - Leverage framework-specific features via overrides. Use Next.js `Image` and `Link`, React Router's `Link`, or any framework's components
27
+ - **Full-stack in one package** - Each feature includes routes, API endpoints, database schemas, React components, hooks, loaders, and metadata
28
+ - **Zero boilerplate** - No wiring up routes, API handlers, or query clients. Just configure and it works
29
+ - **First-class SSR** - Server-side rendering, data prefetching, and SEO metadata generation built-in
30
+ - **Lifecycle hooks** - Intercept at any point: authorization, data transformation, analytics, caching, webhooks
31
+ - **Horizontal features** - Perfect for blog, scheduling, feedback, newsletters, AI assistants, comments—anything reusable across apps
34
32
 
35
- Better Stack works with your existing stack:
33
+ ## What Can You Add?
36
34
 
37
- ```bash
38
- npm install @btst/db @btst/adapter-memory better-call @btst/yar zod
39
- ```
35
+ **Blog** - Content management, editor, drafts, publishing, SEO, RSS feeds
40
36
 
41
- ## Quick Start
37
+ **Scheduling** - Calendar views, time slot booking, availability management, reminders
42
38
 
43
- ### 1. Create Your Backend API
39
+ **Feedback** - In-app feedback widgets, user surveys, bug reporting, feature requests
44
40
 
45
- ```typescript
46
- // app/api/route.ts
47
- import { betterStack } from "@btst/stack/api";
48
- import { myPlugin } from "./plugins/my-plugin";
49
- import { createMemoryAdapter } from "@btst/adapter-memory";
41
+ **Newsletters** - Subscriber management, email campaigns, unsubscribe handling, analytics
50
42
 
51
- const api = betterStack({
52
- plugins: {
53
- myFeature: myPlugin.backend,
54
- },
55
- adapter: createMemoryAdapter,
56
- });
57
-
58
- // Export for Next.js App Router
59
- export const GET = api.handler;
60
- export const POST = api.handler;
61
- export const PUT = api.handler;
62
- export const DELETE = api.handler;
63
- export const PATCH = api.handler;
64
- ```
65
-
66
- ### 2. Create Your Client
67
-
68
- ```typescript
69
- // app/lib/client.ts
70
- import { createStackClient } from "@btst/stack/client";
71
- import { myPlugin } from "./plugins/my-plugin";
43
+ **AI Assistant** - Chat interfaces, prompt templates, conversation history, streaming responses
72
44
 
73
- export const client = createStackClient({
74
- plugins: {
75
- myFeature: myPlugin.client,
76
- },
77
- });
78
-
79
- // Access router and hooks
80
- export const { router, hooks } = client;
81
- ```
45
+ **Comments** - Threaded discussions, moderation, reactions, notifications
82
46
 
83
- ### 3. Use in Your Components
47
+ And any other horizontal feature your app needs. Each comes with a complete UI, backend, and data layer.
84
48
 
85
- ```tsx
86
- // app/components/MyComponent.tsx
87
- "use client";
88
- import { hooks } from "../lib/client";
89
-
90
- export function MyComponent() {
91
- const { useMyData } = hooks.myFeature;
92
- const { data, isLoading } = useMyData();
93
-
94
- if (isLoading) return <div>Loading...</div>;
49
+ ## Installation
95
50
 
96
- return (
97
- <div>
98
- {data?.items.map((item) => (
99
- <div key={item.id}>{item.name}</div>
100
- ))}
101
- </div>
102
- );
103
- }
51
+ ```bash
52
+ npm install @btst/stack
104
53
  ```
105
54
 
106
- ## Building Custom Plugins
107
-
108
- Better Stack's power comes from its plugin architecture. Here's how to build your own:
109
-
110
- ### Plugin Architecture
111
-
112
- Better Stack uses **separate backend and client plugins** to:
113
- - ✅ Prevent SSR issues
114
- - ✅ Enable better code splitting
115
- - ✅ Allow independent deployment and versioning
116
- - ✅ Improve tree-shaking
117
-
118
- Each plugin type is completely independent:
119
-
120
- 1. **Backend Plugin** - API endpoints, database schema, and business logic
121
- 2. **Client Plugin** - Routes, components, and React hooks
122
-
123
- ### Example: Messages Plugin (Backend)
124
-
125
- ```typescript
126
- // plugins/messages/backend.ts
127
- import { createDbPlugin } from "@btst/db";
128
- import { createEndpoint } from "better-call";
129
- import { z } from "zod";
130
- import {
131
- defineBackendPlugin,
132
- type BetterAuthDBSchema,
133
- } from "@btst/stack/plugins";
134
-
135
- // 1. Define your schema
136
- const messagesSchema: BetterAuthDBSchema = {
137
- messages: {
138
- modelName: "Message",
139
- fields: {
140
- id: {
141
- type: "number",
142
- unique: true,
143
- required: true,
144
- },
145
- content: {
146
- type: "string",
147
- required: true,
148
- },
149
- userId: {
150
- type: "string",
151
- required: true,
152
- },
153
- createdAt: {
154
- type: "number",
155
- required: true,
156
- },
157
- },
158
- },
159
- };
160
-
161
- // 2. Create and export the backend plugin with full type inference
162
- export const messagesBackendPlugin = defineBackendPlugin({
163
- name: "messages",
164
- dbPlugin: createDbPlugin("messages", messagesSchema),
165
- routes: (adapter) => ({
166
- // List messages
167
- list: createEndpoint(
168
- "/messages",
169
- {
170
- method: "GET",
171
- query: z.object({
172
- userId: z.string().optional(),
173
- }),
174
- },
175
- async ({ query }) => {
176
- const messages = await adapter.findMany({
177
- model: "messages",
178
- where: query.userId
179
- ? [{ field: "userId", value: query.userId, operator: "eq" }]
180
- : undefined,
181
- });
182
- return {
183
- status: 200,
184
- body: messages,
185
- };
186
- }
187
- ),
188
-
189
- // Create message
190
- create: createEndpoint(
191
- "/messages",
192
- {
193
- method: "POST",
194
- body: z.object({
195
- content: z.string().min(1),
196
- userId: z.string().min(1),
197
- }),
198
- },
199
- async ({ body }) => {
200
- const message = await adapter.create({
201
- model: "messages",
202
- data: {
203
- ...body,
204
- id: Date.now(),
205
- createdAt: Date.now(),
206
- },
207
- });
208
- return {
209
- status: 201,
210
- body: message,
211
- };
212
- }
213
- ),
214
-
215
- // Delete message
216
- delete: createEndpoint(
217
- "/messages/:id",
218
- {
219
- method: "DELETE",
220
- params: z.object({
221
- id: z.coerce.number(),
222
- }),
223
- },
224
- async ({ params }) => {
225
- await adapter.delete({
226
- model: "messages",
227
- where: [{ field: "id", value: params.id, operator: "eq" }],
228
- });
229
- return {
230
- status: 204,
231
- body: null,
232
- };
233
- }
234
- ),
235
- }),
236
- });
237
- ```
55
+ For database schema management, install the CLI:
238
56
 
239
- ### Example: Messages Plugin (Client)
240
-
241
- ```typescript
242
- // plugins/messages/client.ts
243
- import { defineClientPlugin } from "@btst/stack/plugins";
244
- import { createRoute } from "@btst/yar";
245
- import { useQuery } from "@tanstack/react-query";
246
-
247
- // Components
248
- const MessagesPage = ({ data }: { data: any }) => (
249
- <div>{/* Render messages */}</div>
250
- );
251
-
252
- // Create and export the client plugin with full type inference
253
- export const messagesClientPlugin = defineClientPlugin({
254
- name: "messages",
255
- routes: () => ({
256
- // Use Yar's createRoute for proper route creation
257
- messagesList: createRoute(
258
- "/messages",
259
- () => ({
260
- PageComponent: MessagesPage,
261
- loader: async () => {
262
- const response = await fetch("/api/messages");
263
- return response.json();
264
- },
265
- meta: (data) => [
266
- { name: "title", content: "Messages" },
267
- { name: "description", content: `${data.length} messages` },
268
- ],
269
- })
270
- ),
271
- }),
272
- hooks: () => ({
273
- useMessages: () => {
274
- // Use React Query or your preferred data fetching library
275
- return useQuery({
276
- queryKey: ["messages"],
277
- queryFn: async () => {
278
- const response = await fetch("/api/messages");
279
- return response.json();
280
- },
281
- });
282
- },
283
- }),
284
- });
57
+ ```bash
58
+ npm install -D @btst/cli
285
59
  ```
286
60
 
287
- > **💡 Type Safety Tip:** Using `defineClientPlugin` and `defineBackendPlugin` helpers ensures full type inference without needing type annotations or casts. Your route keys, hook names, and endpoint types are automatically preserved!
288
-
289
- ### Using Your Plugins
290
-
291
- Backend and client plugins are used independently in their respective contexts:
292
-
293
- #### Backend Usage
294
-
295
- ```typescript
296
- // app/api/route.ts (Backend only)
297
- import { betterStack } from "@btst/stack/api";
298
- import { messagesBackendPlugin } from "./plugins/messages/backend";
299
- import { createMemoryAdapter } from "@btst/adapter-memory";
300
-
301
- const api = betterStack({
302
- plugins: {
303
- messages: messagesBackendPlugin,
304
- },
305
- adapter: createMemoryAdapter,
306
- });
61
+ The CLI helps generate migrations, Prisma schemas, and other database artifacts from your plugin schemas.
307
62
 
308
- export const GET = api.handler;
309
- export const POST = api.handler;
310
- ```
63
+ ## Quick Example: Add a Blog to Next.js
311
64
 
312
- #### Client Usage
65
+ ### 1. Backend API (`lib/better-stack.ts`)
313
66
 
314
- ```typescript
315
- // app/lib/client.ts (Client only)
316
- import { createStackClient } from "@btst/stack/client";
317
- import { messagesClientPlugin } from "./plugins/messages/client";
67
+ ```ts
68
+ import { betterStack } from "@btst/stack"
69
+ import { blogBackendPlugin } from "@btst/stack/plugins/blog/api"
70
+ import { createPrismaAdapter } from "@btst/adapter-prisma"
318
71
 
319
- const client = createStackClient({
72
+ const { handler, dbSchema } = betterStack({
73
+ basePath: "/api/data",
320
74
  plugins: {
321
- messages: messagesClientPlugin,
75
+ blog: blogBackendPlugin()
322
76
  },
323
- });
77
+ adapter: (db) => createPrismaAdapter(db)({})
78
+ })
324
79
 
325
- export const { router, hooks } = client;
326
-
327
- // Use in components
328
- // const { useMessages } = hooks.messages;
80
+ export { handler, dbSchema }
329
81
  ```
330
82
 
331
- ## Core Concepts
332
-
333
- ### 1. Plugins
334
-
335
- Plugins are self-contained features that can be composed together. Each plugin can provide:
336
-
337
- - **Database Schema** - Table definitions using Better DB
338
- - **API Endpoints** - Type-safe REST endpoints using Better Call
339
- - **Client Routes** - Page routing using Yar Router
340
- - **React Hooks** - Data fetching and mutations
341
-
342
- ### 2. Adapters
343
-
344
- Adapters connect your plugins to different databases:
83
+ **Note:** `betterStack()` returns both `handler` and `dbSchema`. The `dbSchema` contains all merged database schemas from your plugins. Use [@btst/cli](https://www.npmjs.com/package/@btst/cli) to generate migrations, Prisma schemas, or other database artifacts from your `dbSchema`.
345
84
 
346
- ```typescript
347
- // Memory (for testing)
348
- import { createMemoryAdapter } from "@btst/adapter-memory";
85
+ For example, to generate a Prisma schema from your `dbSchema`:
349
86
 
350
- // PostgreSQL
351
- import { createPgAdapter } from "@btst/adapter-pg";
352
-
353
- // MongoDB
354
- import { createMongoAdapter } from "@btst/adapter-mongo";
355
-
356
- const api = betterStack({
357
- plugins: { /* ... */ },
358
- adapter: createMemoryAdapter, // or createPgAdapter, createMongoAdapter
359
- });
360
- ```
361
-
362
- ### 3. Type Safety
363
-
364
- Better Stack provides end-to-end type safety with automatic type inference:
365
-
366
- ```typescript
367
- // ✅ Backend: Route keys and endpoint types are preserved
368
- const myPlugin = defineBackendPlugin({
369
- name: "users",
370
- routes: (adapter) => ({
371
- getUser: createEndpoint(
372
- "/users/:id",
373
- {
374
- method: "GET",
375
- params: z.object({ id: z.string() }),
376
- },
377
- async ({ params }) => {
378
- // params.id is typed as string
379
- return { status: 200, body: { id: params.id, name: "John" } };
380
- }
381
- ),
382
- listUsers: createEndpoint(/* ... */),
383
- })
384
- });
385
- // Type: BackendPlugin<{ getUser: Endpoint, listUsers: Endpoint }>
386
-
387
- // ✅ Client: Route keys and hook types are preserved
388
- const myClientPlugin = defineClientPlugin({
389
- name: "users",
390
- routes: () => ({
391
- userProfile: { path: "/users/:id", Component: UserProfile },
392
- usersList: { path: "/users", Component: UsersList },
393
- }),
394
- hooks: () => ({
395
- useUser: (id: string) => { /* ... */ },
396
- useUsers: () => { /* ... */ },
397
- })
398
- });
399
- // Routes "userProfile" and "usersList" are fully typed!
400
- // Hooks "useUser" and "useUsers" are accessible via hooks.users.*
401
-
402
- ```
403
-
404
- ## Plugin Utilities
405
-
406
- Better Stack exports utilities for building plugins:
407
-
408
- ```typescript
409
- // Type-safe plugin helpers (recommended)
410
- import {
411
- defineBackendPlugin,
412
- defineClientPlugin,
413
- } from "@btst/stack/plugins";
414
-
415
- // Type definitions for backend plugins
416
- import type {
417
- BackendPlugin,
418
- Adapter,
419
- DatabaseDefinition,
420
- Endpoint,
421
- Router,
422
- } from "@btst/stack/plugins";
423
-
424
- // Type definitions for client plugins
425
- import type {
426
- ClientPlugin,
427
- Route,
428
- } from "@btst/stack/plugins";
429
-
430
- // Utilities (can be used in both)
431
- import {
432
- createApiClient,
433
- } from "@btst/stack/plugins";
434
- ```
435
-
436
- ### Type-Safe Plugin Helpers
437
-
438
- Use `defineBackendPlugin` and `defineClientPlugin` for automatic type inference:
439
-
440
- ```typescript
441
- // ✅ Recommended: Full type inference, no casts needed
442
- const myPlugin = defineBackendPlugin({
443
- name: "myFeature",
444
- routes: (adapter) => ({
445
- list: endpoint(...),
446
- create: endpoint(...),
447
- })
448
- });
449
- // Route keys "list" and "create" are fully typed!
450
-
451
- // ❌ Old way: Manual type annotation
452
- const myPlugin: BackendPlugin = {
453
- name: "myFeature",
454
- routes: (adapter) => ({
455
- list: endpoint(...),
456
- create: endpoint(...),
457
- })
458
- };
459
- // Route keys are erased to generic Record<string, Endpoint>
87
+ ```bash
88
+ npx @btst/cli generate --orm prisma --config lib/better-db.ts --output prisma/schema.prisma --filter-auth
460
89
  ```
461
90
 
462
- ### Why Separate Backend and Client Plugins?
91
+ This reads your `dbSchema` export and generates the corresponding Prisma schema file.
463
92
 
464
- **Traditional Approach (Unified Plugin):**
465
- ```typescript
466
- // ❌ This can cause SSR issues and bundle bloat
467
- export const myPlugin = {
468
- backend: { /* server-side code */ },
469
- client: { /* client-side code */ },
470
- };
471
- ```
93
+ ### 2. API Route (`app/api/[[...]]/route.ts`)
472
94
 
473
- **Better Stack Approach (Separated Plugins):**
474
- ```typescript
475
- // ✅ Backend stays on the server
476
- // plugins/my-plugin/backend.ts
477
- export const myBackendPlugin: BackendPlugin = { /* ... */ };
95
+ ```ts
96
+ import { handler } from "@/lib/better-stack"
478
97
 
479
- // Client stays on the client
480
- // plugins/my-plugin/client.ts
481
- export const myClientPlugin: ClientPlugin = { /* ... */ };
98
+ export const GET = handler
99
+ export const POST = handler
482
100
  ```
483
101
 
484
- **Benefits:**
485
- 1. **No SSR Issues** - Server code never reaches the client bundle
486
- 2. **Better Code Splitting** - Frontend and backend can be deployed separately
487
- 3. **Smaller Bundles** - Client bundles don't include server dependencies
488
- 4. **Independent Versioning** - Update backend without touching frontend
489
- 5. **Type Safety** - TypeScript ensures correct usage in each context
490
-
491
- ### API Client Utility
492
-
493
- Create a typed API client for server-side or client-side requests:
494
-
495
- ```typescript
496
- import { createApiClient } from "@btst/stack/plugins";
102
+ ### 3. Client Setup (`lib/better-stack-client.tsx`)
497
103
 
498
- // For server-side (SSR)
499
- const api = createApiClient({
500
- baseURL: "http://localhost:3000",
501
- basePath: "/api",
502
- });
104
+ ```ts
105
+ import { createStackClient } from "@btst/stack/client"
106
+ import { blogClientPlugin } from "@btst/stack/plugins/blog/client"
503
107
 
504
- // For client-side (uses relative URLs)
505
- const api = createApiClient({
506
- basePath: "/api",
507
- });
508
- ```
509
-
510
- ## Testing Your Plugins
511
-
512
- Better Stack includes comprehensive testing utilities:
513
-
514
- ```typescript
515
- // __tests__/my-plugin.test.ts
516
- import { describe, it, expect } from "vitest";
517
- import { betterStack } from "@btst/stack/api";
518
- import { createMemoryAdapter } from "@btst/adapter-memory";
519
- import { myBackendPlugin } from "../plugins/my-plugin/backend";
520
-
521
- describe("My Backend Plugin", () => {
522
- it("should create and retrieve items", async () => {
523
- // Adapter wrapper for testing
524
- const testAdapter = (db) => createMemoryAdapter(db)({});
525
-
526
- const api = betterStack({
527
- plugins: { myFeature: myBackendPlugin },
528
- adapter: testAdapter,
529
- });
530
-
531
- // Create an item
532
- const createResponse = await api.handler(
533
- new Request("http://localhost:3000/api/items", {
534
- method: "POST",
535
- headers: { "Content-Type": "application/json" },
536
- body: JSON.stringify({ name: "Test Item" }),
108
+ export const getStackClient = (queryClient: QueryClient) => {
109
+ return createStackClient({
110
+ plugins: {
111
+ blog: blogClientPlugin({
112
+ queryClient,
113
+ apiBaseURL: baseURL,
114
+ apiBasePath: "/api/data",
115
+ siteBaseURL: baseURL,
116
+ siteBasePath: "/pages"
537
117
  })
538
- );
539
-
540
- expect(createResponse.status).toBe(201);
541
-
542
- // Retrieve items
543
- const listResponse = await api.handler(
544
- new Request("http://localhost:3000/api/items", {
545
- method: "GET",
546
- })
547
- );
548
-
549
- const items = await listResponse.json();
550
- expect(items).toHaveLength(1);
551
- expect(items[0].name).toBe("Test Item");
552
- });
553
- });
554
- ```
555
-
556
- ## Framework Integration
557
-
558
- ### Next.js App Router
559
-
560
- ```typescript
561
- // app/api/[...route]/route.ts
562
- import { betterStack } from "@btst/stack/api";
563
-
564
- const api = betterStack({
565
- plugins: { /* ... */ },
566
- adapter: /* ... */,
567
- });
568
-
569
- export const GET = api.handler;
570
- export const POST = api.handler;
571
- export const PUT = api.handler;
572
- export const DELETE = api.handler;
573
- export const PATCH = api.handler;
574
- ```
575
-
576
- ### Next.js Pages Router
577
-
578
- ```typescript
579
- // pages/api/[...route].ts
580
- import { betterStack } from "@btst/stack/api";
581
-
582
- const api = betterStack({
583
- plugins: { /* ... */ },
584
- adapter: /* ... */,
585
- });
586
-
587
- export default async function handler(req: NextApiRequest, res: NextApiResponse) {
588
- const request = new Request(
589
- `http://localhost:3000${req.url}`,
590
- {
591
- method: req.method,
592
- headers: req.headers as HeadersInit,
593
- body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
594
118
  }
595
- );
596
-
597
- const response = await api.handler(request);
598
- const data = await response.json();
599
-
600
- res.status(response.status).json(data);
119
+ })
601
120
  }
602
121
  ```
603
122
 
604
- ### Standalone Express/Node.js
605
-
606
- ```typescript
607
- import express from "express";
608
- import { betterStack } from "@btst/stack/api";
609
-
610
- const api = betterStack({
611
- plugins: { /* ... */ },
612
- adapter: /* ... */,
613
- });
614
-
615
- const app = express();
616
-
617
- app.all("/api/*", async (req, res) => {
618
- const request = new Request(`http://localhost:3000${req.url}`, {
619
- method: req.method,
620
- headers: req.headers as HeadersInit,
621
- body: req.method !== "GET" ? JSON.stringify(req.body) : undefined,
622
- });
623
-
624
- const response = await api.handler(request);
625
- const data = await response.json();
626
-
627
- res.status(response.status).json(data);
628
- });
629
-
630
- app.listen(3000);
631
- ```
632
-
633
- ## Architecture
634
-
635
- Better Stack is built on top of proven libraries:
636
-
637
- ```
638
- ┌─────────────────────────────────────────┐
639
- │ Better Stack │
640
- │ (Plugin Composition & Type Safety) │
641
- ├─────────────────────────────────────────┤
642
- │ Better Call │ Better DB │ Yar │
643
- │ (API Layer) │ (Database) │ (Router) │
644
- └─────────────────────────────────────────┘
645
- ```
646
-
647
- ### Core Libraries
648
-
649
- - **[Better Call](https://github.com/olliethedev/better-call)** - Type-safe API endpoints with automatic request/response handling
650
- - **[Better DB](https://github.com/olliethedev/better-auth)** - Schema-first database ORM with multiple adapter support
651
- - **[Yar Router](https://github.com/olliethedev/yar)** - Lightweight client-side router for React
652
-
653
- ## Publishing Your Plugin
654
-
655
- Want to share your plugin with the community? Publish backend and client as separate packages for maximum flexibility.
123
+ ### 4. Page Handler (`app/pages/[[...all]]/page.tsx`)
656
124
 
657
- ### Publishing Backend Plugin
658
-
659
- 1. Create backend package:
660
- ```bash
661
- mkdir my-plugin-backend
662
- cd my-plugin-backend
663
- npm init
664
- ```
665
-
666
- 2. Structure:
667
- ```
668
- my-plugin-backend/
669
- ├── src/
670
- │ └── index.ts # Export BackendPlugin
671
- ├── package.json
672
- └── tsconfig.json
673
- ```
674
-
675
- 3. Add peer dependencies:
676
- ```json
677
- {
678
- "name": "@yourorg/my-plugin-backend",
679
- "peerDependencies": {
680
- "@btst/stack": "^1.0.0",
681
- "@btst/db": "^1.0.0",
682
- "better-call": "^1.0.19"
683
- }
125
+ ```ts
126
+ export default async function Page({ params }) {
127
+ const path = `/${(await params).all?.join("/") || ""}`
128
+ const stackClient = getStackClient(queryClient)
129
+ const route = stackClient.router.getRoute(path)
130
+
131
+ if (route?.loader) await route.loader()
132
+
133
+ return (
134
+ <HydrationBoundary state={dehydrate(queryClient)}>
135
+ <ClientRouteResolver path={path} />
136
+ </HydrationBoundary>
137
+ )
684
138
  }
685
139
  ```
686
140
 
687
- ### Publishing Client Plugin
141
+ ### 5. Layout Provider (`app/pages/[[...all]]/layout.tsx`)
688
142
 
689
- 1. Create client package:
690
- ```bash
691
- mkdir my-plugin-client
692
- cd my-plugin-client
693
- npm init
694
- ```
695
-
696
- 2. Structure:
697
- ```
698
- my-plugin-client/
699
- ├── src/
700
- │ └── index.tsx # Export ClientPlugin
701
- ├── package.json
702
- └── tsconfig.json
703
- ```
143
+ ```ts
144
+ import { BetterStackProvider } from "@btst/stack/context"
145
+ import Link from "next/link"
146
+ import Image from "next/image"
147
+ import { useRouter } from "next/navigation"
704
148
 
705
- 3. Add peer dependencies:
706
- ```json
707
- {
708
- "name": "@yourorg/my-plugin-client",
709
- "peerDependencies": {
710
- "@btst/stack": "^1.0.0",
711
- "@btst/yar": "^1.1.1",
712
- "react": "^18.0.0 || ^19.0.0"
713
- }
149
+ export default function Layout({ children }) {
150
+ const router = useRouter()
151
+
152
+ return (
153
+ <BetterStackProvider
154
+ basePath="/pages"
155
+ overrides={{
156
+ blog: {
157
+ // Use Next.js optimized Image component
158
+ Image: (props) => (
159
+ <Image
160
+ alt={props.alt || ""}
161
+ src={props.src || ""}
162
+ width={400}
163
+ height={300}
164
+ />
165
+ ),
166
+ // Use Next.js Link for client-side navigation
167
+ navigate: (path) => router.push(path),
168
+ refresh: () => router.refresh()
169
+ }
170
+ }}
171
+ >
172
+ {children}
173
+ </BetterStackProvider>
174
+ )
714
175
  }
715
176
  ```
716
177
 
717
- 4. Users can install only what they need:
718
- ```bash
719
- # Server-only deployment
720
- npm install @yourorg/my-plugin-backend
721
-
722
- # Client-only deployment
723
- npm install @yourorg/my-plugin-client
724
-
725
- # Full-stack deployment
726
- npm install @yourorg/my-plugin-backend @yourorg/my-plugin-client
727
- ```
728
-
729
- ## Examples
730
-
731
- Check out example plugins and applications:
178
+ ### 6. Sitemap Generation (`app/sitemap.ts`)
732
179
 
733
- - **Messages Plugin** - See `src/__tests__/plugins.test.ts` for a complete example
734
- - **Coming Soon**: Example applications in the `examples/` directory
180
+ ```ts
181
+ import type { MetadataRoute } from "next"
182
+ import { QueryClient } from "@tanstack/react-query"
183
+ import { getStackClient } from "@/lib/better-stack-client"
735
184
 
736
- ## API Reference
185
+ export const dynamic = "force-dynamic"
737
186
 
738
- ### Backend API
187
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
188
+ const queryClient = new QueryClient()
189
+ const stackClient = getStackClient(queryClient)
190
+ const entries = await stackClient.generateSitemap()
739
191
 
740
- ```typescript
741
- betterStack(config: BackendLibConfig): BackendLib
192
+ return entries.map((e) => ({
193
+ url: e.url,
194
+ lastModified: e.lastModified,
195
+ changeFrequency: e.changeFrequency,
196
+ priority: e.priority,
197
+ }))
198
+ }
742
199
  ```
743
200
 
744
- Creates a backend instance with plugins and returns an API handler.
201
+ **That's it.** Your blog feature is live with:
202
+ - ✅ `/blog` - Post listing page
203
+ - ✅ `/blog/[slug]` - Individual post pages
204
+ - ✅ `/blog/new` - Create post editor
205
+ - ✅ `/blog/[slug]/edit` - Edit post page
206
+ - ✅ Full CRUD API (`/api/data/blog/*`)
207
+ - ✅ Server-side rendering
208
+ - ✅ Automatic metadata generation
209
+ - ✅ Automatic sitemap generation
210
+ - ✅ React Query hooks (`usePosts`, `usePost`, etc.)
745
211
 
746
- **Parameters:**
747
- - `plugins` - Record of plugin instances
748
- - `adapter` - Database adapter function
749
- - `dbSchema` - (Optional) Additional database schema
212
+ Now add scheduling, feedback, or newsletters the same way. Each feature is independent and composable.
750
213
 
751
- **Returns:**
752
- - `handler` - Request handler for your framework
753
- - `router` - Better Call router instance
754
- - `dbSchema` - Combined database schema
214
+ ## The Bigger Picture
755
215
 
756
- ### Client API
216
+ Better Stack transforms how you think about building apps:
217
+
218
+ - **Open source** - Share complete features, not just code snippets. Someone can add a newsletter plugin to their Next.js app in minutes
219
+ - **Fast development** - Add 5 features in an afternoon instead of 5 weeks. Validate ideas faster
220
+ - **Agencies** - Create a library of reusable features. Drop scheduling into client A's app, feedback into client B's app.
221
+ - **SaaS platforms** - Offer feature plugins your customers can compose. They pick blog + scheduling + AI assistant, mix and match to build their ideal app
757
222
 
758
- ```typescript
759
- createStackClient<TPlugins>(config: ClientLibConfig<TPlugins>): ClientLib
760
- ```
761
223
 
762
- Creates a client instance with plugins and returns router and hooks.
224
+ Each plugin is a complete, self-contained horizontal full-stack feature. No framework lock-in. Just add it and it works.
763
225
 
764
- **Parameters:**
765
- - `plugins` - Record of plugin instances
766
- - `baseURL` - (Optional) Base URL for API calls
767
- - `basePath` - (Optional) API path prefix (default: "/api")
226
+ ## Learn More
768
227
 
769
- **Returns:**
770
- - `router` - Yar router instance
771
- - `hooks` - Plugin hooks organized by plugin name
228
+ For complete documentation, examples, and plugin development guides, visit **[https://www.better-stack.ai](https://www.better-stack.ai)**
772
229
 
773
- ## Contributing
230
+ ## Examples
774
231
 
775
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
232
+ - [Next.js App Router](./examples/nextjs) - Next.js App Router example
233
+ - [React Router](./examples/react-router) - React Router example
234
+ - [TanStack Router](./examples/tanstack) - TanStack Router example
776
235
 
777
236
  ## License
778
237
 
779
238
  MIT © [olliethedev](https://github.com/olliethedev)
780
-
781
- ## Support
782
-
783
- - 📖 [Documentation](https://github.com/olliethedev/better-stack)
784
- - 🐛 [Issue Tracker](https://github.com/olliethedev/better-stack/issues)
785
- - 💬 [Discussions](https://github.com/olliethedev/better-stack/discussions)
786
-
787
- ---
788
-
789
- <div align="center">
790
- <strong>Built with ❤️ using Better Stack</strong>
791
- </div>