diff --git a/packages/example-pathless-ssr/package.json b/packages/example-pathless-ssr/package.json index af667fb..618fc0f 100644 --- a/packages/example-pathless-ssr/package.json +++ b/packages/example-pathless-ssr/package.json @@ -12,8 +12,8 @@ "dependencies": { "@funstack/router": "workspace:*", "@funstack/static": "^1.2.0", - "react": "19.3.0-canary-1b45e243-20260402", - "react-dom": "19.3.0-canary-1b45e243-20260402" + "react": "^19.2.5", + "react-dom": "^19.2.5" }, "devDependencies": { "@types/react": "^19.2.14", diff --git a/packages/example-pathless-ssr/src/ClientApp.tsx b/packages/example-pathless-ssr/src/ClientApp.tsx index 829e820..928d53d 100644 --- a/packages/example-pathless-ssr/src/ClientApp.tsx +++ b/packages/example-pathless-ssr/src/ClientApp.tsx @@ -6,7 +6,5 @@ import "./styles.css"; export function ClientApp({ routes }: { routes: RouteDefinition[] }) { // No ssr prop — during SSR only pathless routes match, rendering the app shell. // Path-based content fills in on client hydration. - // experimentalPostpone uses React's unstable_postpone API to avoid hydration - // mismatches by deferring unmatched outlet content to client rendering. - return ; + return ; } diff --git a/packages/example-pathless-ssr/vite.config.ts b/packages/example-pathless-ssr/vite.config.ts index 92788ac..877736a 100644 --- a/packages/example-pathless-ssr/vite.config.ts +++ b/packages/example-pathless-ssr/vite.config.ts @@ -11,10 +11,4 @@ export default defineConfig({ react(), ], base: "/", - resolve: { - // Ensure all react imports resolve to the same instance. - // Without this, the workspace router package may resolve to a different - // React version than the one used by this example (React Canary). - dedupe: ["react", "react-dom"], - }, }); diff --git a/packages/router/src/Outlet.tsx b/packages/router/src/Outlet.tsx index b5576c5..669fc61 100644 --- a/packages/router/src/Outlet.tsx +++ b/packages/router/src/Outlet.tsx @@ -1,40 +1,16 @@ import { type ReactNode, useContext } from "react"; -import * as React from "react"; import { RouteContext } from "./context/RouteContext.js"; -import { RouterContext } from "./context/RouterContext.js"; /** * Renders the matched child route. * Used in layout components to specify where child routes should render. - * - * When `experimentalPostpone` is enabled on the Router and no child route - * matches during pathless SSR, calls `React.unstable_postpone()` instead of - * rendering `null`, deferring the content to client-side rendering. */ export function Outlet(): ReactNode { const routeContext = useContext(RouteContext); - const routerContext = useContext(RouterContext); if (!routeContext) { return null; } - if ( - routeContext.outlet === null && - routerContext !== null && - routerContext.experimentalPostpone && - routerContext.url === null - ) { - // During pathless SSR, child routes cannot match because there is no URL. - // Use React's experimental postpone API to signal that this content - // should be rendered on the client instead. - const postpone = (React as Record)["unstable_postpone"]; - if (typeof postpone === "function") { - (postpone as (reason: string) => never)( - "Route not matched during pathless SSR", - ); - } - } - return routeContext.outlet; } diff --git a/packages/router/src/Router/index.tsx b/packages/router/src/Router/index.tsx index 853f27a..8273477 100644 --- a/packages/router/src/Router/index.tsx +++ b/packages/router/src/Router/index.tsx @@ -98,19 +98,6 @@ export type RouterProps = { * ``` */ ssr?: SSRConfig; - /** - * Enable React's experimental `unstable_postpone` API for pathless SSR. - * - * When enabled, `` calls `React.unstable_postpone()` instead of - * rendering `null` when no child route can be matched during pathless SSR. - * This avoids hydration mismatches by telling React to defer rendering of - * the outlet content to the client. - * - * **Requires React Canary or experimental builds.** - * - * @default false - */ - experimentalPostpone?: boolean; }; export function Router({ @@ -118,7 +105,6 @@ export function Router({ onNavigate, fallback = "none", ssr, - experimentalPostpone = false, }: RouterProps): ReactNode { const routes = internalRoutes(inputRoutes); @@ -299,7 +285,6 @@ export function Router({ isPending, navigateAsync, updateCurrentEntryState, - experimentalPostpone, }), [ locationState, @@ -310,7 +295,6 @@ export function Router({ isPending, navigateAsync, updateCurrentEntryState, - experimentalPostpone, ], ); diff --git a/packages/router/src/context/RouterContext.ts b/packages/router/src/context/RouterContext.ts index c8164f4..f41286f 100644 --- a/packages/router/src/context/RouterContext.ts +++ b/packages/router/src/context/RouterContext.ts @@ -24,11 +24,6 @@ export type RouterContextValue = { navigateAsync: (to: string, options?: NavigateOptions) => Promise; /** Update current entry's state without navigation */ updateCurrentEntryState: (state: unknown) => void; - /** - * Whether to use React's experimental `unstable_postpone` API for - * content that cannot be rendered during pathless SSR. - */ - experimentalPostpone: boolean; }; export const RouterContext = createContext(null); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17e4087..6583b7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,13 +90,13 @@ importers: version: link:../router '@funstack/static': specifier: ^1.2.0 - version: 1.2.0(react-dom@19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402))(react@19.3.0-canary-1b45e243-20260402)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)) + version: 1.2.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)) react: - specifier: 19.3.0-canary-1b45e243-20260402 - version: 19.3.0-canary-1b45e243-20260402 + specifier: ^19.2.5 + version: 19.2.5 react-dom: - specifier: 19.3.0-canary-1b45e243-20260402 - version: 19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402) + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) devDependencies: '@types/react': specifier: ^19.2.14 @@ -1632,11 +1632,6 @@ packages: peerDependencies: react: ^19.2.5 - react-dom@19.3.0-canary-1b45e243-20260402: - resolution: {integrity: sha512-GUAjHZZGLPaq0oWL8JAT/Aeal/Urbk6mrzvvhxAmcT0xJkiZnMKkiWNFm4AV84tmgrHmKQzCTxfIs9LG5P228A==} - peerDependencies: - react: 19.3.0-canary-1b45e243-20260402 - react-error-boundary@6.1.1: resolution: {integrity: sha512-BrYwPOdXi5mqkk5lw+Uvt0ThHx32rCt3BkukS4X23A2AIWDPSGX6iaWTc0y9TU/mHDA/6qOSGel+B2ERkOvD1w==} peerDependencies: @@ -1649,10 +1644,6 @@ packages: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} - react@19.3.0-canary-1b45e243-20260402: - resolution: {integrity: sha512-CUTHdpLPSvcJovRhEegguvA6z5d76SoR/CXY32EnDoB22ScDV/PYjvKzAmxwRooY2VcZHzpD3WfcSgHRjocbPA==} - engines: {node: '>=0.10.0'} - redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -1707,9 +1698,6 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - scheduler@0.28.0-canary-1b45e243-20260402: - resolution: {integrity: sha512-9tsooPtKSPIsXs2Z4v3/QzR30fRDMlJncEqbb/TAadkfT54lKGbZuTpuNIt1N68IEhq5y4dxvaqC1pTYOYwA9Q==} - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -2350,19 +2338,6 @@ snapshots: transitivePeerDependencies: - react-server-dom-webpack - '@funstack/static@1.2.0(react-dom@19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402))(react@19.3.0-canary-1b45e243-20260402)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4))': - dependencies: - '@funstack/skill-installer': 1.0.0 - '@vitejs/plugin-rsc': 0.5.23(react-dom@19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402))(react@19.3.0-canary-1b45e243-20260402)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)) - react: 19.3.0-canary-1b45e243-20260402 - react-dom: 19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402) - react-error-boundary: 6.1.1(react@19.3.0-canary-1b45e243-20260402) - rsc-html-stream: 0.0.7 - srvx: 0.11.15 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4) - transitivePeerDependencies: - - react-server-dom-webpack - '@img/colour@1.1.0': {} '@img/sharp-darwin-arm64@0.34.5': @@ -2770,20 +2745,6 @@ snapshots: vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4) vitefu: 1.1.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)) - '@vitejs/plugin-rsc@0.5.23(react-dom@19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402))(react@19.3.0-canary-1b45e243-20260402)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4))': - dependencies: - '@rolldown/pluginutils': 1.0.0-rc.13 - es-module-lexer: 2.0.0 - estree-walker: 3.0.3 - magic-string: 0.30.21 - react: 19.3.0-canary-1b45e243-20260402 - react-dom: 19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402) - srvx: 0.11.15 - strip-literal: 3.1.0 - turbo-stream: 3.2.0 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.27.4) - vitefu: 1.1.3(vite@8.0.10(@types/node@25.6.0)(esbuild@0.27.4)) - '@vitest/expect@4.1.5': dependencies: '@standard-schema/spec': 1.1.0 @@ -3228,25 +3189,14 @@ snapshots: react: 19.2.5 scheduler: 0.27.0 - react-dom@19.3.0-canary-1b45e243-20260402(react@19.3.0-canary-1b45e243-20260402): - dependencies: - react: 19.3.0-canary-1b45e243-20260402 - scheduler: 0.28.0-canary-1b45e243-20260402 - react-error-boundary@6.1.1(react@19.2.5): dependencies: react: 19.2.5 - react-error-boundary@6.1.1(react@19.3.0-canary-1b45e243-20260402): - dependencies: - react: 19.3.0-canary-1b45e243-20260402 - react-is@17.0.2: {} react@19.2.5: {} - react@19.3.0-canary-1b45e243-20260402: {} - redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -3313,8 +3263,6 @@ snapshots: scheduler@0.27.0: {} - scheduler@0.28.0-canary-1b45e243-20260402: {} - semver@7.7.4: {} sharp@0.34.5: