@vexillo/react-sdk 2.0.0 → 2.0.2
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.
- package/README.md +175 -0
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @vexillo/react-sdk
|
|
2
|
+
|
|
3
|
+
React bindings for [Vexillo](https://vexillo-web.vercel.app) — a self-hosted feature flag service.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- React 19+
|
|
8
|
+
- A running Vexillo deployment
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @vexillo/react-sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
### SPA (no SSR)
|
|
19
|
+
|
|
20
|
+
Wrap your app with `<VexilloProvider>` inside a `<Suspense>` boundary. The provider fetches flags on mount and suspends the subtree until they resolve.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Suspense } from "react";
|
|
24
|
+
import { VexilloProvider, useFlag } from "@vexillo/react-sdk";
|
|
25
|
+
|
|
26
|
+
function App() {
|
|
27
|
+
return (
|
|
28
|
+
<Suspense fallback={<Spinner />}>
|
|
29
|
+
<VexilloProvider
|
|
30
|
+
baseUrl="https://your-vexillo.example.com"
|
|
31
|
+
apiKey="your-api-key"
|
|
32
|
+
fallbacks={{ "new-checkout": false }}
|
|
33
|
+
>
|
|
34
|
+
<MyApp />
|
|
35
|
+
</VexilloProvider>
|
|
36
|
+
</Suspense>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function MyApp() {
|
|
41
|
+
const newCheckout = useFlag("new-checkout");
|
|
42
|
+
return newCheckout ? <NewCheckout /> : <OldCheckout />;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### Next.js App Router (RSC)
|
|
49
|
+
|
|
50
|
+
Fetch flags in your server component and pass them as `initialFlags`. The provider renders synchronously on the server with no Suspense needed.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// app/layout.tsx
|
|
54
|
+
import { fetchFlags, VexilloProvider } from "@vexillo/react-sdk";
|
|
55
|
+
|
|
56
|
+
export default async function RootLayout({ children }) {
|
|
57
|
+
const flags = await fetchFlags(
|
|
58
|
+
process.env.VEXILLO_BASE_URL,
|
|
59
|
+
process.env.VEXILLO_API_KEY,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<html>
|
|
64
|
+
<body>
|
|
65
|
+
<VexilloProvider
|
|
66
|
+
baseUrl={process.env.VEXILLO_BASE_URL}
|
|
67
|
+
apiKey={process.env.VEXILLO_API_KEY}
|
|
68
|
+
initialFlags={flags}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</VexilloProvider>
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Client components use `useFlag` as normal:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
"use client";
|
|
82
|
+
import { useFlag } from "@vexillo/react-sdk";
|
|
83
|
+
|
|
84
|
+
export function CheckoutButton() {
|
|
85
|
+
const newCheckout = useFlag("new-checkout");
|
|
86
|
+
return newCheckout ? <NewCheckoutButton /> : <OldCheckoutButton />;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### Node.js SSR with `renderToPipeableStream`
|
|
93
|
+
|
|
94
|
+
Suspense is supported — `initialFlags` is optional. The provider suspends inline and the resolved flags are streamed to the client.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { renderToPipeableStream } from "react-dom/server";
|
|
98
|
+
import { VexilloProvider } from "@vexillo/react-sdk";
|
|
99
|
+
|
|
100
|
+
const { pipe } = renderToPipeableStream(
|
|
101
|
+
<Suspense fallback={<Spinner />}>
|
|
102
|
+
<VexilloProvider baseUrl={BASE_URL} apiKey={API_KEY}>
|
|
103
|
+
<App />
|
|
104
|
+
</VexilloProvider>
|
|
105
|
+
</Suspense>,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
pipe(res);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or pass `initialFlags` to skip the suspension entirely:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { fetchFlags, VexilloProvider } from "@vexillo/react-sdk";
|
|
115
|
+
|
|
116
|
+
const flags = await fetchFlags(BASE_URL, API_KEY);
|
|
117
|
+
|
|
118
|
+
const { pipe } = renderToPipeableStream(
|
|
119
|
+
<VexilloProvider baseUrl={BASE_URL} apiKey={API_KEY} initialFlags={flags}>
|
|
120
|
+
<App />
|
|
121
|
+
</VexilloProvider>,
|
|
122
|
+
);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### Node.js SSR with `renderToString`
|
|
128
|
+
|
|
129
|
+
> **`renderToString` does not support Suspense.** You must call `fetchFlags` before rendering and pass the result as `initialFlags`, otherwise the provider will throw.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import { renderToString } from "react-dom/server";
|
|
133
|
+
import { fetchFlags, VexilloProvider } from "@vexillo/react-sdk";
|
|
134
|
+
|
|
135
|
+
// In your request handler:
|
|
136
|
+
const flags = await fetchFlags(BASE_URL, API_KEY);
|
|
137
|
+
|
|
138
|
+
const html = renderToString(
|
|
139
|
+
<VexilloProvider baseUrl={BASE_URL} apiKey={API_KEY} initialFlags={flags}>
|
|
140
|
+
<App />
|
|
141
|
+
</VexilloProvider>,
|
|
142
|
+
);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## API
|
|
148
|
+
|
|
149
|
+
### `<VexilloProvider>`
|
|
150
|
+
|
|
151
|
+
| Prop | Type | Required | Description |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| `baseUrl` | `string` | Yes | Base URL of your Vexillo deployment |
|
|
154
|
+
| `apiKey` | `string` | Yes | SDK API key for the target environment |
|
|
155
|
+
| `initialFlags` | `Record<string, boolean>` | No* | Pre-resolved flags from the server. **Required when using `renderToString`** |
|
|
156
|
+
| `fallbacks` | `Record<string, boolean>` | No | Default values for unknown flag keys (default: `{}`) |
|
|
157
|
+
| `children` | `ReactNode` | Yes | |
|
|
158
|
+
|
|
159
|
+
### `useFlag(key: string): boolean`
|
|
160
|
+
|
|
161
|
+
Returns the current value of a feature flag. Falls back to `fallbacks[key] ?? false` for unknown keys. Must be called inside a `<VexilloProvider>`.
|
|
162
|
+
|
|
163
|
+
### `fetchFlags(baseUrl: string, apiKey: string): Promise<Record<string, boolean>>`
|
|
164
|
+
|
|
165
|
+
Fetches flags from the Vexillo API. Use this on the server to get `initialFlags`. Returns an empty object on error — never throws.
|
|
166
|
+
|
|
167
|
+
## Error handling
|
|
168
|
+
|
|
169
|
+
Fetch failures (network errors, non-2xx responses) silently resolve with an empty flag map. All `useFlag` calls fall back to `fallbacks[key] ?? false`. Feature flags will never crash your app.
|
|
170
|
+
|
|
171
|
+
## Caching
|
|
172
|
+
|
|
173
|
+
On the **client**, flags are cached in memory by `baseUrl + apiKey`. The cache is invalidated when either prop changes, triggering a new fetch and re-suspension.
|
|
174
|
+
|
|
175
|
+
On the **server**, every render fetches fresh — there is no server-side cache to prevent flag data from leaking across requests in Node.js.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vexillo/react-sdk",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
20
21
|
],
|
|
21
22
|
"peerDependencies": {
|
|
22
23
|
"react": "^19",
|