Skip to main content
Pages in COSMOS RSC are React Server Components that map directly to URL routes.

Creating your first page

1

Create a page file

Create a new file in the app/pages/ directory. The filename determines the route:
app/pages/about.js
export default function AboutPage() {
  return (
    <div>
      <h1>About Us</h1>
      <p>Welcome to our site!</p>
    </div>
  );
}
This page is now accessible at /about.
2

Add styling

Use Tailwind CSS classes to style your page:
app/pages/about.js
export default function AboutPage() {
  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-4xl font-bold'>About Us</h1>
      <p className='text-gray-700'>Welcome to our site!</p>
    </div>
  );
}
3

Fetch data on the server

Make your page component async to fetch data:
app/pages/about.js
async function fetchTeamData() {
  const res = await fetch('https://api.example.com/team');
  return res.json();
}

export default async function AboutPage() {
  const team = await fetchTeamData();

  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-4xl font-bold'>Our Team</h1>
      <ul>
        {team.map(member => (
          <li key={member.id}>{member.name}</li>
        ))}
      </ul>
    </div>
  );
}

Nested routes

Create nested routes by organizing pages in subdirectories:
app/pages/
├── index.js              # /
├── blog.js               # /blog
└── blog/
    ├── post-1.js         # /blog/post-1
    └── post-2.js         # /blog/post-2
Example nested page:
app/pages/blog/post-1.js
export default function BlogPost() {
  return (
    <article>
      <h1>My First Post</h1>
      <p>Content goes here...</p>
    </article>
  );
}

Server Components by default

All pages are React Server Components, which means they:
  • Run only on the server
  • Can use async/await for data fetching
  • Have access to server-only APIs
  • Don’t increase the client bundle size
Here’s a real example from the COSMOS RSC demo:
app/pages/features/server-components.js
import { cookies } from '#cosmos-rsc/server';

// Async server-only component
async function UserProfile() {
  const userData = await fetchUserData();
  const cookieManager = cookies();
  const lastVisit = cookieManager.get('last_visit');

  return (
    <div className='rounded bg-white p-4 shadow'>
      <h3 className='mb-2 text-lg font-medium'>User Profile</h3>
      <div className='space-y-2 text-gray-600'>
        <p>Name: {userData.name}</p>
        <p>Email: {userData.email}</p>
        {lastVisit && (
          <p className='text-sm'>
            Last visit: {new Date(lastVisit).toLocaleString()}
          </p>
        )}
      </div>
    </div>
  );
}

export default function ServerComponentsDemo() {
  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-3xl font-bold'>Server Components Demo</h1>
      <UserProfile />
    </div>
  );
}

Using Suspense for streaming

Wrap slow components in Suspense to stream content progressively:
app/pages/features/streaming.js
import { Suspense } from 'react';

async function SlowData({ delay }) {
  await new Promise(resolve => setTimeout(resolve, delay));
  return <div>Data loaded after {delay}ms</div>;
}

function LoadingCard() {
  return (
    <div className='animate-pulse rounded bg-gray-50 p-4 shadow'>
      <div className='h-4 w-3/4 rounded bg-gray-200'></div>
    </div>
  );
}

export default function StreamingDemo() {
  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-3xl font-bold'>Streaming Demo</h1>

      <div className='grid gap-4'>
        <Suspense fallback={<LoadingCard />}>
          <SlowData delay={1000} />
        </Suspense>

        <Suspense fallback={<LoadingCard />}>
          <SlowData delay={3000} />
        </Suspense>
      </div>
    </div>
  );
}

Adding client interactivity

To add client-side interactivity, import client components:
app/pages/interactive.js
import { Counter } from '../components/counter';

export default function InteractivePage() {
  return (
    <div>
      <h1>Interactive Page</h1>
      <Counter />
    </div>
  );
}
app/components/counter.js
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Linking between pages

Use standard HTML <a> tags for navigation. COSMOS RSC automatically handles client-side routing:
export default function HomePage() {
  return (
    <div>
      <h1>Home</h1>
      <nav>
        <a href='/about' className='text-blue-600 hover:underline'>
          About
        </a>
        <a href='/blog' className='text-blue-600 hover:underline'>
          Blog
        </a>
      </nav>
    </div>
  );
}
For programmatic navigation, use the useRouter hook in client components (see Client Navigation).

Build docs developers (and LLMs) love