Server components: Closing the gap between frontend and backend

Sandro Maglione

Sandro Maglione

Software development

I've always considered myself a "frontend" developer. Lately this started to change ๐Ÿค”

With React 19 and Effect the gap between "front/back" is getting slimmer.

With Server components as the main culprit, "full-stack" developers are more real than ever.

This is how from my perspective ๐Ÿ‘‡


What frontend is all about (when done right)

Frontend development is a state management problem.

  1. You get some data from somewhere ("API")
  2. You show this data to the user ("UI")
  3. You allow the user to interact with the data ("State")
  4. You send the data back to the server ("API")

That's it.

In the process you must take care of:

  • Performance
  • Accessibility
  • Bundle size
  • User experience
  • (and more ๐Ÿฅต)

Things like data storage ("database"), file system (read/write files), observability (traces), these were all handled somewhere else for you.

Now this may not be the case anymore.

Closing the gap: Server components

With server components the game changes.

From the docs:

Server components can run at build time to read from the filesystem or fetch static content [...]

Meaning: you do some "backend" stuff that you then pass on to the "frontend".

The new frontend

Here is a concrete example I worked on recently: MDX.

Here is how it works:

  1. I have some files locally that I read using Node's file system API (using Effect's FileSystem)
  2. I use the mdx compiler (@mdx-js/mdx) to covert MDX to javascript
  3. I apply styles to code blocks using shiki
  4. I send all the data to the "frontend" using a server component
page.tsx
const ContentLayer = Content.Content.Live({
  contentDir: Config.string("CONTENT_DIR"),
});

export const main = Content.Content.pipe(
    /// Compute the data (aka "backend") ๐Ÿ’๐Ÿผโ€โ™‚๏ธ
    Effect.provide(ContentLayer)
  );

export default async function Page() {
  const contentOption = await main.pipe(Effect.runPromise);
  return Option.match(contentOption, {
    onNone: () => <span>Invalid content</span>,
    onSome: ({ content, step }) => (
      <div className="max-w-4xl mx-auto">
        <Nav step={step} />
        <Tutorial content={content} />
      </div>
    ),
  });
}

All of this is backend. What's more, this is the "core" of the app ๐Ÿ’๐Ÿผโ€โ™‚๏ธ

The frontend is only responsible to display this data in the UI and manage users interactions.

Result: this is more "backend" than "frontend", even when using React ๐Ÿ˜ฒ

I wrote an article highlighting all the details of how to use MDX with React ๐Ÿ‘ˆ

There is more.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 700+ readers.

The finishing blow: Local first

This distinction becomes even slimmer to the point of disappearing with local first.

With local first the client becomes your server ๐Ÿ’ก

All the data is stored locally on the user device, so no need to interact with any "API" both inbound and outbound.

I am flying to Berlin this week for localfirstconf โœˆ๏ธ

Be ready for some cool updates on this next week ๐Ÿ”œ


React 19 stable will be released soon, and with it many new frameworks will start to support server components.

Now it's the time to adapt and start closing the gap. Furthermore, Effect is here to help, and to make everything more interesting and fun ๐Ÿš€

See you next ๐Ÿ‘‹

Start here.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 700+ readers.