Skip to main content

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
  • /aboutapp/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:
core/server/index.js
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:
app/pages/search.js
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>
  );
}
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:
core/server/index.js
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 };
}
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:
app/pages/index.js
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:
app/root-layout.js
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