diff --git a/deno.json b/deno.json
index 18ff152..671586b 100644
--- a/deno.json
+++ b/deno.json
@@ -153,7 +153,7 @@
},
"imports": {
"react": "npm:react@^19.2.4",
- "@types/react": "npm:@types/react@^19.2.9"
+ "@types/react": "npm:@types/react@^19.2.13"
},
"compilerOptions": {
"lib": [
diff --git a/deno.lock b/deno.lock
index 147e117..db7607b 100644
--- a/deno.lock
+++ b/deno.lock
@@ -3,11 +3,14 @@
"specifiers": {
"jsr:@deno/esbuild-plugin@^1.2.1": "1.2.1",
"jsr:@deno/loader@~0.3.10": "0.3.11",
- "jsr:@std/assert@^1.0.17": "1.0.17",
+ "jsr:@std/assert@^1.0.17": "1.0.18",
+ "jsr:@std/assert@^1.0.18": "1.0.18",
"jsr:@std/async@^1.1.0": "1.1.0",
+ "jsr:@std/async@^1.1.1": "1.1.1",
"jsr:@std/bytes@^1.0.6": "1.0.6",
"jsr:@std/cli@^1.0.26": "1.0.26",
"jsr:@std/collections@^1.1.4": "1.1.4",
+ "jsr:@std/collections@^1.1.5": "1.1.5",
"jsr:@std/crypto@^1.0.5": "1.0.5",
"jsr:@std/data-structures@^1.0.10": "1.0.10",
"jsr:@std/encoding@^1.0.10": "1.0.10",
@@ -23,24 +26,27 @@
"jsr:@udibo/esbuild-plugin-postcss@0.3": "0.3.0",
"jsr:@udibo/esbuild-plugin-postcss@0.3.0": "0.3.0",
"jsr:@udibo/http-error@~0.11.1": "0.11.1",
+ "jsr:@udibo/juniper@~0.3.2": "0.3.2",
"npm:@babel/core@^7.28.6": "7.28.6",
- "npm:@modelcontextprotocol/sdk@^1.25.3": "1.25.3_zod@4.3.6_hono@4.11.6_ajv@8.17.1_express@5.2.1",
+ "npm:@modelcontextprotocol/sdk@^1.25.3": "1.25.3_zod@4.3.6_hono@4.11.9_ajv@8.17.1_express@5.2.1",
"npm:@opentelemetry/api@^1.9.0": "1.9.0",
"npm:@tailwindcss/postcss@^4.1.18": "4.1.18",
"npm:@tanstack/react-query@^5.90.20": "5.90.20_react@19.2.4",
- "npm:@testing-library/react@^16.3.2": "16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.9_react@19.2.4_react-dom@19.2.4__react@19.2.4",
+ "npm:@testing-library/react@^16.3.2": "16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.13_react@19.2.4_react-dom@19.2.4__react@19.2.4",
"npm:@testing-library/user-event@^14.6.1": "14.6.1_@testing-library+dom@10.4.1",
- "npm:@types/react@^19.2.9": "19.2.9",
+ "npm:@types/react@^19.2.13": "19.2.13",
+ "npm:@types/react@^19.2.9": "19.2.13",
"npm:babel-plugin-react-compiler@1": "1.0.0",
"npm:cbor2@^2.2.1": "2.2.1",
"npm:esbuild@~0.25.12": "0.25.12",
"npm:esbuild@~0.25.5": "0.25.12",
"npm:esbuild@~0.27.2": "0.27.2",
"npm:global-jsdom@27": "27.0.0_jsdom@27.4.0",
- "npm:hono@^4.11.6": "4.11.6",
+ "npm:hono@^4.11.6": "4.11.9",
+ "npm:hono@^4.11.9": "4.11.9",
"npm:isbot@^5.1.34": "5.1.34",
"npm:less@^4.4.2": "4.5.1",
- "npm:playwright@^1.58.0": "1.58.0",
+ "npm:playwright@^1.58.2": "1.58.2",
"npm:postcss-modules@^6.0.1": "6.0.1_postcss@8.5.6",
"npm:postcss@^8.5.6": "8.5.6",
"npm:quick-lru@^7.3.0": "7.3.0",
@@ -71,9 +77,18 @@
"jsr:@std/internal"
]
},
+ "@std/assert@1.0.18": {
+ "integrity": "270245e9c2c13b446286de475131dc688ca9abcd94fc5db41d43a219b34d1c78",
+ "dependencies": [
+ "jsr:@std/internal"
+ ]
+ },
"@std/async@1.1.0": {
"integrity": "72418df08d1be84668a53e48aab3520d68ae6882182f8a5ca75c6d1f087220d1"
},
+ "@std/async@1.1.1": {
+ "integrity": "8a79beb3378cc229ce65ba2c746cfd03e4855ddd891d1eb6b9e32128e0d5339c"
+ },
"@std/bytes@1.0.6": {
"integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a"
},
@@ -86,6 +101,9 @@
"@std/collections@1.1.4": {
"integrity": "90a7acae07652b0f7227db9b22116ddf4b7cfb0886f8e409c1d7f0c39c526bc1"
},
+ "@std/collections@1.1.5": {
+ "integrity": "c37cc6bbbbf90fec3e782535a46c9c8a3210f19118c61add71fcea86b0ffd491"
+ },
"@std/crypto@1.0.5": {
"integrity": "0dcfbb319fe0bba1bd3af904ceb4f948cde1b92979ec1614528380ed308a3b40"
},
@@ -123,8 +141,8 @@
"@std/testing@1.0.17": {
"integrity": "87bdc2700fa98249d48a17cd72413352d3d3680dcfbdb64947fd0982d6bbf681",
"dependencies": [
- "jsr:@std/assert",
- "jsr:@std/async",
+ "jsr:@std/assert@^1.0.17",
+ "jsr:@std/async@^1.1.0",
"jsr:@std/data-structures",
"jsr:@std/fs",
"jsr:@std/internal",
@@ -155,6 +173,32 @@
"dependencies": [
"jsr:@std/http"
]
+ },
+ "@udibo/juniper@0.3.2": {
+ "integrity": "e1bf0c780130995acc740cf2aa65157f5f2a3979cd1d0c23432cecb9b29bbdaf",
+ "dependencies": [
+ "jsr:@deno/esbuild-plugin",
+ "jsr:@std/async@^1.1.0",
+ "jsr:@std/cli",
+ "jsr:@std/collections@^1.1.4",
+ "jsr:@std/fs",
+ "jsr:@std/path@^1.1.4",
+ "jsr:@std/streams",
+ "jsr:@std/testing",
+ "jsr:@udibo/http-error",
+ "npm:@babel/core",
+ "npm:@opentelemetry/api",
+ "npm:babel-plugin-react-compiler",
+ "npm:cbor2",
+ "npm:esbuild@~0.27.2",
+ "npm:global-jsdom",
+ "npm:hono@^4.11.6",
+ "npm:isbot",
+ "npm:quick-lru",
+ "npm:react",
+ "npm:react-dom",
+ "npm:react-router"
+ ]
}
},
"npm": {
@@ -610,7 +654,7 @@
"@exodus/bytes@1.10.0": {
"integrity": "sha512-tf8YdcbirXdPnJ+Nd4UN1EXnz+IP2DI45YVEr3vvzcVTOyrApkmIB4zvOQVd3XPr7RXnfBtAx+PXImXOIU0Ajg=="
},
- "@hono/node-server@1.19.9_hono@4.11.6": {
+ "@hono/node-server@1.19.9_hono@4.11.9": {
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
"dependencies": [
"hono"
@@ -654,7 +698,7 @@
"@jridgewell/sourcemap-codec"
]
},
- "@modelcontextprotocol/sdk@1.25.3_zod@4.3.6_hono@4.11.6_ajv@8.17.1_express@5.2.1": {
+ "@modelcontextprotocol/sdk@1.25.3_zod@4.3.6_hono@4.11.9_ajv@8.17.1_express@5.2.1": {
"integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==",
"dependencies": [
"@hono/node-server",
@@ -892,7 +936,7 @@
"pretty-format"
]
},
- "@testing-library/react@16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.9_react@19.2.4_react-dom@19.2.4__react@19.2.4": {
+ "@testing-library/react@16.3.2_@testing-library+dom@10.4.1_@types+react@19.2.13_react@19.2.4_react-dom@19.2.4__react@19.2.4": {
"integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
"dependencies": [
"@babel/runtime",
@@ -914,8 +958,8 @@
"@types/aria-query@5.0.4": {
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="
},
- "@types/react@19.2.9": {
- "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
+ "@types/react@19.2.13": {
+ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==",
"dependencies": [
"csstype"
]
@@ -1410,6 +1454,7 @@
"package-json-from-dist",
"path-scurry"
],
+ "deprecated": true,
"bin": true
},
"global-jsdom@27.0.0_jsdom@27.4.0": {
@@ -1433,8 +1478,8 @@
"function-bind"
]
},
- "hono@4.11.6": {
- "integrity": "sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw=="
+ "hono@4.11.9": {
+ "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="
},
"html-encoding-sniffer@6.0.0": {
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
@@ -1826,12 +1871,12 @@
"pkce-challenge@5.0.1": {
"integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="
},
- "playwright-core@1.58.0": {
- "integrity": "sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==",
+ "playwright-core@1.58.2": {
+ "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"bin": true
},
- "playwright@1.58.0": {
- "integrity": "sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==",
+ "playwright@1.58.2": {
+ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"dependencies": [
"playwright-core"
],
@@ -2293,29 +2338,29 @@
},
"workspace": {
"dependencies": [
- "npm:@types/react@^19.2.9",
+ "npm:@types/react@^19.2.13",
"npm:react@^19.2.4"
],
"members": {
"example": {
"dependencies": [
- "jsr:@std/assert@^1.0.17",
- "jsr:@std/async@^1.1.0",
- "jsr:@std/collections@^1.1.4",
+ "jsr:@std/assert@^1.0.18",
+ "jsr:@std/async@^1.1.1",
+ "jsr:@std/collections@^1.1.5",
"jsr:@std/encoding@^1.0.10",
"jsr:@std/path@^1.1.4",
"jsr:@std/streams@^1.0.17",
"jsr:@std/testing@^1.0.17",
"jsr:@std/uuid@^1.1.0",
"jsr:@udibo/esbuild-plugin-postcss@0.3",
- "jsr:@udibo/juniper@~0.3.1",
+ "jsr:@udibo/juniper@~0.3.2",
"npm:@opentelemetry/api@^1.9.0",
"npm:@tailwindcss/postcss@^4.1.18",
"npm:@testing-library/react@^16.3.2",
"npm:@testing-library/user-event@^14.6.1",
- "npm:@types/react@^19.2.9",
- "npm:hono@^4.11.6",
- "npm:playwright@^1.58.0",
+ "npm:@types/react@^19.2.13",
+ "npm:hono@^4.11.9",
+ "npm:playwright@^1.58.2",
"npm:react-router@^7.13.0",
"npm:react@^19.2.4",
"npm:tailwindcss@^4.1.18",
@@ -2354,32 +2399,32 @@
},
"templates/minimal": {
"dependencies": [
- "jsr:@std/assert@^1.0.17",
+ "jsr:@std/assert@^1.0.18",
"jsr:@std/path@^1.1.4",
"jsr:@std/streams@^1.0.17",
"jsr:@std/testing@^1.0.17",
- "jsr:@udibo/juniper@~0.3.1",
+ "jsr:@udibo/juniper@~0.3.2",
"npm:@opentelemetry/api@^1.9.0",
"npm:@testing-library/react@^16.3.2",
- "npm:@types/react@^19.2.9",
- "npm:hono@^4.11.6",
+ "npm:@types/react@^19.2.13",
+ "npm:hono@^4.11.9",
"npm:react-router@^7.13.0",
"npm:react@^19.2.4"
]
},
"templates/tailwindcss": {
"dependencies": [
- "jsr:@std/assert@^1.0.17",
+ "jsr:@std/assert@^1.0.18",
"jsr:@std/path@^1.1.4",
"jsr:@std/streams@^1.0.17",
"jsr:@std/testing@^1.0.17",
"jsr:@udibo/esbuild-plugin-postcss@0.3.0",
- "jsr:@udibo/juniper@~0.3.1",
+ "jsr:@udibo/juniper@~0.3.2",
"npm:@opentelemetry/api@^1.9.0",
"npm:@tailwindcss/postcss@^4.1.18",
"npm:@testing-library/react@^16.3.2",
- "npm:@types/react@^19.2.9",
- "npm:hono@^4.11.6",
+ "npm:@types/react@^19.2.13",
+ "npm:hono@^4.11.9",
"npm:react-router@^7.13.0",
"npm:react@^19.2.4",
"npm:tailwindcss@^4.1.18"
@@ -2387,33 +2432,33 @@
},
"templates/tanstack": {
"dependencies": [
- "jsr:@std/assert@^1.0.17",
- "jsr:@std/async@^1.1.0",
+ "jsr:@std/assert@^1.0.18",
+ "jsr:@std/async@^1.1.1",
"jsr:@std/path@^1.1.4",
"jsr:@std/streams@^1.0.17",
"jsr:@std/testing@^1.0.17",
- "jsr:@udibo/juniper@~0.3.1",
+ "jsr:@udibo/juniper@~0.3.2",
"npm:@opentelemetry/api@^1.9.0",
"npm:@tanstack/react-query@^5.90.20",
"npm:@testing-library/react@^16.3.2",
"npm:@testing-library/user-event@^14.6.1",
- "npm:@types/react@^19.2.9",
- "npm:hono@^4.11.6",
+ "npm:@types/react@^19.2.13",
+ "npm:hono@^4.11.9",
"npm:react-router@^7.13.0",
"npm:react@^19.2.4"
]
},
"tutorials/blog": {
"dependencies": [
- "jsr:@std/assert@^1.0.17",
+ "jsr:@std/assert@^1.0.18",
"jsr:@std/path@^1.1.4",
"jsr:@std/streams@^1.0.17",
"jsr:@std/testing@^1.0.17",
- "jsr:@udibo/juniper@~0.3.1",
+ "jsr:@udibo/juniper@~0.3.2",
"npm:@opentelemetry/api@^1.9.0",
"npm:@testing-library/react@^16.3.2",
- "npm:@types/react@^19.2.9",
- "npm:hono@^4.11.6",
+ "npm:@types/react@^19.2.13",
+ "npm:hono@^4.11.9",
"npm:react-router@^7.13.0",
"npm:react@^19.2.4"
]
diff --git a/example/deno.json b/example/deno.json
index 8fb29ca..2183742 100644
--- a/example/deno.json
+++ b/example/deno.json
@@ -36,19 +36,19 @@
"@udibo/juniper": "jsr:@udibo/juniper@^0.3.2",
"@udibo/esbuild-plugin-postcss": "jsr:@udibo/esbuild-plugin-postcss@^0.3.0",
"@std/testing": "jsr:@std/testing@^1.0.17",
- "@std/assert": "jsr:@std/assert@^1.0.17",
- "@std/async": "jsr:@std/async@^1.1.0",
- "@std/collections": "jsr:@std/collections@^1.1.4",
+ "@std/assert": "jsr:@std/assert@^1.0.18",
+ "@std/async": "jsr:@std/async@^1.1.1",
+ "@std/collections": "jsr:@std/collections@^1.1.5",
"@std/encoding": "jsr:@std/encoding@^1.0.10",
"@std/path": "jsr:@std/path@^1.1.4",
"@std/streams": "jsr:@std/streams@^1.0.17",
"@std/uuid": "jsr:@std/uuid@^1.1.0",
"@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0",
- "playwright": "npm:playwright@^1.58.0",
+ "playwright": "npm:playwright@^1.58.2",
"react": "npm:react@^19.2.4",
- "@types/react": "npm:@types/react@^19.2.9",
+ "@types/react": "npm:@types/react@^19.2.13",
"react-router": "npm:react-router@^7.13.0",
- "hono": "npm:hono@^4.11.6",
+ "hono": "npm:hono@^4.11.9",
"tailwindcss": "npm:tailwindcss@^4.1.18",
"@tailwindcss/postcss": "npm:@tailwindcss/postcss@^4.1.18",
"zod": "npm:zod@^4.3.6",
diff --git a/src/_server.tsx b/src/_server.tsx
index 03b5a8c..35c71b0 100644
--- a/src/_server.tsx
+++ b/src/_server.tsx
@@ -5,7 +5,7 @@ import { parseArgs } from "@std/cli/parse-args";
import { HttpError } from "./mod.ts";
import { SpanStatusCode } from "@opentelemetry/api";
import { Hono } from "hono";
-import type { Context, Env, Schema } from "hono";
+import type { Context, Env, MiddlewareHandler, Schema } from "hono";
import { createFactory } from "hono/factory";
import { serveStatic } from "hono/deno";
import { stream } from "hono/streaming";
@@ -1047,6 +1047,20 @@ export function buildApp<
app.route("*", catchallApp);
}
+ // Catch data requests that didn't match any route handler at this level.
+ // This happens when the client navigates to a non-existent URL and React Router
+ // needs to re-fetch parent route data (e.g., GET /identity/applications with
+ // X-Juniper-Route-Id: "/" to refresh the root loader).
+ if (isClientRoute && errorHandler) {
+ const handleDataRequest = reactHandlers[reactHandlers.length - 1];
+ app.use("*", (c, next) => {
+ if (c.req.header("X-Juniper-Route-Id")) {
+ return (handleDataRequest as unknown as MiddlewareHandler)(c, next);
+ }
+ return next();
+ });
+ }
+
app.use("*", notFound);
return app;
}
diff --git a/src/server.test.tsx b/src/server.test.tsx
index d97d11a..1e06e0f 100644
--- a/src/server.test.tsx
+++ b/src/server.test.tsx
@@ -542,6 +542,61 @@ describe("createServer", () => {
assertEquals(html.includes("Admin Error Boundary"), false);
});
+ it("should return parent loader data for data requests to non-existent child URLs", async () => {
+ const client = new Client({
+ path: "/",
+ main: {
+ default: () =>