DEV Community

Cover image for React 18 server components deep dive

React 18 server components deep dive

Ruslan Kh. on May 21, 2023

React Server Components (RSC) is a promising feature that can have a significant impact on page load performance, bundle size, and the way we write...
Collapse
 
tresorama profile image
Jacopo Marrone @tresorama • Edited

Is not prohibited to import a Server Component into a Client component?
In <App> you import <Message>.

Collapse
 
iamhectorsosa profile image
Hector Sosa

It is possible to do both ways. You just need to make sure to keep state out of Server components and also you need to make sure to write the right directive at the beginning of the file.

A good example is when you have a context provider in your application. The recommendation is to put it as close as possible in your component tree, but just because you use a Theme Context or a Authentication Context it doesn't mean that the rest of the components in the tree cannot be server components. Hope that explains. Cheers

Collapse
 
tresorama profile image
Jacopo Marrone @tresorama • Edited

Before reading this post, that shows this implementation :

// hello.client.js
"use client"

import {useState} from 'react';
import Message from './Message.server';

function Hello_Client() {
  const [selectedId, setSelectedId] = useState(1);
  return (
    <div>
      <button onClick={() => setSelectedId(selectedId + 1)}>
        Next
      </button>
      <Message id={selectedId} />
    </div>
  );
}

// message.server.js

import {db} from './db.server';

function Message({id}) {
  const message = db.messages.get(id);
  return (
    <div>
      <h1>{message.title}</h1>
      <p>{message.body}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

my idea to do the same is to implement with cookies as communication between server and client tree.
Like so:

// root.server.js
import Message from './Message.server';
import Hello_Client from './hello.client';

function Root_Server() {
  const selectedMessageID = getFromCookie('message-id');
  return (
    <Hello_Client>
      <Message={selectedMessageID} />
    </Hello_Client>
  );
}

// hello.client.js
"use client";

function Hello_Client(props) {
  const handleSelect = ( ) => {
    const currentMessageID = getFromCookie('message-id');
    setCookie('message-id', currentMessageID + 1);
    revalidateRoute('/'); // tell server tree to rerender with new data
  }

  return (
    <div>
      <button onClick={handleSelect}>
        Next
      </button>
      {children}
    </div>
  );
}

// message.server.js

import {db} from './db.server';

function Message({id}) {
  const message = db.messages.get(id);
  return (
    <div>
      <h1>{message.title}</h1>
      <p>{message.body}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Are both ok ?
Is there a risk of exposing server stuff with first approach ?

Edited: added revalidateRoute() in click handler

Thread Thread
 
xakrume profile image
Ruslan Kh.

Both approaches are possible, but they have different tradeoffs:

  • React Server Components (RSC): This is the recommended approach. It allows the server and client to collaborate on rendering, improving performance and reducing the amount of JavaScript sent to the client.
  • Use cookies: This is unconventional and generally not recommended due to potential performance issues, security risks (such as XSS and CSRF attacks), and added complexity.

As for exposing server stuff, RSCs are designed to be secure. They run only on the server and have no direct access to the client. Any data returned by a server component is included in the HTML response, much like any other data included in an HTTP response.

Thread Thread
 
xakrume profile image
Ruslan Kh.

Modify the App.client.js file to use cookies:

// App.client.js

import {useState, useEffect} from 'react';
import Message from './Message.server';

function App() {
  const [selectedId, setSelectedId] = useState(() => {
    // Read the initial value from the cookie
    return Number(document.cookie.replace(/(?:(?:^|.*;\s*)selectedId\s*\=\s*([^;]*).*$)|^.*$/, "$1")) || 1;
  });

  useEffect(() => {
    // Update the cookie whenever selectedId changes
    document.cookie = `selectedId=${selectedId}`;
  }, [selectedId]);

  return (
    <div>
      <button onClick={() => setSelectedId(selectedId + 1)}>
        Next
      </button>
      <Message id={selectedId} />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, we'll use the useState and useEffect hooks to read the initial selectedId value from a cookie and update the cookie whenever the selectedId changes.

Note that this is a simplified example. In a real application, you would want to use a more robust method for managing cookies, such as the js-cookie library.

Thread Thread
 
tresorama profile image
Jacopo Marrone @tresorama • Edited

This is from Next.js docs:

"use client" sits between server-only and client code. It's placed at the top of a file, above imports, to define the cut-off point where it crosses the boundary from the server-only to the client part. Once "use client" is defined in a file, all other modules imported into it, including child components, are considered part of the client bundle.

Since Server Components are the default, all components are part of the Server Component module graph unless defined or imported in a module that starts with the "use client" directive.

Good to know:

"use client" does not need to be defined in every file. The Client module boundary only needs to be defined once, at the "entry point", for all modules imported into it to be considered a Client Component.

I was assuming that this behavior is a React thing and not a Next thing.
There are some place where this is explained from a plain React side?

Collapse
 
pintovsa profile image
Rizaka23

Hi, Importing server components into a client component depends on the context and technology you are using. Typically, server components contain business logic and single CPU, databases and other sensitive data, and importing them directly into a client component can pose potential risks to the security and efficiency of the application.

Collapse
 
trongtai37 profile image
trongtai37

RSC is currently on experimental stage.
Some APIs and rules is changing and may be changed in the future.
It's worth mentioning this reference, which is maintained officially by React team.
github.com/reactjs/rfcs/blob/main/...

Btw, React team and Next.js team is collaborating closely, improve RSC and ship it ASAP as a playground for community before this feature is officially stable .
nextjs.org/docs/getting-started/re...

Collapse
 
iamhectorsosa profile image
Hector Sosa

Nice illustrations! Framing them would've been great to see! We've built a simple OSS tool to help with screenshots. Check it out and let us know what you think! github.com/ekqt/screenshot I'd appreciate giving it a Star on GitHub if you find it helpful! Cheers!

Collapse
 
daggett206 profile image
Andrey Smirnov

thanks for the post! I haven't found explanation for why we don't have "await" to get a db record here:

function Message({id}) {
  const message = db.messages.get(id); // where is await?
  return (
    <div>
      <h1>{message.title}</h1>
      <p>{message.body}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
muhammadazfaraslam profile image
Muhammad Azfar Aslam

Can we use react hooks like (useState or useEffect) inside Message.server.js file?

Collapse
 
raibtoffoletto profile image
Raí B. Toffoletto

No, those are features for client components.

Collapse
 
muhammadazfaraslam profile image
Muhammad Azfar Aslam

So React js is just copying Next js.

Thread Thread
 
raibtoffoletto profile image
Raí B. Toffoletto

NextJS IS a React framework ;) they aren't copying, but rather implementing it.