Overview
COSMOS RSC uses file-system based routing where each file in the app/pages/ directory automatically becomes a route in your application.
Creating routes
Create a new file in app/pages/ to define a route:
export default function HomePage() {
return (
<div>
<h1>Welcome to COSMOS RSC</h1>
</div>
);
}
These files create the following routes:
/ → app/pages/index.js
/about → app/pages/about.js
Nested routes
Create nested routes using directories:
app/pages/features/streaming.js
export default function StreamingDemo() {
return <div>Streaming Demo</div>;
}
This creates the route /features/streaming.
Example from the demo
The COSMOS RSC demo includes several nested feature routes:
app/pages/
├── index.js → /
└── features/
├── server-components.js → /features/server-components
├── streaming.js → /features/streaming
└── forms.js → /features/forms
Route implementation
The server maps URL paths to page components:
async function requestHandler(req, res) {
// Map URL path directly to file path
const pagePath = `../../app/pages${req.path}`;
try {
// Dynamically import the page component
Page = require(pagePath).default;
} catch (error) {
logger.error(`Failed to import page: ${pagePath}`, error);
res.status(500).send('Internal Server Error');
return;
}
// Render the page
const tree = createElement(Page, { searchParams: { ...req.query } });
}
Every page file must export a default function that returns a React component.
Query parameters
Access query parameters through the searchParams prop:
export default function SearchPage({ searchParams }) {
const query = searchParams.q;
const filter = searchParams.filter;
return (
<div>
<h1>Search Results</h1>
<p>Query: {query}</p>
<p>Filter: {filter}</p>
</div>
);
}
Visiting /search?q=react&filter=new will pass:
{ q: 'react', filter: 'new' }
Client-side navigation
Use the router from context to navigate without full page reloads:
'use client';
import { useRouter } from '#cosmos-rsc/client';
function NavigationButton() {
const router = useRouter();
return (
<button onClick={() => router.push('/about')}>
Go to About
</button>
);
}
Navigation API integration
COSMOS RSC integrates with the browser’s Navigation API:
core/client/components/app/browser-app.js
window.navigation.addEventListener('navigate', (navigateEvent) => {
if (shouldNotInterceptNavigation(navigateEvent)) {
return;
}
const { url } = navigateEvent.destination;
const path = getFullPath(url);
navigateEvent.intercept({
precommitHandler() {
startTransition(() => {
dispatch({
type: APP_ACTION.NAVIGATE,
payload: { path, navigationType },
});
});
},
});
});
This enables:
- Smooth transitions between routes
- Back/forward button support
- Browser history management
RSC payload fetching
Client-side navigation fetches just the RSC payload, not full HTML:
core/client/lib/get-rsc-payload.js
export async function getRSCPayload(url) {
const headers = new Headers();
headers.append('accept', 'text/x-component');
const response = await fetch(url, { headers });
const { tree } = await createFromReadableStream(response.body, {
callServer,
});
return tree;
}
The server detects the text/x-component accept header and returns only the RSC stream:
if (req.headers.accept === 'text/x-component') {
res.setHeader('Content-Type', 'text/x-component');
rscStream.pipe(res);
return;
}
Router cache
Navigated pages are cached to speed up back/forward navigation:
core/client/lib/app-reducer.js
case APP_ACTION.NAVIGATE: {
const { path, navigationType } = action.payload;
// Check cache for traverse (back/forward) navigation
if (navigationType === 'traverse' && routerCache.has(path)) {
return {
...prevState,
tree: routerCache.get(path),
};
}
// Fetch and cache new pages
const tree = await getRSCPayload(path);
routerCache.set(path, tree);
return { ...prevState, tree };
}
Navigation transitions
COSMOS RSC supports view transitions for smooth page changes:
app/components/navigation-transition.js
'use client';
import { useTransition } from 'react';
export function NavigationTransition({ children }) {
const [isPending, startTransition] = useTransition();
return (
<div style={{
viewTransitionName: 'page-title',
opacity: isPending ? 0.7 : 1,
}}>
{children}
</div>
);
}
Use this component to wrap page headings:
app/pages/features/server-components.js
export default function ServerComponentsDemo() {
return (
<div>
<NavigationTransition>
<h1>Server Components Demo</h1>
</NavigationTransition>
{/* Rest of page */}
</div>
);
}
Linking between pages
You can use standard HTML anchor tags for navigation:
export default function Page() {
return (
<div>
<h1>Features</h1>
<ul>
<li>
<a href='/features/server-components'>
Server Components Demo
</a>
</li>
<li>
<a href='/features/streaming'>
Streaming SSR Demo
</a>
</li>
<li>
<a href='/features/forms'>
Server Actions Form Demo
</a>
</li>
</ul>
</div>
);
}
The Navigation API intercepts these clicks and handles them as client-side navigations automatically.
Standard <a> tags work for navigation thanks to Navigation API integration. No special Link component is required.
Root layout
All pages are wrapped in a root layout that provides the HTML document structure:
export default function RootLayout({ children }) {
return (
<html lang='en'>
<head>
<meta charSet='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<title>COSMOS RSC</title>
<link rel='stylesheet' href='/style.css' />
</head>
<body>{children}</body>
</html>
);
}
The {children} slot is filled with your page content via the SlotContext.
Next steps
Server Components
Learn about React Server Components
Server Actions
Handle forms with server actions