Skip to main content

Quick start

This guide will walk you through creating your first page with COSMOS RSC, demonstrating React Server Components, streaming, and server actions.

Create your first page

1

Create a new page file

Create a new file in the app/pages/ directory. The filename determines the route.
app/pages/hello.js
export default function Page() {
  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-4xl font-bold'>Hello, COSMOS RSC!</h1>
      <p className='text-gray-700'>Welcome to React Server Components.</p>
    </div>
  );
}
This page is now available at /hello. By default, all components are Server Components.
2

Add async data fetching

Server Components can be async and fetch data directly without useEffect or separate API routes.
app/pages/hello.js
// Simulated database call
async function fetchUserData() {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
  };
}

export default async function Page() {
  const user = await fetchUserData();

  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-4xl font-bold'>Hello, {user.name}!</h1>
      <p className='text-gray-700'>Email: {user.email}</p>
    </div>
  );
}
Server Components can directly access databases, file systems, and server-side APIs without exposing sensitive data to the client.
3

Use server-only APIs

Import server-only utilities like cookies directly in your Server Components.
app/pages/hello.js
import { cookies } from '#cosmos-rsc/server';

async function fetchUserData() {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
  };
}

export default async function Page() {
  const user = await fetchUserData();
  const cookieManager = cookies();
  const lastVisit = cookieManager.get('last_visit');

  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-4xl font-bold'>Hello, {user.name}!</h1>
      <p className='text-gray-700'>Email: {user.email}</p>
      {lastVisit && (
        <p className='text-sm text-gray-500'>
          Last visit: {new Date(lastVisit).toLocaleString()}
        </p>
      )}
    </div>
  );
}
4

Add streaming with Suspense

Use React Suspense to stream components progressively as data becomes available.
app/pages/hello.js
import { Suspense } from 'react';
import { cookies } from '#cosmos-rsc/server';

async function SlowData({ delay, label }) {
  await new Promise((resolve) => setTimeout(resolve, delay));
  return (
    <div className='rounded bg-white p-4 shadow'>
      <h3 className='font-medium'>{label}</h3>
      <p>Data loaded after {delay}ms</p>
    </div>
  );
}

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

export default function Page() {
  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <h1 className='mb-8 text-4xl font-bold'>Streaming Demo</h1>
      
      <div className='grid gap-4'>
        <Suspense fallback={<LoadingCard />}>
          <SlowData delay={1000} label='Fast Component' />
        </Suspense>

        <Suspense fallback={<LoadingCard />}>
          <SlowData delay={3000} label='Slow Component' />
        </Suspense>
      </div>
    </div>
  );
}
Each Suspense boundary streams independently, showing loading states until data is ready.
5

Create a server action

Server actions handle form submissions without needing API endpoints.Create a new file for your server actions:
app/actions/my-actions.js
'use server';

import { cookies } from '#cosmos-rsc/server';

export async function contactAction(formData) {
  // Simulate server processing
  await new Promise((resolve) => setTimeout(resolve, 1000));

  const name = formData.get('name');
  const email = formData.get('email');
  const message = formData.get('message');

  // Simple validation
  if (!name || !email || !message) {
    return { success: false };
  }

  // Store submission timestamp in cookie
  const cookieManager = cookies();
  cookieManager.set('last_submission', new Date().toISOString(), {
    maxAge: 24 * 60 * 60, // 24 hours
    path: '/',
  });

  console.log('Contact form submitted:', { name, email, message });

  return { success: true };
}
Server action files must include 'use server' at the top to mark them as server-only.
6

Create a form with server action

Use your server action in a form. You can also create a client component for enhanced UX.First, create a submit button component:
app/components/submit-button.js
'use client';

import { useFormStatus } from 'react-dom';

export function SubmitButton({ children, ...props }) {
  const { pending } = useFormStatus();

  return (
    <button
      type='submit'
      disabled={pending}
      className='rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700'
      {...props}
    >
      {pending ? 'Submitting...' : children}
    </button>
  );
}
Then use it in your page:
app/pages/contact.js
import { contactAction } from '../actions/my-actions';
import { SubmitButton } from '../components/submit-button';

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

      <form action={contactAction} className='max-w-lg space-y-4'>
        <div>
          <label htmlFor='name' className='block text-sm font-medium'>
            Name
          </label>
          <input
            type='text'
            id='name'
            name='name'
            className='mt-1 block w-full rounded-md border px-4 py-2'
          />
        </div>

        <div>
          <label htmlFor='email' className='block text-sm font-medium'>
            Email
          </label>
          <input
            type='email'
            id='email'
            name='email'
            className='mt-1 block w-full rounded-md border px-4 py-2'
          />
        </div>

        <div>
          <label htmlFor='message' className='block text-sm font-medium'>
            Message
          </label>
          <textarea
            id='message'
            name='message'
            rows={4}
            className='mt-1 block w-full rounded-md border px-4 py-2'
          />
        </div>

        <SubmitButton>Submit</SubmitButton>
      </form>
    </div>
  );
}
The SubmitButton uses 'use client' to access React hooks like useFormStatus, while the form and page remain Server Components.
7

Run your application

Start the development server to see your pages in action:
NODE_ENV=development npm start
Visit your pages:
  • http://localhost:3000/hello - Your first page
  • http://localhost:3000/contact - Contact form with server action
The server automatically rebuilds when you make changes to your code.

Next steps

Now that you’ve created your first pages, explore more features:

Routing

Learn about file-system based routing and navigation

Server Components

Deep dive into React Server Components

Server Actions

Master server actions for forms and mutations

Styling

Customize your application with Tailwind CSS

Build docs developers (and LLMs) love