Skip to main content

Overview

React Server Components (RSC) are components that run exclusively on the server. They have direct access to server resources like databases, file systems, and environment variables, and never send JavaScript to the client.

Server vs client components

By default, all components in COSMOS RSC are server components unless marked with 'use client'.
// No directive needed - this is a server component
async function UserProfile() {
  // Can directly access server resources
  const userData = await fetchUserData();
  
  return (
    <div>
      <h3>User Profile</h3>
      <p>Name: {userData.name}</p>
      <p>Email: {userData.email}</p>
    </div>
  );
}

Server component benefits

Server components provide several key advantages:
Server components don’t ship any JavaScript to the browser. The component code stays on the server, reducing bundle size.
Access databases, file systems, and environment variables directly without creating API endpoints.
API keys, database credentials, and sensitive logic never reach the client.
Each server component is automatically split and loaded on-demand.

Async server components

Server components can be async functions, allowing you to await data directly:
app/pages/features/server-components.js
// Simulated database call
async function fetchUserData() {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return {
    id: 1,
    name: 'John Doe',
    email: '[email protected]',
    preferences: {
      theme: 'light',
      notifications: true,
    },
  };
}

// Server component with async data fetching
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>
        <p>Theme: {userData.preferences.theme}</p>
        {lastVisit && (
          <p className='text-sm'>
            Last visit: {new Date(lastVisit).toLocaleString()}
          </p>
        )}
      </div>
    </div>
  );
}
Async components are only supported in React Server Components. Client components cannot be async.

Accessing server resources

Server components can import and use server-only utilities:
import { cookies } from '#cosmos-rsc/server';
import { db } from '../lib/database';

async function DashboardPage() {
  // Read cookies
  const cookieManager = cookies();
  const userId = cookieManager.get('user_id');
  
  // Query database
  const user = await db.users.findById(userId);
  const posts = await db.posts.findByAuthor(userId);
  
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      <PostList posts={posts} />
    </div>
  );
}

Using cookies

The cookies() function provides access to request cookies:
import { cookies } from '#cosmos-rsc/server';

async function ServerComponent() {
  const cookieManager = cookies();
  
  // Get a cookie
  const theme = cookieManager.get('theme');
  
  // Set a cookie
  cookieManager.set('last_visit', new Date().toISOString(), {
    maxAge: 24 * 60 * 60, // 24 hours
    path: '/',
  });
  
  return <div>Theme: {theme}</div>;
}
The cookies() function can only be called during the RSC render phase, not during server actions.

Multiple async operations

You can compose multiple server components with different data requirements:
app/pages/features/server-components.js
// Component 1: User data
async function UserProfile() {
  const userData = await fetchUserData();
  return <div>{/* User profile UI */}</div>;
}

// Component 2: Weather data
async function WeatherWidget() {
  const weather = await fetchWeatherData();
  return (
    <div className='rounded bg-blue-50 p-4 shadow'>
      <h3 className='mb-2 text-lg font-medium'>Current Weather</h3>
      <div className='space-y-1'>
        <p className='text-gray-600'>Location: {weather.location}</p>
        <p className='text-2xl'>{weather.temperature}°C</p>
        <p className='text-gray-500'>{weather.condition}</p>
      </div>
    </div>
  );
}

// Page combining both components
export default function ServerComponentsDemo() {
  return (
    <div className='grid gap-6 md:grid-cols-2'>
      <UserProfile />
      <WeatherWidget />
    </div>
  );
}
Each async component fetches its own data independently. React handles the coordination.

Composing with client components

Server components can render client components and pass data via props:
// Server component
async function ProductPage({ productId }) {
  const product = await fetchProduct(productId);
  const reviews = await fetchReviews(productId);
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* Pass server data to client component */}
      <AddToCartButton product={product} />
      <ReviewList reviews={reviews} />
    </div>
  );
}

// Client component
'use client';

function AddToCartButton({ product }) {
  const [added, setAdded] = useState(false);
  
  return (
    <button onClick={() => {
      addToCart(product.id);
      setAdded(true);
    }}>
      {added ? 'Added!' : 'Add to Cart'}
    </button>
  );
}
Client components can be children of server components, but server components cannot be children of client components.

Serializable props

Data passed from server components to client components must be serializable:
// Plain objects, arrays, primitives
<ClientComponent 
  user={{ name: 'John', age: 30 }}
  items={[1, 2, 3]}
  message="Hello"
/>

Server-only code

Use the server-only package to ensure code never runs on the client:
lib/database.js
import 'server-only';

export async function queryDatabase(sql) {
  // This code will error if accidentally imported in a client component
  return db.query(sql);
}

Rendering flow

When a page is requested:
  1. Server receives the request
  2. Server components execute and fetch data
  3. React renders server components to RSC format
  4. RSC stream includes data and client component references
  5. Server renders HTML with React DOM Server
  6. Client hydrates and renders client components

Server component limitations

Server components have some restrictions:
  • Cannot use React hooks like useState, useEffect
  • Cannot use browser APIs like window or document
  • Cannot attach event handlers like onClick
  • Cannot use context created with createContext
For these features, use client components instead.

Example: Full server component page

Here’s the complete server components demo from the COSMOS RSC project:
app/pages/features/server-components.js
import { cookies } from '#cosmos-rsc/server';
import { NavigationTransition } from '../../components/navigation-transition';

async function fetchUserData() {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return {
    id: 1,
    name: 'John Doe',
    email: '[email protected]',
    preferences: { theme: 'light', notifications: true },
  };
}

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>User Profile</h3>
      <p>Name: {userData.name}</p>
      <p>Email: {userData.email}</p>
      {lastVisit && <p>Last visit: {new Date(lastVisit).toLocaleString()}</p>}
    </div>
  );
}

export default function ServerComponentsDemo() {
  return (
    <div className='mx-auto max-w-4xl px-4 py-12'>
      <NavigationTransition>
        <h1>Server Components Demo</h1>
      </NavigationTransition>
      
      <div className='grid gap-6 md:grid-cols-2'>
        <UserProfile />
      </div>
    </div>
  );
}

Next steps

Streaming

Learn about SSR streaming with Suspense

Server Actions

Execute server code from client components

Build docs developers (and LLMs) love