Um guia de React Server Components

O que é React Server Components?

O maior apelo do React Server Components, é mover componentes que não tem interação com o usuário do client para o server.

Outro problema encontrado é o número alto de requests feitos, enquanto os usuários esperam para a página ou os dados, estarem prontos, quando eles abrem a página.

Por exemplo uma forma de fazer fetch de dados hoje, é chamar uma API usando o hook useEffect :

useEffect(() => {
axios.get("ENDEREÇO AQUI")
.then((response) => {
// set data into state
setData(response.data);
})
.catch((error) => {
console.log(error);
});
}, []);

Apesar de não ter nada de errado com isso, essa forma de pegar os dados sempre vai ter um custo de tempo para renderizar algo útil para o usuário.

O outro problema claro é sempre o tamanho do bundle. Minificação, code splitting, eliminação de código morto, são algumas técnicas utilizadas para reduzir o tamanho do bundle de uma aplicação React. Porque? Porque impacta diretamente na experiência do usuário, com carregamentos lentos.

Como funciona?

Pode também clonar o repositório demo para entender como funciona a feature

A primeira coisa que notamos são vários packages com versão experimental no arquivo package.json :

"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
"react-fetch": "0.0.0-experimental-3310209d0",
"react-fs": "0.0.0-experimental-3310209d0",
"react-pg": "0.0.0-experimental-3310209d0",
"react-server-dom-webpack": "0.0.0-experimental-3310209d0",

Os pacotes react , react-dom e, react-server-dom-webpack estão usando uma versão experimental que funcionam com o React Server Component, enquanto oreact-fetch , react-fs e react-pg é um grupo de packages usado pela interação com o sistema de input/output ( chamado de libs do React IO)

Outro ponto importante é que o demo funciona em cima do Express.js, o que faz sentido, porque você precisa de um servidor para renderizar os componentes.Mas isso levanta a questão, Server Components apenas em ambientes Java Script? Nada de Go, Java, C# .NET, PHP?

Dentro da pasta src/ você tem três tipos de extensões nos arquivos:

  • .server.js indica um Server Components
  • .client.js indica um React Client Components
  • O habitual .js , pode ser rodado no client ou no server. Dependendo de quem o importa. Também serve para Shared Components. Por exemplo se você importa um Shared Component no server, ele atuará como Server Component, se você importa ele no Client, Será um Client Component.

Quando você inicia sua aplicação com o comando npm start, duas tarefas vão ser executadas simultaneamente:

  • O servidor Node, rodando o scriptserver/api.server.js
  • O build do Webpack rodando o bundle client-side do React, usando o script scripts/build.js

Olhando o script do server podemos ver que o app.server.js está sendo importado para o arquivo:

const ReactApp = require('../src/App.server').default;

e depois é processado com um Node Writable stream:

const {pipeToNodeWritable} = 
require('react-server-dom-webpack/writer');
async function renderReactTree(res, props) {
await waitForWebpack();
const manifest = readFileSync(
path.resolve(__dirname, '../build/react-client-manifest.json'),
'utf8'
);
const moduleMap = JSON.parse(manifest);
pipeToNodeWritable(React.createElement(ReactApp, props), res, moduleMap);
}

Bundle de tamanho Zero

As bibliotecas que importamos no código atualmente, serão lidas do lado do servidor, economizando no tamanho do bundle do client.

Client Component:

//markdown.jsimport marked from 'marked'; // 37.38KBimport sanitizeHtml from 'sanitize-html'; // 208 KBexport default function TextwithMarkdown({Text}) {
return(
<div className="text-markdown"
dangerousSetInnerHTML={{
__html: sanitizeHtml(marked(text)),
}}
/>
);
}

Server Component:

// markdown.server.jsimport marked from 'marked'; // 0KBimport sanitizeHtml from 'sanitize-html'; // 0KBexport default function TextwithMarkdown({Text}) {
return(
<div className="text-markdown"
dangerousSetInnerHTML={{
__html: sanitizeHtml(marked(text)),
}}
/>
);
}

Acesso do backend ao banco de dados:

como você pode ver no exemplo do arquivo NoteList.server.js :

export default function NoteList({searchText}) {
const notes = db.query(
`select * from notes where title ilike $1 order by id desc`,
['%' + searchText + '%']
).rows;


return notes.length > 0 ? (
<ul className="notes-list">
{notes.map((note) => (
<li key={note.id}>
<SidebarNote note={note} />
</li>
))}
</ul>
) : (
<div className="notes-empty">
{searchText
? `Couldn't find any notes titled "${searchText}".`
: 'No notes created yet!'}{' '}
</div>
);
}

Restrições

  • Primeiro, um Server Component não tem nenhuma interatividade (sem useState(),nem useEffect()).
    Uma resolução para esse problema, seria importar Client Components, ( que podem ser interativos) , dentro de um Server Component, por exemplo você pode ter o melhor dos dois mundos:
//Page.server.js// import de componente interativo do client
import Button from '.Button.client.js';
export default Page() {
// data fetch, processamento, outras coisas..
return(
// use o <Button /> e outros componentes interativos aqui
);
}
  • Segundo,props de um Server Component para um Client Component tem que ser serializada na rede ( ex: passar dados como strings, JSON e JSX mas não podem passar em funções JavaScript) . Isso é porque os Server Components renderizados tem que ser enviados pela rede.

Rotas

Quais as vantagens?

  • Desenvolvimento mais fácil, por ter acesso a recursos de servidor como : banco de dados,filesystem, micro serviços, etc.).
  • Melhor performance, pois evitamos a latência de rede entre servidor e cliente.
  • Menor tamanho do bundle. Libs que forem usados só no servidor não precisam ser usados no client : (como loadash, rambda, moment,e etc.), além dos Server Components nunca serem incluidos no bundle, e lidos pelo client.
  • Segurança: Fazer queries no lado do servidor sempre é mais seguro que no client. Além de não expor as requisições
  • Pode fazer queries GraphQL
  • Pode acessar diretamente bancos de dados PostgreSQL com o package react-pg

Também oferece algumas vantagens para desenvolvedores React:

  • Code splitting automático. (A técnica de dividir o código em bundles menores, para o cliente ler apenas o que é necessário) Atualmente, Desenvolvedores React tem que fazer um esforço para poder implementar o code splitting algo como:
const MyComponent = React.lazy(() => import('./MyComponent.js'));

no futuro os Server Components vai lidar com isso automaticamente. Isso significa que poderemos importar diretamente como já fazemos:

import MyComponent from './MyComponent.client.js';

Como os Server Components são diferentes de SSR (ex: Next.js)?

React Server Components é diferente.Como mostrado no demo, Server Components não é renderizado como HTML mas como um stream de formato especial enviado ao client. O stream não tem um padrão de protocolo, mas se parece muito com json. Aqui um exemplo:

M1:{"id":"./src/SearchField.client.js","chunks":["client5"],"name":""}

No SSR , usamos apenas para o render inicial, Server Components podem puxar os dados multiplas vezes para fazer re-render ( no exemplo do demo, os posts de markdown)

Server Component x Client Components

  1. Assim que você ,uma requisição fetch é feita para uma API para um payload JSON.
  2. Assim que você recebe a resposta, você parseia o JSON, e manda pro React.
  3. O React renderiza os dados, e mostra as informações do filme na tela.

Simples , não? Com React Server Components acontece o seguinte:

  1. Uma requisição é feita para um backend que você é dono e é capaz de fazer um render RSC ( React Server Component)
  2. O componente é renderizado no próprio servidor, e você recebe um build do markup estático, sem ser JSON ou HTML , como abaixo: M1:{“id”:”./src/SearchField.client.js”,”chunks”:[“client5”],”name”:””}
  3. O frontend (client), renderiza esse markup, como um UI estático ( importante: não um componente React), isso salva processamento adicional do componente no front-end.

Conclusão

Com conteúdo traduzido de:

Links Úteis:

--

--

Senior Software Engineer @ Mintbase (https://rubenmarcus.dev)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store