diff --git a/markdown-pages/blog/reactive-analysis.mdx b/markdown-pages/blog/reactive-analysis.mdx
new file mode 100644
index 000000000..3cff7c05f
--- /dev/null
+++ b/markdown-pages/blog/reactive-analysis.mdx
@@ -0,0 +1,130 @@
+---
+author: rescript-team
+date: "2025-12-28"
+title: "Real-Time Analysis is Coming to ReScript"
+badge: roadmap
+description: |
+ ReScript's static analyzer is going reactive. Dead code detection that updates instantly as you edit, powered by novel reactive combinators.
+---
+
+## Introduction
+
+Imagine editing a ReScript file and seeing dead code warnings update almost immediately as you work. No waiting for a full re-analysis command. Just continuous feedback about which parts of your code are actually used.
+
+This is what we're bringing to ReScript.
+
+The static analyzer that powers dead code detection is being rebuilt on a **reactive foundation**. When you add a reference to a function, the "unused" warning vanishes quickly. When you remove the last use of a module, the dead code warning appears right away. The analysis stays in sync with your code, updating in real time.
+
+## Why This Matters
+
+Traditional static analyzers work in batch mode: gather all files, analyze everything, report results. This works fine when you run analysis as a CI check or a manual command. But it's not what you want when you're actively editing code.
+
+Batch analysis has an awkward tradeoff:
+
+- **Run it rarely** and feedback comes too late to be useful
+- **Run it often** and you're constantly waiting
+
+What developers actually want is _continuous_ feedback that keeps up with their typing speed. That's exactly what reactive analysis provides.
+
+## The Reactive Approach
+
+Instead of re-analyzing your entire project on every change, the reactive analyzer represents the analysis as a **computation graph**. Each piece of data—declarations, references, liveness information—flows through this graph. When a file changes, only the affected parts of the graph recompute.
+
+The result: analysis that completes in milliseconds for typical edits, even in large codebases.
+
+### A Concrete Update Flow
+
+Suppose you have a call graph where `main` calls `helper`, and `helper` calls `format` and `validate`. In your edit, `validate` starts calling `old`.
+
+With batch analysis, the analyzer scans everything again. With reactive analysis:
+
+1. The edited `.cmt` updates — **FlatMap** extracts the new reference. The compiler already resolved it: the ref knows its target (`old`) and its source location (inside `validate`)
+2. **Join** maps that source position to its containing declaration, producing a new graph edge: _validate → old_
+3. **Fixpoint** propagates reachability from roots (`main`) along edges — `old` now becomes reachable and enters the live set
+4. A final **Classify** step (a join of declarations with the live set) marks `old` as live — so its dead-code warning disappears
+
+The rest of the project graph stays untouched.
+
+### A Glimpse Under the Hood
+
+Here is a simplified view of the reactive dead code pipeline:
+
+
+
+The analysis pipeline is built from composable operators:
+
+- **Sources** hold the raw data from your `.cmt` files
+- **FlatMap** extracts declarations, references, and annotations from each file's data
+- **Union** merges collections — for example, combining value references, type references, and exception references into a unified set
+- **Join** maps data across collections — for example, mapping each reference to the declaration that contains it, or partitioning declarations into dead and live using the fixpoint result
+- **Fixpoint** computes transitive reachability — starting from root declarations (entry points, annotated `@live`, or externally referenced), following edges to find everything that's live
+
+That last one, **fixpoint**, is particularly interesting. Dead code detection needs to know which declarations are _reachable_ from your entry points. This is a classic graph traversal—but doing it incrementally is hard. When you add one edge to a graph, how do you efficiently update the set of reachable nodes without recomputing from scratch?
+
+In the running example above, only one new edge (`validate → old`) is added, and fixpoint propagates liveness from that delta.
+
+
+
+The reactive fixpoint operator solves exactly this problem. It takes a set of roots and a set of edges, and maintains the full reachable set. In this example, once `old` becomes reachable through `validate`, fixpoint only propagates through neighbors affected by that new edge, without revisiting unrelated parts of the graph. This is what makes the analysis fast enough for real-time use.
+
+### Glitch-Free by Design
+
+There's a subtle correctness issue with reactive systems: **glitches**. If node A depends on both B and C, and both B and C update, node A might briefly see an inconsistent state—B's new value with C's old value, or vice versa.
+
+For analysis, glitches mean incorrect results. A function might briefly appear dead because the reference to it hasn't propagated yet, even though it's actually used.
+
+The reactive scheduler prevents this with a simple principle: **accumulate, then propagate**. All updates at each level of the graph are collected before any downstream nodes run. Nodes process in topological order, wave by wave. The analysis never sees partial updates.
+
+## What's Shipping
+
+The reactive analysis infrastructure is landing now. Here's what it enables:
+
+### For Developers
+
+- **Editor integration**: Available now in the latest extension, with reactive dead code updates during active development
+- **Monorepo support**: The analyzer now works correctly in monorepo setups, running from the workspace root with proper binary lookup
+- **Faster CI analysis**: Incremental runs are dramatically faster than full runs
+- **Immediate feedback loop**: See the impact of your changes instantly
+
+### For Tooling Authors
+
+The reactive primitives are general-purpose. The same infrastructure that powers dead code detection can express other analyses:
+
+- Dependency graph visualization
+- Unused export detection
+- Reference counting and hotspot identification
+- Custom project-specific checks
+
+## The Road Ahead
+
+This is the beginning of a larger shift. The same reactive foundation will extend to other parts of the editor experience:
+
+- **Type checking**: Incremental type feedback without waiting for builds
+- **Navigation**: Jump-to-definition that stays accurate as files change
+- **Refactoring**: Real-time previews of rename and move operations
+
+The goal is an editor experience where the tooling _keeps up with you_—no waiting, no stale results, just continuous assistance.
+
+## Try It Today
+
+**Requirements:**
+
+Reactive analysis is a newer capability of Editor Code Analysis and requires a newer extension version.
+
+- ReScript compiler >= 12.1.0
+- VSCode or Cursor with rescript-vscode extension >= 1.73.9 (pre-release)
+
+1. Start the build watcher (accept the prompt when opening a ReScript file, or run **"ReScript: Start Build"**)
+2. Run **"ReScript: Start Code Analyzer"**
+
+Dead code warnings appear in your editor and update reactively while you develop (currently driven by save/build updates). For configuration options and usage details, see the [Dead Code Analysis guide](../docs/manual/editor-code-analysis.mdx).
+
+## Acknowledgments
+
+The reactive primitives are based on work by [SkipLabs](https://skiplabs.io). Their reactive collections library provides the foundation for glitch-free, incremental computation that makes real-time analysis possible.
+
+---
+
+We're excited to bring this to the ReScript community. Static analysis that runs continuously, in the background, without you having to think about it—that's the experience we're building toward.
+
+Stay tuned for broader reactive tooling updates. And as always, we welcome feedback on [GitHub](https://github.com/rescript-lang/rescript) and on the [forum](https://forum.rescript-lang.org).
diff --git a/markdown-pages/docs/manual/editor-code-analysis.mdx b/markdown-pages/docs/manual/editor-code-analysis.mdx
index c4d18d059..9a1a5bca4 100644
--- a/markdown-pages/docs/manual/editor-code-analysis.mdx
+++ b/markdown-pages/docs/manual/editor-code-analysis.mdx
@@ -37,6 +37,10 @@ ReScript’s language design allows for accurate and efficient dead code analysi
- The “Problems” pane populates with dead code warnings and suggestions.
+### Reactive Updates (New)
+
+Reactive dead code updates are a newer enhancement of Editor Code Analysis and require ReScript VSCode extension v1.73.9 or higher (pre-release).
+
## Real-World Use Cases
### 1. **Unused Record Fields**
diff --git a/markdown-pages/docs/manual/editor-plugins.mdx b/markdown-pages/docs/manual/editor-plugins.mdx
index d6593695d..97c5bb884 100644
--- a/markdown-pages/docs/manual/editor-plugins.mdx
+++ b/markdown-pages/docs/manual/editor-plugins.mdx
@@ -120,7 +120,7 @@ Look - [Editor Code Analysis](./editor-code-analysis.mdx) for a more detailed gu
### Caveats
-- Doesn't support cross-package dead code analysis in monorepos. Run it per package instead.
+- For older extension versions, cross-package dead code analysis in monorepos may be limited.
## Editor features
diff --git a/public/blog/reactive-analysis/fixpoint.mmd b/public/blog/reactive-analysis/fixpoint.mmd
new file mode 100644
index 000000000..8387dcd08
--- /dev/null
+++ b/public/blog/reactive-analysis/fixpoint.mmd
@@ -0,0 +1,20 @@
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#e8f5e9', 'primaryTextColor': '#102a43', 'primaryBorderColor': '#4caf50', 'lineColor': '#486581'}}}%%
+flowchart LR
+ A_main["main root"]
+ A_helper["helper"]
+ A_format["format"]
+ A_validate["validate"]
+ A_old["old"]
+
+ A_main -- "wave 1" --> A_helper
+ A_helper -- "wave 2" --> A_format
+ A_helper -- "wave 2" --> A_validate
+ A_validate -. "new edge: validate → old" .-> A_old
+
+ classDef rootNode fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px,color:#102a43
+ classDef liveNode fill:#e8f5e9,stroke:#4caf50,stroke-width:2px,color:#102a43
+ classDef newlyLive fill:#fff9c4,stroke:#f9a825,stroke-width:2.5px,color:#102a43
+
+ class A_main rootNode
+ class A_helper,A_format,A_validate liveNode
+ class A_old newlyLive
diff --git a/public/blog/reactive-analysis/fixpoint.svg b/public/blog/reactive-analysis/fixpoint.svg
new file mode 100644
index 000000000..bdb3e9225
--- /dev/null
+++ b/public/blog/reactive-analysis/fixpoint.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/blog/reactive-analysis/reactive-pipeline-simple.mmd b/public/blog/reactive-analysis/reactive-pipeline-simple.mmd
new file mode 100644
index 000000000..2a2db3299
--- /dev/null
+++ b/public/blog/reactive-analysis/reactive-pipeline-simple.mmd
@@ -0,0 +1,24 @@
+%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#eef6ff', 'primaryTextColor': '#102a43', 'primaryBorderColor': '#4a90d9', 'lineColor': '#486581', 'secondaryColor': '#fff1f1', 'tertiaryColor': '#fff5e6'}}}%%
+flowchart LR
+ sources["Sources"]
+ flatmap["FlatMap"]
+ edges["Edges"]
+ roots["Roots"]
+ fixpoint["Fixpoint"]
+ classify["Classify"]
+ issues["Issues"]
+
+ sources --> flatmap
+ flatmap -- "refs, decls" --> edges
+ flatmap -- "refs, decls, ann." --> roots
+ edges --> fixpoint
+ roots --> fixpoint
+ flatmap -. "decls" .-> classify
+ fixpoint -- "live" --> classify
+ classify -- "dead" --> issues
+
+ classDef coreBox fill:#eef6ff,stroke:#4a90d9,stroke-width:2px,color:#102a43
+ classDef solveBox fill:#fff1f1,stroke:#cc6666,stroke-width:2px,color:#102a43
+
+ class sources,flatmap,edges,roots coreBox
+ class fixpoint,classify,issues solveBox
diff --git a/public/blog/reactive-analysis/reactive-pipeline-simple.svg b/public/blog/reactive-analysis/reactive-pipeline-simple.svg
new file mode 100644
index 000000000..f66bd8104
--- /dev/null
+++ b/public/blog/reactive-analysis/reactive-pipeline-simple.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/scripts/test-hrefs.mjs b/scripts/test-hrefs.mjs
index 285fc920a..4cab81c03 100644
--- a/scripts/test-hrefs.mjs
+++ b/scripts/test-hrefs.mjs
@@ -26,8 +26,27 @@ for (const file of files) {
const warningMessage = log.replace(file, "");
+ // Skip warnings about files that exist in public/ (served at root by Vite)
+ const missingFileMatches = [
+ ...warningMessage.matchAll(/`\.\.\/\.\.\/(.*?)`/g),
+ ];
+ let allMissingExistInPublic = false;
+ if (missingFileMatches.length > 0) {
+ allMissingExistInPublic = (
+ await Promise.all(
+ missingFileMatches.map(([, p]) =>
+ fs.access("public/" + p).then(
+ () => true,
+ () => false,
+ ),
+ ),
+ )
+ ).every(Boolean);
+ }
+
if (
log &&
+ !allMissingExistInPublic &&
!warningMessage.includes("api/") &&
// When running on CI it fails to ignore the link directly to the blog root
// https://github.com/rescript-lang/rescript-lang.org/actions/runs/19520461368/job/55882556586?pr=1115#step:6:338