Pular para o conteúdo
// visualizar em
salvo em todas as páginas

Server Components e SSR

O Camaleo renderiza no servidor com o estilo já aplicado. Sem script de tema, sem piscar, e a maior parte das peças de layout continua Server Component.

// você vai aprender

  • Quais componentes são Server Components e quais abrem uma fronteira "use client"
  • Como definir estilo, modo e marca no <html> a partir do layout raiz
  • Como guardar a escolha de claro ou escuro sem a página piscar
  • Como os textos embutidos chegam aos Server Components pelo Message

Componentes de servidor e de cliente

Cada componente fica no próprio arquivo em packages/react/src, e o build mantém esses arquivos separados, então um "use client" nunca vaza para um arquivo que não precisa dele. Você pode importar tudo de @camaleo/react num Server Component: o React transforma cada componente de cliente numa fronteira sozinho.

Estes arquivos não têm "use client". Os componentes deles renderizam só no servidor e não mandam JavaScript:

ArquivoComponentes
alert.tsxAlert, AlertTitle, AlertDescription
app-shell.tsxAppShell, AppShellHeader, AppShellBody, AppShellContent, AppShellFooter, SkipLink
data-display.tsxStat e partes, DataList e partes, Timeline e partes, EmptyState e partes, ProgressCircle
display.tsxBadge, Kbd, Card e partes, Table e partes, Skeleton
layout.tsxBox, Stack, Grid, Container, AspectRatio
spinner.tsxSpinner
typography.tsxHeading, Text
visually-hidden.tsxVisuallyHidden
style-scope.tsstyleScopeProps() (uma função comum)

Todo o resto é componente de cliente: toda parte interativa (feita sobre Base UI ou React Aria), mais StyleProvider, MessagesProvider e DirectionProvider, que guardam contexto do React. Componentes de cliente também viram HTML no servidor; eles só hidratam no navegador.

DICA

O Table continua Server Component mesmo rolando: só o pequeno contêiner de rolagem (table-scroll.tsx) é de cliente, e ele só vira uma região focável quando a tabela transborda.

Defina o estilo no <html>

No layout raiz, espalhe styleScopeProps() no <html>. É uma função comum, segura num Server Component, que devolve data-style, data-mode e a escala da marca como custom properties inline. O estilo está no primeiro byte do HTML, então nada espera o JavaScript.

app/layout.tsx
import "@camaleo/styles/base.css";
import "@camaleo/styles/glass.css";
import { styleScopeProps } from "@camaleo/react/style-scope";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="pt-BR" {...styleScopeProps({ styleName: "glass", brand: "#3fa34d" })}>
      <body>{children}</body>
    </html>
  );
}

Importe o base.css uma vez e o CSS de cada estilo que você renderiza. Diálogos, menus e toasts vão por portal para o <body>, então herdam o estilo do <html> sem trabalho extra.

Guarde o modo da pessoa sem piscar

As cores do Camaleo usam light-dark(), e o data-mode só define o color-scheme. Sem mode, a página segue o sistema com CSS puro: não há script rodando antes da pintura, então não há o que piscar.

Quando a pessoa escolhe claro ou escuro, guarde a escolha num cookie e leia no layout. O servidor já manda o data-mode certo na primeira resposta.

app/layout.tsx
import { cookies } from "next/headers";
import { styleScopeProps } from "@camaleo/react/style-scope";

const MODES = ["light", "dark", "system"] as const;

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const saved = (await cookies()).get("cml-mode")?.value;
  // Só aceite valores conhecidos: o cookie vem do navegador.
  const mode = MODES.find((m) => m === saved) ?? "system";
  return (
    <html lang="pt-BR" {...styleScopeProps({ styleName: "glass", mode })}>
      <body>{children}</body>
    </html>
  );
}

Grave o cookie numa Server Function. Usada como action de um formulário, ela devolve a página renderizada de novo na mesma ida ao servidor:

app/actions.ts
"use server";
import { cookies } from "next/headers";

export async function setMode(form: FormData) {
  const mode = form.get("mode");
  if (mode !== "light" && mode !== "dark" && mode !== "system") return;
  (await cookies()).set("cml-mode", mode, { maxAge: 60 * 60 * 24 * 365 });
}
app/mode-switch.tsx
import { Button } from "@camaleo/react";
import { setMode } from "./actions";

export function ModeSwitch() {
  return (
    <form action={setMode}>
      <Button type="submit" name="mode" value="dark" variant="outline">
        Escuro
      </Button>
    </form>
  );
}
CUIDADO

Nesta versão do Next.js, cookies() é assíncrono, e ler cookies deixa a rota dinâmica. Se a página precisa ser estática, deixe o mode de fora e o sistema decide.

Crie ilhas com StyleProvider

O StyleProvider é componente de cliente, mas renderiza o seu <div data-style> no servidor como qualquer outro. Uma seção em outro estilo já está no HTML desde o início:

app/page.tsx
import { Button, StyleProvider } from "@camaleo/react";

export default function Page() {
  return (
    <StyleProvider styleName="neubrutalism" mode="light">
      <Button>Começar</Button>
    </StyleProvider>
  );
}

Popups abertos dentro da ilha mantêm o estilo dela: o DialogContent e as outras partes em portal leem o escopo do provider pelo contexto e o aplicam de novo.

Defina o idioma no servidor

O MessagesProvider aceita um pacote embutido pelo nome, que é um texto simples e atravessa a fronteira servidor–cliente sem problema:

app/layout.tsx
import { MessagesProvider } from "@camaleo/react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="pt-BR">
      <body>
        <MessagesProvider locale="pt-BR">{children}</MessagesProvider>
      </body>
    </html>
  );
}

Trocas feitas só de textos também funcionam aqui. Algumas mensagens são funções (pagination.page, carousel.goToSlide…), e funções não passam de um Server Component para um de cliente, então troque essas num componente de cliente. O guia de Idiomas e RTL traz todas as chaves.

O auxiliar Message

Server Components não leem contexto do React, então Spinner, SkipLink e ProgressCircle não podem chamar useMessages(). O rótulo padrão deles é <Message pick="…" />: um componente de cliente minúsculo que imprime uma mensagem do MessagesProvider mais próximo. O resto de cada componente fica no servidor.

Você pode usá-lo nos seus Server Components para as mesmas três chaves:

app/loading.tsx
import { Message } from "@camaleo/react";

// Um "Carregando…" visível, no idioma do MessagesProvider mais próximo.
export default function Loading() {
  return (
    <p role="status">
      <Message pick="spinner.label" />…
    </p>
  );
}

O pick aceita "skipLink.label", "spinner.label" e "progressCircle.label". Para qualquer outro texto, use useMessages() num componente de cliente.

Antes de publicar

  • No código-fonte da página, o <html> (ou o <div> do provider) já tem data-style
  • Todo estilo renderizado tem o CSS importado no layout raiz
  • O modo lido do cookie é conferido contra os valores conhecidos
  • Nenhum layout ganhou "use client" só por causa do Camaleo
  • O MessagesProvider num Server Component recebe locale ou messages só com textos
Esta página foi útil? · Editar esta página no GitHub