Componentes de Servidor
Componentes de Servidor são um novo tipo de Componente que renderiza com antecedência, antes do bundling, em um ambiente separado do seu aplicativo cliente ou servidor SSR.
Este ambiente separado é o “servidor” em Componentes de Servidor React. Componentes de Servidor podem rodar uma vez no tempo de build em seu servidor CI, ou eles podem ser executados para cada requisição usando um servidor web.
- Componentes de Servidor sem um Servidor
- Componentes de Servidor com um Servidor
- Adicionando interatividade aos Componentes de Servidor
- Componentes assíncronos com Componentes de Servidor
Componentes de Servidor sem um Servidor
Componentes de Servidor podem executar em tempo de build para ler do sistema de arquivos ou buscar conteúdo estático, então um servidor web não é necessário. Por exemplo, você pode querer ler dados estáticos de um sistema de gerenciamento de conteúdo.
Sem Componentes de Servidor, é comum buscar dados estáticos no cliente com um Effect:
// bundle.js
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
function Page({page}) {
const [content, setContent] = useState('');
// NOTE: loads *after* first page render.
useEffect(() => {
fetch(`/api/content/${page}`).then((data) => {
setContent(data.content);
});
}, [page]);
return <div>{sanitizeHtml(marked(content))}</div>;
}
// api.js
app.get(`/api/content/:page`, async (req, res) => {
const page = req.params.page;
const content = await file.readFile(`${page}.md`);
res.send({content});
});
Este padrão significa que os usuários precisam baixar e analisar 75KB (gzipped) de bibliotecas adicionais, e esperar por uma segunda requisição para buscar os dados após a página carregar, apenas para renderizar conteúdo estático que não irá mudar durante o tempo de vida da página.
Com Componentes de Servidor, você pode renderizar esses componentes uma vez em tempo de build:
import marked from 'marked'; // Not included in bundle
import sanitizeHtml from 'sanitize-html'; // Not included in bundle
async function Page({page}) {
// NOTE: loads *during* render, when the app is built.
const content = await file.readFile(`${page}.md`);
return <div>{sanitizeHtml(marked(content))}</div>;
}
A saída renderizada pode então ser renderizada no lado do servidor (SSR) para HTML e carregada em uma CDN. Quando o app carrega, o cliente não verá o componente Page
original, ou as bibliotecas caras para renderizar o markdown. O cliente irá apenas ver a saída renderizada:
<div><!-- html for markdown --></div>
Isso significa que o conteúdo é visível durante o primeiro carregamento da página, e o bundle não inclui as bibliotecas custosas necessárias para renderizar o conteúdo estático.
Componentes de Servidor com um Servidor
Componentes de Servidor podem também executar em um servidor web durante uma requisição para uma página, permitindo que você acesse sua camada de dados sem ter que construir uma API. Eles são renderizados antes que sua aplicação seja empacotada e podem passar dados e JSX como props para Componentes Cliente.
Sem Componentes de Servidor, é comum buscar dados dinâmicos no cliente em um Effect:
// bundle.js
function Note({id}) {
const [note, setNote] = useState('');
// NOTE: loads *after* first render.
useEffect(() => {
fetch(`/api/notes/${id}`).then(data => {
setNote(data.note);
});
}, [id]);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
function Author({id}) {
const [author, setAuthor] = useState('');
// NOTE: loads *after* Note renders.
// Causing an expensive client-server waterfall.
useEffect(() => {
fetch(`/api/authors/${id}`).then(data => {
setAuthor(data.author);
});
}, [id]);
return <span>By: {author.name}</span>;
}
// api
import db from './database';
app.get(`/api/notes/:id`, async (req, res) => {
const note = await db.notes.get(id);
res.send({note});
});
app.get(`/api/authors/:id`, async (req, res) => {
const author = await db.authors.get(id);
res.send({author});
});
Com Componentes de Servidor, você pode ler os dados e renderizá-los no componente:
import db from './database';
async function Note({id}) {
// NOTE: loads *during* render.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
async function Author({id}) {
// NOTE: loads *after* Note,
// but is fast if data is co-located.
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
O bundler então combina os dados, Componentes de Servidor renderizados e Componentes Cliente dinâmicos em um bundle. Opcionalmente, esse bundle pode então ser renderizado no lado do servidor (SSR) para criar o HTML inicial para a página. Quando a página carrega, o navegador não vê os componentes Note
e Author
originais; apenas a saída renderizada é enviada para o cliente:
<div>
<span>By: The React Team</span>
<p>React 19 is...</p>
</div>
Componentes de Servidor podem ser feitos dinâmicos re-buscando-os de um servidor, onde eles podem acessar os dados e renderizar novamente. Essa nova arquitetura de aplicação combina o modelo mental simples de “requisição/resposta” de Apps Multi-Página server-centric com a interatividade contínua de Apps Single-Page client-centric, dando a você o melhor dos dois mundos.
Adicionando interatividade aos Componentes de Servidor
Componentes de Servidor não são enviados para o navegador, então eles não podem usar APIs interativas como useState
. Para adicionar interatividade aos Componentes de Servidor, você pode compô-los com Componentes Cliente usando a diretiva "use client"
.
No exemplo a seguir, o Componente de Servidor Notes
importa um Componente Cliente Expandable
que usa state para alternar seu estado expanded
:
// Componente de Servidor
import Expandable from './Expandable';
async function Notes() {
const notes = await db.notes.getAll();
return (
<div>
{notes.map(note => (
<Expandable key={note.id}>
<p note={note} />
</Expandable>
))}
</div>
)
}
// Componente Cliente
"use client"
export default function Expandable({children}) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<button
onClick={() => setExpanded(!expanded)}
>
Alternar
</button>
{expanded && children}
</div>
)
}
Isso funciona renderizando primeiro Notes
como um Componente de Servidor, e então instruindo o bundler a criar um bundle para o Componente Cliente Expandable
. No navegador, os Componentes Cliente verão a saída dos Componentes de Servidor passados como props:
<head>
<!-- the bundle for Client Components -->
<script src="bundle.js" />
</head>
<body>
<div>
<Expandable key={1}>
<p>this is the first note</p>
</Expandable>
<Expandable key={2}>
<p>this is the second note</p>
</Expandable>
<!--...-->
</div>
</body>
Componentes assíncronos com Componentes de Servidor
Componentes de Servidor introduzem uma nova forma de escrever Componentes usando async/await. Quando você await
em um componente assíncrono, React irá suspender e esperar pela promise ser resolvida antes de retomar a renderização. Isso funciona através de limites de servidor/cliente com suporte a streaming para Suspense.
Você pode até mesmo criar uma promise no servidor, e esperar por ela no cliente:
// Componente de Servidor
import db from './database';
async function Page({id}) {
// Irá suspender o Componente de Servidor.
const note = await db.notes.get(id);
// NOTE: not awaited, will start here and await on the client.
const commentsPromise = db.comments.get(note.id);
return (
<div>
{note}
<Suspense fallback={<p>Loading Comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</div>
);
}
// Componente Cliente
"use client";
import {use} from 'react';
function Comments({commentsPromise}) {
// NOTE: this will resume the promise from the server.
// It will suspend until the data is available.
const comments = use(commentsPromise);
return comments.map(commment => <p>{comment}</p>);
}
O conteúdo de note
é dados importantes para a página renderizar, então nós fazemos await
nele no servidor. Os comentários estão abaixo da dobra e de baixa prioridade, então nós iniciamos a promise no servidor, e esperamos por ela no cliente com a API use
. Isso irá Suspender no cliente, sem bloquear o conteúdo de note
de renderizar.
Já que componentes assíncronos não são suportados no cliente, nós fazemos await na promise com use
.