diff --git a/packages/plugin-rsc/examples/hmr/README.md b/packages/plugin-rsc/examples/hmr/README.md
new file mode 100644
index 00000000..bfb04fe6
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/README.md
@@ -0,0 +1,7 @@
+The `App` just renders a client `Item` component passing in a key which It gets from 'lookup.ts'. The `Item` passes this key into 'lookup.ts' to get the value which it then displays. If you edit the key in 'lookup.ts' from 'a' to 'b', for example, then the hmr update throws.
+
+```ts
+export const key = 'b'
+```
+
+The reason for the error is that the server response uses the latest 'lookup.ts' but the client uses the old 'lookup.ts'. So the rsc response has the key 'b' but the client still has the key 'a'.
diff --git a/packages/plugin-rsc/examples/hmr/package.json b/packages/plugin-rsc/examples/hmr/package.json
new file mode 100644
index 00000000..981def69
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@vitejs/plugin-rsc-examples-hmr",
+ "private": true,
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@vitejs/plugin-rsc": "latest",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0"
+ },
+ "devDependencies": {
+ "@types/react": "^19.1.8",
+ "@types/react-dom": "^19.1.6",
+ "@vitejs/plugin-react": "latest",
+ "rsc-html-stream": "^0.0.7",
+ "vite": "^7.0.4",
+ "vite-plugin-inspect": "^11.3.0"
+ }
+}
diff --git a/packages/plugin-rsc/examples/hmr/public/favicon.ico b/packages/plugin-rsc/examples/hmr/public/favicon.ico
new file mode 100644
index 00000000..4aff0766
Binary files /dev/null and b/packages/plugin-rsc/examples/hmr/public/favicon.ico differ
diff --git a/packages/plugin-rsc/examples/hmr/src/App.tsx b/packages/plugin-rsc/examples/hmr/src/App.tsx
new file mode 100644
index 00000000..08f56400
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/src/App.tsx
@@ -0,0 +1,17 @@
+import { key } from './lookup'
+import Item from './Item'
+
+const App = async () => {
+ return (
+
+
+ Hmr
+
+
+
+
+
+ )
+}
+
+export default App
diff --git a/packages/plugin-rsc/examples/hmr/src/Item.tsx b/packages/plugin-rsc/examples/hmr/src/Item.tsx
new file mode 100644
index 00000000..b9684269
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/src/Item.tsx
@@ -0,0 +1,8 @@
+'use client'
+import lookup from './lookup'
+
+const Item = ({ k }: { k: string }) => {
+ return lookup(k).hello
+}
+
+export default Item
diff --git a/packages/plugin-rsc/examples/hmr/src/client.tsx b/packages/plugin-rsc/examples/hmr/src/client.tsx
new file mode 100644
index 00000000..72dc3eea
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/src/client.tsx
@@ -0,0 +1,30 @@
+import * as ReactClient from '@vitejs/plugin-rsc/browser'
+import { useState, useEffect } from 'react'
+import ReactDOM from 'react-dom/client'
+import { rscStream } from 'rsc-html-stream/client'
+import { createFromFetch } from '@vitejs/plugin-rsc/browser'
+
+async function fetchRSC(url: string, options: any) {
+ const payload = (await createFromFetch(fetch(url, options))) as any
+ return payload.root
+}
+
+const initialPayload = await ReactClient.createFromReadableStream<{
+ root: React.ReactNode
+}>(rscStream)
+function Shell() {
+ const [root, setRoot] = useState(initialPayload.root)
+ useEffect(() => {
+ const onHmrReload = () => {
+ const root = fetchRSC('/', {
+ method: 'post',
+ headers: { 'Content-Type': 'application/json' },
+ })
+ setRoot(root)
+ }
+ import.meta.hot?.on('rsc:update', onHmrReload)
+ return () => import.meta.hot?.off('rsc:update', onHmrReload)
+ })
+ return root
+}
+ReactDOM.hydrateRoot(document, )
diff --git a/packages/plugin-rsc/examples/hmr/src/lookup.ts b/packages/plugin-rsc/examples/hmr/src/lookup.ts
new file mode 100644
index 00000000..2e7da649
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/src/lookup.ts
@@ -0,0 +1,11 @@
+export const key = 'a'
+
+const data = {
+ [key]: { hello: 'world ' },
+} as Record
+
+const lookup = (k: string) => {
+ return data[k]
+}
+
+export default lookup
diff --git a/packages/plugin-rsc/examples/hmr/src/server.ssr.tsx b/packages/plugin-rsc/examples/hmr/src/server.ssr.tsx
new file mode 100644
index 00000000..790251b4
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/src/server.ssr.tsx
@@ -0,0 +1,24 @@
+import * as ReactClient from '@vitejs/plugin-rsc/ssr'
+import React from 'react'
+import * as ReactDOMServer from 'react-dom/server.edge'
+import { injectRSCPayload } from 'rsc-html-stream/server'
+
+type RscPayload = {
+ root: React.ReactNode
+}
+export async function renderHTML(rscStream: ReadableStream) {
+ const [rscStream1, rscStream2] = rscStream.tee()
+ let payload: Promise
+ function SsrRoot() {
+ payload ??= ReactClient.createFromReadableStream(rscStream1)
+ return React.use(payload).root
+ }
+ const bootstrapScriptContent =
+ await import.meta.viteRsc.loadBootstrapScriptContent('index')
+ const htmlStream = await ReactDOMServer.renderToReadableStream( , {
+ bootstrapScriptContent,
+ })
+ let responseStream: ReadableStream = htmlStream
+ responseStream = responseStream.pipeThrough(injectRSCPayload(rscStream2))
+ return responseStream
+}
diff --git a/packages/plugin-rsc/examples/hmr/src/server.tsx b/packages/plugin-rsc/examples/hmr/src/server.tsx
new file mode 100644
index 00000000..2ce2c6e1
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/src/server.tsx
@@ -0,0 +1,21 @@
+import * as ReactServer from '@vitejs/plugin-rsc/rsc'
+import App from './App.tsx'
+
+export default async function handler(request: Request): Promise {
+ const root =
+ const rscStream = ReactServer.renderToReadableStream({ root })
+ if (request.method !== 'GET') {
+ return new Response(rscStream, {
+ headers: { 'Content-type': 'text/x-component' },
+ })
+ }
+ const ssrEntryModule = await import.meta.viteRsc.loadModule<
+ typeof import('./server.ssr.tsx')
+ >('ssr', 'index')
+ const htmlStream = await ssrEntryModule.renderHTML(rscStream)
+ return new Response(htmlStream, { headers: { 'Content-type': 'text/html' } })
+}
+
+if (import.meta.hot) {
+ import.meta.hot.accept()
+}
diff --git a/packages/plugin-rsc/examples/hmr/tsconfig.json b/packages/plugin-rsc/examples/hmr/tsconfig.json
new file mode 100644
index 00000000..77438d9d
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "skipLibCheck": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+ "moduleResolution": "Bundler",
+ "module": "ESNext",
+ "target": "ESNext",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "types": ["vite/client", "@vitejs/plugin-rsc/types"],
+ "jsx": "react-jsx"
+ }
+}
diff --git a/packages/plugin-rsc/examples/hmr/vite.config.ts b/packages/plugin-rsc/examples/hmr/vite.config.ts
new file mode 100644
index 00000000..6c734151
--- /dev/null
+++ b/packages/plugin-rsc/examples/hmr/vite.config.ts
@@ -0,0 +1,22 @@
+import rsc from '@vitejs/plugin-rsc'
+import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ clearScreen: false,
+ plugins: [
+ react(),
+ rsc({
+ entries: {
+ client: './src/client.tsx',
+ ssr: './src/server.ssr.tsx',
+ rsc: './src/server.tsx',
+ },
+ ignoredPackageWarnings: ['navigation-react'],
+ }),
+ ],
+ optimizeDeps: {
+ include: ['navigation'],
+ exclude: ['navigation-react'],
+ },
+})
diff --git a/packages/plugin-rsc/examples/navigation/package.json b/packages/plugin-rsc/examples/navigation/package.json
new file mode 100644
index 00000000..1f3cf25f
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@vitejs/plugin-rsc-examples-navigation",
+ "private": true,
+ "license": "MIT",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@vitejs/plugin-rsc": "latest",
+ "navigation": "^6.3.0",
+ "navigation-react": "^4.13.0",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0"
+ },
+ "devDependencies": {
+ "@types/react": "^19.1.8",
+ "@types/react-dom": "^19.1.6",
+ "@vitejs/plugin-react": "latest",
+ "rsc-html-stream": "^0.0.7",
+ "vite": "^7.0.4",
+ "vite-plugin-inspect": "^11.3.0"
+ }
+}
diff --git a/packages/plugin-rsc/examples/navigation/public/favicon.ico b/packages/plugin-rsc/examples/navigation/public/favicon.ico
new file mode 100644
index 00000000..4aff0766
Binary files /dev/null and b/packages/plugin-rsc/examples/navigation/public/favicon.ico differ
diff --git a/packages/plugin-rsc/examples/navigation/src/App.tsx b/packages/plugin-rsc/examples/navigation/src/App.tsx
new file mode 100644
index 00000000..99ee93aa
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/App.tsx
@@ -0,0 +1,29 @@
+import { SceneView } from 'navigation-react'
+import NavigationProvider from './NavigationProvider'
+import HmrProvider from './HmrProvider'
+import People from './People'
+import Person from './Person'
+
+const App = async ({ url }: any) => {
+ return (
+
+
+ Navigation React
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default App;
diff --git a/packages/plugin-rsc/examples/navigation/src/Filter.tsx b/packages/plugin-rsc/examples/navigation/src/Filter.tsx
new file mode 100644
index 00000000..904c1091
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/Filter.tsx
@@ -0,0 +1,38 @@
+'use client'
+import { startTransition, useOptimistic } from 'react'
+import { RefreshLink, useNavigationEvent } from 'navigation-react'
+
+const Filter = () => {
+ const { data, stateNavigator } = useNavigationEvent()
+ const { name } = data
+ const [optimisticName, setOptimisticName] = useOptimistic(
+ name || '',
+ (_, newName) => newName,
+ )
+ return (
+
+
+ Name
+ {
+ startTransition(() => {
+ setOptimisticName(value)
+ stateNavigator.refresh({ ...data, name: value, page: null })
+ })
+ }}
+ />
+
+ Page size
+
+ 5
+
+
+ 10
+
+
+ )
+}
+
+export default Filter
diff --git a/packages/plugin-rsc/examples/navigation/src/Friends.tsx b/packages/plugin-rsc/examples/navigation/src/Friends.tsx
new file mode 100644
index 00000000..7254cab2
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/Friends.tsx
@@ -0,0 +1,34 @@
+import { RefreshLink, useNavigationEvent } from 'navigation-react'
+import { getFriends } from './data'
+import Gender from './Gender'
+
+const Friends = async () => {
+ const {
+ data: { show, id, gender },
+ } = useNavigationEvent()
+ const friends = show ? await getFriends(id, gender) : null
+ return (
+ <>
+ {`${!show ? 'Show' : 'Hide'} Friends`}
+ {show && (
+ <>
+
+
+ {friends?.map(({ id, name }) => (
+
+
+ {name}
+
+
+ ))}
+
+ >
+ )}
+ >
+ )
+}
+
+export default Friends
diff --git a/packages/plugin-rsc/examples/navigation/src/Gender.tsx b/packages/plugin-rsc/examples/navigation/src/Gender.tsx
new file mode 100644
index 00000000..a7cf044f
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/Gender.tsx
@@ -0,0 +1,35 @@
+'use client'
+import { startTransition } from 'react'
+import { useNavigationEvent } from 'navigation-react'
+import { useOptimistic } from 'react'
+
+const Gender = () => {
+ const { data, stateNavigator } = useNavigationEvent()
+ const { gender } = data
+ const [optimisticGender, setOptimisticGender] = useOptimistic(
+ gender || '',
+ (_, newGender) => newGender,
+ )
+ return (
+
+ Gender
+ {
+ startTransition(() => {
+ setOptimisticGender(value)
+ stateNavigator.refresh({ ...data, gender: value })
+ })
+ }}
+ >
+
+ Male
+ Female
+ Other
+
+
+ )
+}
+
+export default Gender
diff --git a/packages/plugin-rsc/examples/navigation/src/HmrProvider.tsx b/packages/plugin-rsc/examples/navigation/src/HmrProvider.tsx
new file mode 100644
index 00000000..537229cd
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/HmrProvider.tsx
@@ -0,0 +1,42 @@
+'use client'
+import { useContext, useEffect } from 'react'
+import { BundlerContext, useNavigationEvent } from 'navigation-react'
+
+const HmrProvider = ({ children }: any) => {
+ const { setRoot, deserialize } = useContext(BundlerContext)
+ const { stateNavigator } = useNavigationEvent()
+ useEffect(() => {
+ const onHmrReload = () => {
+ const {
+ stateContext: {
+ state,
+ data,
+ crumbs,
+ nextCrumb: { crumblessUrl },
+ },
+ } = stateNavigator
+ const root = deserialize(
+ stateNavigator.historyManager.getHref(crumblessUrl),
+ {
+ method: 'put',
+ headers: { 'Content-Type': 'application/json' },
+ body: {
+ crumbs: crumbs.map(({ state, data }) => ({
+ state: state.key,
+ data,
+ })),
+ state: state.key,
+ data,
+ },
+ },
+ )
+ stateNavigator.historyManager.stop()
+ setRoot(root)
+ }
+ import.meta.hot?.on("rsc:update", onHmrReload);
+ return () => import.meta.hot?.off("rsc:update", onHmrReload);
+ })
+ return children
+}
+
+export default HmrProvider
diff --git a/packages/plugin-rsc/examples/navigation/src/NavigationProvider.tsx b/packages/plugin-rsc/examples/navigation/src/NavigationProvider.tsx
new file mode 100644
index 00000000..1e1c5ec9
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/NavigationProvider.tsx
@@ -0,0 +1,23 @@
+'use client'
+import { useMemo } from 'react'
+import { StateNavigator, HTML5HistoryManager } from 'navigation'
+import { NavigationHandler } from 'navigation-react'
+import stateNavigator from './stateNavigator'
+
+const historyManager = new HTML5HistoryManager()
+
+const NavigationProvider = ({ url, children }: any) => {
+ const clientNavigator = useMemo(() => {
+ historyManager.stop()
+ const clientNavigator = new StateNavigator(stateNavigator, historyManager)
+ clientNavigator.navigateLink(url)
+ return clientNavigator
+ }, [])
+ return (
+
+ {children}
+
+ )
+}
+
+export default NavigationProvider
diff --git a/packages/plugin-rsc/examples/navigation/src/Pager.tsx b/packages/plugin-rsc/examples/navigation/src/Pager.tsx
new file mode 100644
index 00000000..bb8a7991
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/Pager.tsx
@@ -0,0 +1,64 @@
+import { RefreshLink, useNavigationEvent } from 'navigation-react'
+
+const Pager = ({ totalRowCount }: { totalRowCount: number }) => {
+ const {
+ data: { page, size },
+ } = useNavigationEvent()
+ const lastPage = Math.ceil(totalRowCount / size)
+ return (
+
+
+ {totalRowCount ? (
+ <>
+
+
+ First
+
+
+
+
+ Previous
+
+
+
+
+ Next
+
+
+
+
+ Last
+
+
+ >
+ ) : (
+ <>
+ First
+ Previous
+ Next
+ Last
+ >
+ )}
+
+ Total Count {totalRowCount}
+
+ )
+}
+
+export default Pager
diff --git a/packages/plugin-rsc/examples/navigation/src/People.tsx b/packages/plugin-rsc/examples/navigation/src/People.tsx
new file mode 100644
index 00000000..6262ade3
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/People.tsx
@@ -0,0 +1,51 @@
+import { searchPeople } from './data'
+import {
+ NavigationLink,
+ RefreshLink,
+ useNavigationEvent,
+} from 'navigation-react'
+import Filter from './Filter'
+import Pager from './Pager'
+
+const People = async () => {
+ const {
+ data: { name, page, size, sort },
+ } = useNavigationEvent()
+ const { people, count } = await searchPeople(name, page, size, sort)
+ return (
+ <>
+ People
+
+
+
+
+
+
+ Name
+
+
+ Date of Birth
+
+
+
+ {people.map(({ id, name, dateOfBirth }) => (
+
+
+
+ {name}
+
+
+ {dateOfBirth}
+
+ ))}
+
+
+
+ >
+ )
+}
+
+export default People
diff --git a/packages/plugin-rsc/examples/navigation/src/Person.tsx b/packages/plugin-rsc/examples/navigation/src/Person.tsx
new file mode 100644
index 00000000..f76e82da
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/Person.tsx
@@ -0,0 +1,34 @@
+import {
+ SceneView,
+ NavigationBackLink,
+ useNavigationEvent,
+} from 'navigation-react'
+import { getPerson } from './data'
+import Friends from './Friends'
+
+const Person = async () => {
+ const { data } = useNavigationEvent()
+ const { name, dateOfBirth, email, phone } = await getPerson(data.id)
+ return (
+ <>
+ Person
+
+
Person Search
+
+
{name}
+
Date of Birth
+
{dateOfBirth}
+
Email
+
{email}
+
Phone
+
{phone}
+
+
+
+
+
+ >
+ )
+}
+
+export default Person
diff --git a/packages/plugin-rsc/examples/navigation/src/client.tsx b/packages/plugin-rsc/examples/navigation/src/client.tsx
new file mode 100644
index 00000000..afe91f80
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/client.tsx
@@ -0,0 +1,23 @@
+import * as ReactClient from '@vitejs/plugin-rsc/browser'
+import { useState, useMemo } from "react";
+import ReactDOM from "react-dom/client";
+import { rscStream } from 'rsc-html-stream/client'
+import { createFromFetch } from "@vitejs/plugin-rsc/browser";
+import { BundlerContext } from 'navigation-react';
+
+async function fetchRSC(url: string, {body, ...options}: any) {
+ const payload = await createFromFetch(fetch(url, {...options, body: JSON.stringify(body)})) as any;
+ return payload.root;
+}
+
+const initialPayload = await ReactClient.createFromReadableStream<{root: React.ReactNode}>(rscStream)
+function Shell() {
+ const [root, setRoot] = useState(initialPayload.root);
+ const bundler = useMemo(() => ({setRoot, deserialize: fetchRSC}), []);
+ return (
+
+ {root}
+
+ );
+}
+ReactDOM.hydrateRoot(document, );
diff --git a/packages/plugin-rsc/examples/navigation/src/data.ts b/packages/plugin-rsc/examples/navigation/src/data.ts
new file mode 100644
index 00000000..01da4cba
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/data.ts
@@ -0,0 +1,175 @@
+type Person = {
+ id: number
+ name: string
+ gender: 'male' | 'female' | 'other'
+ dateOfBirth: string
+ email: string
+ phone: string
+ friends: number[]
+}
+
+var people: Person[] = [
+ {
+ id: 1,
+ name: 'Bell Halvorson',
+ gender: 'female',
+ dateOfBirth: '01/01/1980',
+ email: 'bell@navigation.com',
+ phone: '555 0001',
+ friends: [2, 3, 4, 5],
+ },
+ {
+ id: 2,
+ name: 'Aditya Larson',
+ gender: 'male',
+ dateOfBirth: '01/02/1980',
+ email: 'aditya@navigation.com',
+ phone: '555 0002',
+ friends: [3, 4, 5, 6],
+ },
+ {
+ id: 3,
+ name: 'Rashawn Schamberger',
+ gender: 'male',
+ dateOfBirth: '01/03/1980',
+ email: 'rashawn@navigation.com',
+ phone: '555 0003',
+ friends: [4, 5, 6, 7],
+ },
+ {
+ id: 4,
+ name: 'Rupert Grant',
+ gender: 'male',
+ dateOfBirth: '01/04/1980',
+ email: 'rupert@navigation.com',
+ phone: '555 0004',
+ friends: [5, 6, 7, 8],
+ },
+ {
+ id: 5,
+ name: 'Opal Carter',
+ gender: 'female',
+ dateOfBirth: '01/05/1980',
+ email: 'opal@navigation.com',
+ phone: '555 0005',
+ friends: [6, 7, 8, 9],
+ },
+ {
+ id: 6,
+ name: 'Candida Christiansen',
+ gender: 'female',
+ dateOfBirth: '01/06/1980',
+ email: 'candida@navigation.com',
+ phone: '555 0006',
+ friends: [7, 8, 9, 10],
+ },
+ {
+ id: 7,
+ name: 'Haven Stroman',
+ gender: 'other',
+ dateOfBirth: '01/07/1980',
+ email: 'haven@navigation.com',
+ phone: '555 0007',
+ friends: [8, 9, 10, 11],
+ },
+ {
+ id: 8,
+ name: 'Celine Leannon',
+ gender: 'female',
+ dateOfBirth: '01/08/1980',
+ email: 'celine@navigation.com',
+ phone: '555 0008',
+ friends: [9, 10, 11, 12],
+ },
+ {
+ id: 9,
+ name: 'Ryan Ruecker',
+ gender: 'male',
+ dateOfBirth: '01/09/1980',
+ email: 'ryan@navigation.com',
+ phone: '555 0009',
+ friends: [10, 11, 12, 1],
+ },
+ {
+ id: 10,
+ name: 'Kaci Hoppe',
+ gender: 'other',
+ dateOfBirth: '01/10/1980',
+ email: 'kaci@navigation.com',
+ phone: '555 0010',
+ friends: [11, 12, 1, 2],
+ },
+ {
+ id: 11,
+ name: 'Fernando Dietrich',
+ gender: 'male',
+ dateOfBirth: '01/11/1980',
+ email: 'fernando@navigation.com',
+ phone: '555 0011',
+ friends: [12, 1, 2, 3],
+ },
+ {
+ id: 12,
+ name: 'Emelie Lueilwitz',
+ gender: 'female',
+ dateOfBirth: '01/12/1980',
+ email: 'emelie@navigation.com',
+ phone: '555 0012',
+ friends: [1, 2, 3, 4],
+ },
+]
+
+const searchPeople = async (
+ name: string,
+ pageNumber: number,
+ pageSize: number,
+ sortExpression: string,
+) => {
+ return new Promise(
+ (res: (data: { people: Person[]; count: number }) => void) => {
+ var start = (pageNumber - 1) * pageSize
+ var filteredPeople = people
+ .sort((personA, personB) => {
+ var mult = sortExpression.indexOf('desc') === -1 ? -1 : 1
+ return mult * (personA.name < personB.name ? 1 : -1)
+ })
+ .filter(
+ (person) =>
+ !name ||
+ person.name.toUpperCase().indexOf(name.toUpperCase()) !== -1,
+ )
+ setTimeout(() => {
+ res({
+ people: filteredPeople.slice(start, start + pageSize),
+ count: filteredPeople.length,
+ })
+ }, 10)
+ },
+ )
+}
+
+const getPerson = async (id: number) => {
+ return new Promise((res: (person: Person) => void) => {
+ const person = people.find(({ id: personId }) => personId === id)!
+ setTimeout(() => {
+ res(person)
+ }, 10)
+ })
+}
+
+const getFriends = async (id: number, gender: 'male' | 'female' | 'other') => {
+ return new Promise((res: (friends: Person[]) => void) => {
+ const person = people.find(({ id: personId }) => personId == id)!
+ setTimeout(() => {
+ res(
+ person.friends
+ .map((id) => people.find(({ id: personId }) => personId === id)!)
+ .filter(
+ ({ gender: personGender }) => !gender || personGender === gender,
+ ),
+ )
+ }, 10)
+ })
+}
+
+export { searchPeople, getPerson, getFriends }
diff --git a/packages/plugin-rsc/examples/navigation/src/server.ssr.tsx b/packages/plugin-rsc/examples/navigation/src/server.ssr.tsx
new file mode 100644
index 00000000..2dfe8785
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/server.ssr.tsx
@@ -0,0 +1,27 @@
+import * as ReactClient from '@vitejs/plugin-rsc/ssr';
+import React from 'react';
+import * as ReactDOMServer from 'react-dom/server.edge';
+import { injectRSCPayload } from 'rsc-html-stream/server';
+
+type RscPayload = {
+ root: React.ReactNode
+}
+export async function renderHTML(
+ rscStream: ReadableStream) {
+ const [rscStream1, rscStream2] = rscStream.tee();
+ let payload: Promise;
+ function SsrRoot() {
+ payload ??= ReactClient.createFromReadableStream(rscStream1);
+ return React.use(payload).root;
+ }
+ const bootstrapScriptContent =
+ await import.meta.viteRsc.loadBootstrapScriptContent('index');
+ const htmlStream = await ReactDOMServer.renderToReadableStream( , {
+ bootstrapScriptContent,
+ });
+ let responseStream: ReadableStream = htmlStream;
+ responseStream = responseStream.pipeThrough(
+ injectRSCPayload(rscStream2),
+ );
+ return responseStream;
+}
diff --git a/packages/plugin-rsc/examples/navigation/src/server.tsx b/packages/plugin-rsc/examples/navigation/src/server.tsx
new file mode 100644
index 00000000..8dd1dd77
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/server.tsx
@@ -0,0 +1,61 @@
+import * as ReactServer from '@vitejs/plugin-rsc/rsc';
+import { StateNavigator } from 'navigation';
+import stateNavigator from './stateNavigator.ts';
+
+export default async function handler(request: Request): Promise {
+ let url: string = '';
+ let view: any;
+ const serverNavigator = new StateNavigator(stateNavigator);
+ if (request.method === 'GET') {
+ let reqUrl = new URL(request.url);
+ url = `${reqUrl.pathname}${reqUrl.search}`;
+ const App = (await import('./App.tsx')).default;
+ view = ;
+ }
+ if (request.method === 'POST') {
+ const sceneViews: any = {
+ people: await import('./People.tsx'),
+ person: await import('./Person.tsx'),
+ friends: await import('./Friends.tsx')
+ };
+ const {url: reqUrl, sceneViewKey} = await request.json();
+ url = reqUrl;
+ const SceneView = sceneViews[sceneViewKey].default;
+ view = ;
+ }
+ if (request.method === 'PUT') {
+ const {state, data, crumbs} = await request.json();
+ let fluentNavigator = serverNavigator.fluent();
+ for (let i = 0; i < crumbs.length; i++) {
+ fluentNavigator = fluentNavigator.navigate(crumbs[i].state, crumbs[i].data);
+ }
+ fluentNavigator = fluentNavigator.navigate(state, data);
+ url = fluentNavigator.url;
+ const App = (await import('./App.tsx')).default;
+ view = ;
+ }
+ try {
+ serverNavigator.navigateLink(url);
+ } catch(e) {
+ return new Response('Not Found', { status: 404 });
+ }
+ const {NavigationHandler} = await import('navigation-react');
+ const root = (
+ <>
+
+ {view}
+
+ >
+ );
+ const rscStream = ReactServer.renderToReadableStream({root});
+ if (request.method !== 'GET') {
+ return new Response(rscStream, {headers: {'Content-type': 'text/x-component'}});
+ }
+ const ssrEntryModule = await import.meta.viteRsc.loadModule('ssr', 'index');
+ const htmlStream = await ssrEntryModule.renderHTML(rscStream);
+ return new Response(htmlStream, {headers: {'Content-type': 'text/html'}});
+}
+
+if (import.meta.hot) {
+ import.meta.hot.accept();
+}
diff --git a/packages/plugin-rsc/examples/navigation/src/stateNavigator.ts b/packages/plugin-rsc/examples/navigation/src/stateNavigator.ts
new file mode 100644
index 00000000..e33e2758
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/src/stateNavigator.ts
@@ -0,0 +1,18 @@
+import { StateNavigator } from 'navigation'
+
+const stateNavigator = new StateNavigator([
+ {
+ key: 'people',
+ route: '{page?}',
+ defaults: { page: 1, sort: 'asc', size: 10 },
+ },
+ {
+ key: 'person',
+ route: 'person/{id}+/{show}',
+ defaults: { id: 0, show: false },
+ trackCrumbTrail: true,
+ },
+])
+stateNavigator.historyManager.stop()
+
+export default stateNavigator
diff --git a/packages/plugin-rsc/examples/navigation/tsconfig.json b/packages/plugin-rsc/examples/navigation/tsconfig.json
new file mode 100644
index 00000000..77438d9d
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "skipLibCheck": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+ "moduleResolution": "Bundler",
+ "module": "ESNext",
+ "target": "ESNext",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "types": ["vite/client", "@vitejs/plugin-rsc/types"],
+ "jsx": "react-jsx"
+ }
+}
diff --git a/packages/plugin-rsc/examples/navigation/vite.config.ts b/packages/plugin-rsc/examples/navigation/vite.config.ts
new file mode 100644
index 00000000..6c734151
--- /dev/null
+++ b/packages/plugin-rsc/examples/navigation/vite.config.ts
@@ -0,0 +1,22 @@
+import rsc from '@vitejs/plugin-rsc'
+import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite'
+
+export default defineConfig({
+ clearScreen: false,
+ plugins: [
+ react(),
+ rsc({
+ entries: {
+ client: './src/client.tsx',
+ ssr: './src/server.ssr.tsx',
+ rsc: './src/server.tsx',
+ },
+ ignoredPackageWarnings: ['navigation-react'],
+ }),
+ ],
+ optimizeDeps: {
+ include: ['navigation'],
+ exclude: ['navigation-react'],
+ },
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 889b162a..341d1f67 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -601,6 +601,37 @@ importers:
specifier: 19.1.0-rc.2
version: 19.1.0-rc.2
+ packages/plugin-rsc/examples/navigation:
+ dependencies:
+ '@vitejs/plugin-rsc':
+ specifier: latest
+ version: link:../..
+ navigation:
+ specifier: ^6.3.0
+ version: 6.3.0
+ navigation-react:
+ specifier: ^4.12.0
+ version: 4.12.0(navigation@6.3.0)(react@19.1.0)
+ react:
+ specifier: ^19.1.0
+ version: 19.1.0
+ react-dom:
+ specifier: ^19.1.0
+ version: 19.1.0(react@19.1.0)
+ devDependencies:
+ '@types/react':
+ specifier: ^19.1.8
+ version: 19.1.8
+ '@types/react-dom':
+ specifier: ^19.1.6
+ version: 19.1.6(@types/react@19.1.8)
+ '@vitejs/plugin-react':
+ specifier: latest
+ version: link:../../../plugin-react
+ vite:
+ specifier: ^7.0.2
+ version: 7.0.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.7.1)
+
packages/plugin-rsc/examples/no-ssr:
dependencies:
'@vitejs/plugin-rsc':
@@ -3856,6 +3887,15 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ navigation-react@4.12.0:
+ resolution: {integrity: sha512-sYCWeqyB7Ev4nLcIs2REkA9WbL74XpFagto9VOVPloKwVb6zgSlRbt0CwQKtFVLU/iL4VncdSmlp6OnJaSSfYw==}
+ peerDependencies:
+ navigation: '*'
+ react: '*'
+
+ navigation@6.3.0:
+ resolution: {integrity: sha512-FzpKHKcqn0c1Qcm6e6UHSJY2TvIWHHXMRYzSHXdCVOTnnMQO012gzsqtxu5aCD4qNK9ffAgn48wOn3umczgVaw==}
+
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -7845,6 +7885,13 @@ snapshots:
natural-compare@1.4.0: {}
+ navigation-react@4.12.0(navigation@6.3.0)(react@19.1.0):
+ dependencies:
+ navigation: 6.3.0
+ react: 19.1.0
+
+ navigation@6.3.0: {}
+
neo-async@2.6.2: {}
node-domexception@1.0.0: {}