diff --git a/src/content/docs/blog/openfeature-react-migration.md b/src/content/docs/blog/openfeature-react-migration.md
new file mode 100644
index 0000000..1bb45da
--- /dev/null
+++ b/src/content/docs/blog/openfeature-react-migration.md
@@ -0,0 +1,173 @@
+---
+title: "OpenFeature React: Audit and Migrate Your LaunchDarkly React App"
+description: "Migrate a React app from the LaunchDarkly SDK to OpenFeature. FlagLint audits flag debt, maps every flag key and call type, and builds your migration plan."
+date: 2026-08-20
+---
+
+# OpenFeature React: Audit and Migrate Your LaunchDarkly React App
+
+Your React app is probably riddled with `ldClient.variation()` calls and `useFlags()` hooks spread across 30–50 components. They work, they've always worked, and nobody remembers which flags are still live. Then LaunchDarkly's bill arrives — or your platform team announces an OpenFeature mandate — and you realise you have no idea how many flag keys exist across your component tree.
+
+That's the core problem with LaunchDarkly SDK usage in long-lived React codebases: the calls accumulate invisibly. You can't migrate what you haven't measured.
+
+This guide walks through auditing a React project with FlagLint, reading the results, and doing the actual LaunchDarkly SDK → OpenFeature React swap, component by component.
+
+## Why flag debt builds up faster in React
+
+In a Node.js service, flag evaluations tend to live close together — middleware, handlers, config loaders. In React, they scatter. A flag that controls a header renders in `AppShell.tsx`. The same flag shows up in `MobileNav.tsx`, `DesktopNav.tsx`, and `NavAnalytics.tsx`. One flag key, four call sites, none of them talking to each other.
+
+Three years in, you have 80 flags in LaunchDarkly, 140 call sites in React, and no map. Flags that shipped are still evaluated on every render. That's flag debt — stale signals running up cost and making every component harder to understand.
+
+The migration plan cannot start with "pick a component and swap the hook." It has to start with: what flag keys exist, what call type each uses, which ones have a high staleness signal, and what the current readiness score is. FlagLint produces that map.
+
+## Step 1: Run the FlagLint audit
+
+FlagLint targets TypeScript and JavaScript source files. Running it against a directory that contains only documentation shows the baseline case:
+
+```bash
+npx flaglint@latest audit ./src/content/docs/
+```
+
+Output:
+
+```
+- Auditing ./src/content/docs/...
+No matching files found. Check your .flaglintrc include patterns.
+```
+
+Correct — that directory holds only Markdown. For a React project, point the audit at `src/`:
+
+```bash
+npx flaglint@latest audit ./src/
+```
+
+FlagLint walks every `.ts` and `.tsx` file, extracts every flag key, classifies each call type, assigns staleness signals, and prints the overall readiness score.
+
+## Step 2: Configure .flaglintrc for React
+
+Out of the box, FlagLint looks for calls from `launchdarkly-node-server-sdk`. To catch React-specific patterns — the `useFlags()` hook, the `withLDConsumer()` HOC, and the `useLDClient()` hook — add a `.flaglintrc` at the repo root:
+
+```json
+{
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "exclude": ["src/**/*.test.tsx", "src/**/*.spec.ts"],
+ "sdks": ["launchdarkly-js-client-sdk", "launchdarkly-react-client-sdk"]
+}
+```
+
+The `launchdarkly-react-client-sdk` entry tells FlagLint to recognise the React SDK's call types. Without it, hook-based flag evaluations are invisible to the audit and won't appear in the flag debt count or the readiness score.
+
+Re-run after saving `.flaglintrc` and you'll see each flag key, its call type, and whether the staleness signal is high, medium, or low.
+
+## Step 3: Understand the readiness score
+
+The readiness score runs from 0 to 100. Zero means every flag evaluation in your codebase goes through the LaunchDarkly SDK. One hundred means every evaluation goes through an OpenFeature provider.
+
+FlagLint computes it by comparing LaunchDarkly SDK call sites to total flag call sites. A repo at score 40 has moved 40% of its flag evaluations to OpenFeature. The remaining 60% still carry LaunchDarkly SDK imports.
+
+The readiness score tells you at a glance how far through the migration plan you are, and gives you a concrete number to track PR-by-PR. See [LaunchDarkly Flag Debt: Audit, Estimate, and Prioritize Your Migration](/blog/launchdarkly-flag-debt) for a deeper breakdown of how to read the score alongside flag debt estimates.
+
+## Step 4: Before and after — LaunchDarkly SDK to OpenFeature React
+
+### Hooks (the common case)
+
+**Before — LaunchDarkly React SDK:**
+
+```tsx
+import { useFlags } from 'launchdarkly-react-client-sdk';
+
+function BillingPage() {
+ const { billingV2Rollout } = useFlags();
+ return billingV2Rollout ? : ;
+}
+```
+
+**After — OpenFeature React:**
+
+```tsx
+import { useBooleanFlagValue } from '@openfeature/react-sdk';
+
+function BillingPage() {
+ const billingV2Rollout = useBooleanFlagValue('billing-v2-rollout', false);
+ return billingV2Rollout ? : ;
+}
+```
+
+Two things change. First, the import: `launchdarkly-react-client-sdk` → `@openfeature/react-sdk`. Second, the call type: `useFlags()` returns all flags at once as a camelCase object, so you destructure `billingV2Rollout`. The OpenFeature `useBooleanFlagValue()` evaluates a single flag key and takes the raw kebab-case key `'billing-v2-rollout'` as a string.
+
+FlagLint's audit marks the camelCase-to-kebab-case normalisation as a staleness signal to watch for. If a flag key was committed as `billingV2Rollout` in one component and `billing-v2-rollout` in another, the audit surfaces both call sites so you can consolidate before migrating.
+
+### HOC pattern
+
+Older React codebases use `withLDConsumer` instead of hooks:
+
+**Before:**
+
+```tsx
+import { withLDConsumer } from 'launchdarkly-react-client-sdk';
+
+function FeatureNav({ flags }) {
+ return flags.showNewNav ? : ;
+}
+
+export default withLDConsumer()(FeatureNav);
+```
+
+**After:**
+
+```tsx
+import { useBooleanFlagValue } from '@openfeature/react-sdk';
+
+function FeatureNav() {
+ const showNewNav = useBooleanFlagValue('show-new-nav', false);
+ return showNewNav ? : ;
+}
+
+export default FeatureNav;
+```
+
+The HOC disappears entirely. OpenFeature React hooks pull from context without a wrapper. FlagLint classifies `withLDConsumer` wraps as their own call type during the audit, so the migration plan counts them separately from hook-based evaluations.
+
+### Wiring the OpenFeature provider at app root
+
+Before any hook resolves, set the OpenFeature provider once at the top of your tree. The LaunchDarkly OpenFeature provider is the easiest starting point — it keeps your existing flags working during migration:
+
+```tsx
+import { OpenFeatureProvider, OpenFeature } from '@openfeature/react-sdk';
+import { LaunchDarklyClientProvider } from '@launchdarkly/openfeature-js-client-sdk';
+
+const ldClient = LaunchDarklyClientProvider.createClient('your-client-side-id');
+OpenFeature.setProvider(new LaunchDarklyClientProvider(ldClient));
+
+function App() {
+ return (
+
+
+
+ );
+}
+```
+
+Once this is in place, every component that calls `useBooleanFlagValue()` evaluates against the LaunchDarkly OpenFeature provider automatically. You can swap the provider to Flipt, Unleash, or any other OpenFeature-compatible system later without touching component code. That's the architectural value: the migration plan stops being locked to one vendor.
+
+## Step 5: Clear stale flags before migrating
+
+Not every flag needs an OpenFeature equivalent. Some flags are done — the rollout shipped, the old branch is deleted, but the LaunchDarkly SDK call is still in the component tree because removing it never made the sprint.
+
+FlagLint assigns a staleness signal based on source history and call frequency. A flag key with a high staleness signal, a single call site, and a boolean call type is a strong candidate for outright deletion: remove the LaunchDarkly SDK call, remove the conditional branch, archive the flag in LaunchDarkly. That drops flag debt and improves the readiness score without writing any OpenFeature code.
+
+Kill high-staleness-signal flags first. Then migrate the rest. The [LaunchDarkly Feature Flag Cleanup guide](/blog/launchdarkly-feature-flag-cleanup-typescript) covers the removal workflow in detail for TypeScript codebases.
+
+## Step 6: Enforce in CI
+
+Once you start migrating, new LaunchDarkly SDK calls introduced in PRs will quietly undo your progress. FlagLint has a CI mode that fails the build when the readiness score drops below a threshold you set. Wire it into your pipeline so that every PR that adds a new LaunchDarkly SDK call type is blocked until the author migrates it to an OpenFeature React hook instead. See [Enforcing Your LaunchDarkly to OpenFeature Migration in GitHub Actions](/blog/enforce-launchdarkly-migration-github-actions) for the exact workflow and YAML.
+
+## Next steps
+
+1. **Run `npx flaglint@latest audit ./src/`** against your actual React source. Get the real flag key count and readiness score before estimating effort.
+2. **Add `.flaglintrc`** with `launchdarkly-react-client-sdk` in the `sdks` list so hooks and HOCs appear in the flag debt count.
+3. **Delete high-staleness-signal flags first.** Reducing the LaunchDarkly SDK call count before migrating keeps each PR focused.
+4. **Wire the OpenFeature provider at app root.** Flags keep working via the LaunchDarkly OpenFeature provider while you migrate component by component.
+5. **Track the readiness score per PR.** FlagLint in CI gives you a number that goes up. When it hits 100, the LaunchDarkly SDK import can come out of `package.json`.
+
+OpenFeature React is a hook-for-hook replacement at the component level. The migration plan is mechanical once you have the audit. FlagLint gives you that map.