Astro and React Server Components: Two Worlds, Same Shape

If you’re an Astro developer, you already work with a clear separation between two kinds of UI code. Astro Components (files with the .astro extension) execute only on the server or at build time. They can read from the filesystem, call internal services, or query a database, but they can’t be interactive. For anything client-side, you drop in “Client Islands” — React, Vue, or other framework components that handle interactivity and can render their own framework-specific children.

That split creates a one-way data flow: Astro Components do all the preprocessing and hand off to the interactive Client Islands. The two never mix directly, and an Astro Component can’t be rendered from inside a Client Island.

React Server Components (RSC) operates on the same principle, but with different names: Server Components play the role of Astro Components, and Client Components take the place of Client Islands. If you understand the Astro mental model, you already have most of what you need for RSC.

Here’s a React Server Component that does what the Astro example above does:

import { readFile } from 'fs/promises';
import { LikeButton } from './LikeButton';
 
async function PostPreview({ slug }) {
  const title = await readFile(`./posts/${slug}/title.txt`, 'utf8');
  return (
    <article>
      <h1>{title}</h1>
      <LikeButton />
    </article>
  );
}
'use client';
 
import { useState } from 'react';
 
export function LikeButton() {
  const [liked, setLiked] = useState(false);
 
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'} Like
    </button>
  );
}

How the Syntax Differs

There are a few key differences between the two models:

  • React Server Components are regular JavaScript functions, not single-file templates. Props come in as function arguments rather than from Astro.props, and there’s no separate template syntax.
  • In Astro, the .astro extension is what marks the server-side boundary. As soon as you import a Client Island, you’ve left the Astro world. In RSC, the same boundary is declared with the 'use client' directive, which acts as the “door” between the two worlds.
  • Astro gives you directives like client:load to control whether an Island is server-rendered HTML or hydrated on the client. RSC leaves that decision out of the user’s hands. If a component needs interactivity, it should keep 'use client'; if it doesn’t, remove it and the component stays server-only when imported from the server side.

That last point is important because it highlights a fundamental difference. In Astro, the two worlds use different file formats, which makes the boundary visually obvious. A component can’t act as both an Astro Component and a Client Island — they’re distinct types with distinct syntaxes.

In RSC, both sides are just React. A component like <Markdown /> that doesn’t use any client- or server-specific features can be imported from either world. If it’s imported from a server component, it behaves like an Astro Component; if imported from a client component, it behaves like a Client Island. This isn’t a special mechanism — it’s simply how function imports work.

RSC relies on build-time errors to prevent you from accidentally using a database on the client or useState on the server. When that happens, you “cut a door” with 'use client' and the error goes away.

Why the Ambiguity Is Both Painful and Powerful

That fluidity is a double-edged sword. On one hand, it makes RSC harder to learn because you’re constantly asking yourself which world you’re in. On the other hand, it solves real Astro limitations:

  • No costly conversions. In Astro, if you build a UI as an Astro Component and later need it as an interactive Client Island, you have to convert it to that framework’s syntax, or duplicate it. In RSC, you can extract the shared parts and import them from either side. Adding or removing 'use client' (and moving the “door” up or down the import chain) is far less disruptive.
  • Nesting interactive behavior composes properly. In Astro, nesting Client Islands inside other Client Islands still leaves them as separate roots for the framework, so React or Vue context can’t flow between them. RSC uses a single React tree, so a client context provider can wrap server components, and any client child below can use that context. RSC gives you “fractal” islands.
  • In-place refresh without losing client state. Astro components output HTML, so a server-side refresh means a full page reload (or manual View Transitions work). When RSC runs on an actual server, the server can return a fresh description of the tree — formatted as JSON-like data — that merges into the existing stateful client tree. Interactive parts keep their state while server data updates beneath them.
  • MPA thinking, SPA feel. Astro’s page navigation replaces the entire HTML document. RSC’s default output isn’t just HTML — it’s a React tree that can be turned into HTML for the first paint but refetched as JSON for subsequent navigations. You compose pages like a multi-page app but get single-page-app-style navigation that preserves DOM state like input values, scroll positions, and React state.

One Tree, Two Kinds of Code

Astro’s core output format is HTML. That makes its model easy to grasp, and it’s a good fit for mostly static sites. But the more interactive you make an Astro app, the more you’ll feel the friction of moving pieces between Astro Components and isolated Islands.

RSC’s fundamental output is a React tree. That’s more demanding to learn, but it removes the visual distinction between server and client code. The same “map data to UI” patterns apply whether you’re writing a read-only component that hits a database or one that refreshes in response to a user action.

Because both sides are React, everything is integrated into the same tree. A <Suspense> boundary on the client can coordinate all kinds of asynchronous work — waiting on server data, JavaScript, CSS, even images and fonts. Server and client pieces can nest arbitrarily and refresh in place. The price of that integration is that RSC is not just a rendering model; it’s full-stack React.

Frameworks With a Different Scope

Astro ships as a complete framework. RSC, by contrast, is more like a building block. Two officially supported production implementations exist right now: Next.js App Router, which is a full framework, and Parcel RSC, which is not. That means the RSC experience will vary depending on which implementation you use — its tooling and developer experience are still maturing.

Still, the ideas are worth learning. If RSC feels hostile, Astro is a gentler way to get exposed to the same concepts. And if you’ve only ever done client-side React, Astro will show you a model where the server isn’t just an API — it’s part of your component thinking.