diff --git a/skills/wp-interactivity-api/SKILL.md b/skills/wp-interactivity-api/SKILL.md
index 6f3072d..84a0736 100644
--- a/skills/wp-interactivity-api/SKILL.md
+++ b/skills/wp-interactivity-api/SKILL.md
@@ -13,7 +13,8 @@ Use this skill when the user mentions:
- Interactivity API, `@wordpress/interactivity`,
- `data-wp-interactive`, `data-wp-on--*`, `data-wp-bind--*`, `data-wp-context`,
- block `viewScriptModule` / module-based view scripts,
-- hydration issues or “directives don’t fire”.
+- hydration issues or "directives don't fire",
+- **client-side navigation**, `@wordpress/interactivity-router`, `data-wp-router-region`.
## Inputs required
@@ -134,16 +135,55 @@ Verify the repo supports the required module build path:
- if it uses `@wordpress/scripts`, prefer its conventions.
- if it uses custom bundling, confirm module output is supported.
-### 6) Debug common failure modes
+### 6) Client-side navigation with the Router
-If “nothing happens” on interaction:
+**CRITICAL: The router automatically intercepts `` links. Do NOT add custom click handlers.**
+
+When using `@wordpress/interactivity-router` for SPA-like navigation:
+
+1. Enqueue the router: `wp_enqueue_script_module('my-nav', '...', ['@wordpress/interactivity', '@wordpress/interactivity-router'])`
+2. **Mark module as compatible (WordPress 6.9+ REQUIRED):**
+ ```php
+ wp_interactivity()->add_client_navigation_support_to_script_module('my-nav');
+ ```
+3. Add `data-wp-router-region="region-id"` to the content area to be swapped
+4. Use **plain `` links** - NO `data-wp-on--click` handlers!
+
+The router automatically:
+- Intercepts all same-origin link clicks
+- Fetches the target page
+- Swaps ONLY the matching router region content
+- Updates browser URL via History API
+
+**WordPress 6.9+ CRITICAL:** Script modules must be marked as compatible with client-side navigation:
+- For blocks: use `"supports": { "interactivity": { "clientNavigation": true } }` in block.json
+- For manual modules: call `wp_interactivity()->add_client_navigation_support_to_script_module('module-id')`
+
+Without this, the router will NOT intercept link clicks. The script tag must have `data-wp-router-options` attribute.
+
+Reference: https://make.wordpress.org/core/2025/11/12/interactivity-apis-client-navigation-improvements-in-wordpress-6-9/
+
+**Common mistake:** Adding `data-wp-on--click="actions.navigate"` to nav elements. This breaks the router. Remove it.
+
+See `references/router.md` for full details.
+
+### 7) Debug common failure modes
+
+If "nothing happens" on interaction:
- confirm the `viewScriptModule` is enqueued/loaded,
- confirm the DOM element has `data-wp-interactive`,
-- confirm the store namespace matches the directive’s value,
+- confirm the store namespace matches the directive's value,
- confirm there are no JS errors before hydration.
-See `references/debugging.md`.
+If client-side **navigation** isn't working:
+- confirm `@wordpress/interactivity-router` is enqueued,
+- confirm `data-wp-router-region` exists on both source and target pages with SAME ID,
+- **confirm script module has `data-wp-router-options` attribute** (WP 6.9+) - if missing, call `wp_interactivity()->add_client_navigation_support_to_script_module('your-module')`,
+- **remove any custom `data-wp-on--click` handlers from navigation links**,
+- links should be plain `` tags.
+
+See `references/debugging.md` and `references/router.md`.
## Verification
@@ -174,6 +214,7 @@ See `references/debugging.md`.
- If repo build constraints are unclear, ask: "Is this using `@wordpress/scripts` or a custom bundler (webpack/vite)?"
- Consult:
+ - `references/router.md` - **READ THIS FIRST for navigation issues**
- `references/server-side-rendering.md`
- `references/directives-quickref.md`
- `references/debugging.md`
diff --git a/skills/wp-interactivity-api/references/router.md b/skills/wp-interactivity-api/references/router.md
new file mode 100644
index 0000000..cbff6eb
--- /dev/null
+++ b/skills/wp-interactivity-api/references/router.md
@@ -0,0 +1,152 @@
+# Interactivity Router (`@wordpress/interactivity-router`)
+
+## Key Concept: Automatic Link Interception
+
+**The router automatically intercepts ALL same-origin `` link clicks.** You do NOT need custom click handlers.
+
+## How It Works
+
+When `@wordpress/interactivity-router` is enqueued and `data-wp-router-region` exists on the page:
+
+1. User clicks any `` link (same-origin)
+2. Router intercepts the click automatically (calls `preventDefault`)
+3. Fetches the target page via AJAX
+4. Finds the matching `data-wp-router-region` on the new page
+5. Swaps ONLY that region's innerHTML
+6. Updates the browser URL via History API
+7. Sidebar, header, footer stay intact
+
+## Required Setup
+
+```php
+// 1. Enqueue the router module
+wp_enqueue_script_module(
+ 'my-navigation',
+ get_stylesheet_directory_uri() . '/assets/js/navigation.js',
+ ['@wordpress/interactivity', '@wordpress/interactivity-router'],
+ '1.0.0'
+);
+
+// 2. Mark module as compatible with client-side navigation (WordPress 6.9+)
+// This is REQUIRED for manually registered script modules!
+wp_interactivity()->add_client_navigation_support_to_script_module('my-navigation');
+```
+
+```php
+// 3. Add router region to the content area (via filter or directly in template)
+
+
+
+```
+
+```html
+
+
+```
+
+## WordPress 6.9+ Requirements
+
+**CRITICAL: Script modules must be marked as compatible with client-side navigation.**
+
+For blocks, this is automatic when `block.json` has:
+```json
+{
+ "supports": {
+ "interactivity": { "clientNavigation": true }
+ }
+}
+```
+
+For manually registered script modules (themes/plugins), you MUST call:
+```php
+wp_interactivity()->add_client_navigation_support_to_script_module('my-module');
+```
+
+This adds `data-wp-router-options='{"loadOnClientNavigation":true}'` to the script tag.
+
+**Without this, the router will not intercept link clicks.**
+
+Reference: https://make.wordpress.org/core/2025/11/12/interactivity-apis-client-navigation-improvements-in-wordpress-6-9/
+
+## Common Mistakes
+
+### ❌ WRONG: Adding click handlers to links
+
+```html
+
+
+```
+
+```javascript
+// DON'T DO THIS
+store('my/nav', {
+ actions: {
+ navigate(e) {
+ e.preventDefault();
+ routerActions.navigate(e.target.href);
+ }
+ }
+});
+```
+
+### ✅ CORRECT: Plain links, router does the work
+
+```html
+
+
+```
+
+```javascript
+// Just register an empty store if needed, or don't register one at all
+import { store } from '@wordpress/interactivity';
+store('my/namespace', {});
+```
+
+## When You DO Need Custom Handlers
+
+Only use custom navigation handlers for:
+
+- Non-link elements (buttons, divs) that should navigate
+- Conditional navigation (confirm dialogs, form validation)
+- Links that need special processing before navigation
+
+```javascript
+// Example: Button that navigates
+store('my/namespace', {
+ actions: {
+ *navigateToProfile() {
+ const { actions } = yield import('@wordpress/interactivity-router');
+ yield actions.navigate('/profile/');
+ }
+ }
+});
+```
+
+## Debugging
+
+If navigation isn't working:
+
+1. **Check router is enqueued**: View page source, search for `interactivity-router`
+2. **Check region exists**: Search HTML for `data-wp-router-region`
+3. **Check target page has same region ID**: Both pages need matching region IDs
+4. **Check for custom handlers**: Remove any `data-wp-on--click` from nav elements
+5. **Check console errors**: Router logs navigation events
+6. **Check script module has router options (WP 6.9+)**: Your script tag should have `data-wp-router-options` attribute. If missing, call `wp_interactivity()->add_client_navigation_support_to_script_module('your-module')`
+
+## Multiple Router Regions
+
+You can have multiple independent regions:
+
+```html
+
+...
+```
+
+Each region swaps independently based on the target page's matching regions.