Renderização no servidor

Todos os pacotes do reelkit funcionam no servidor. Importe e renderize com Next.js, Remix, Angular SSR, Nuxt 3 ou qualquer configuração de SSR.

Como funciona

O controlador do slider é lógica pura, sem nenhum acesso ao DOM na construção. Escutas de gesto, eventos de teclado e animações só entram nos ciclos de vida do lado do cliente.

Durante o SSR, o Reel do React renderiza um contêiner estático com os slides visíveis iniciais (normalmente 3: anterior, atual, próximo) quando recebe size, e um contêiner vazio quando mede a si mesmo. Na hidratação, ele conecta os controladores de gesto, teclado e roda do mouse, deixando tudo interativo.

O Reel do Vue renderiza um contêiner vazio no servidor e desenha os slides depois da hidratação. Com um size, esse contêiner não bate com a primeira renderização no cliente, então no Nuxt ele vai dentro de <ClientOnly> (veja Nuxt 3).

PacoteSeguro no SSRObservações
@reelkit/coreSimLógica pura, sem APIs do navegador na importação ou na construção
@reelkit/reactSimReel e ReelIndicator produzem HTML válido no servidor
@reelkit/angularSimComponentes standalone, compatíveis com Angular SSR (@angular/ssr)
@reelkit/vueSimSeguro para importar e renderizar no servidor; com um size, no Nuxt 3 coloque o Reel dentro de <ClientOnly>
@reelkit/react-reel-playerSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/react-lightboxSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/angular-reel-playerSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/angular-lightboxSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/vue-reel-playerSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/vue-lightboxSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/stories-coreSimIndependente de framework, sem acesso ao DOM
@reelkit/react-stories-playerSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/angular-stories-playerSimNão renderiza nada quando fechado (isOpen=false)
@reelkit/vue-stories-playerSimNão renderiza nada quando fechado (isOpen=false)

Next.js App Router

O Reel usa eventos do navegador e refs, então ele roda como Client Component. Adicione a diretiva "use client" no topo do arquivo que usa Reel:

tsx
'use client';

import { Reel, ReelIndicator } from '@reelkit/react';

export function Feed({ items }: { items: FeedItem[] }) {
  return (
    <Reel
      count={items.length}
      size={[400, 700]}
      direction="vertical"
      enableWheel
      itemBuilder={(index) => (
        <div className="w-full h-full flex items-center justify-center">
          {items[index].title}
        </div>
      )}
    >
      <ReelIndicator />
    </Reel>
  );
}

Você pode buscar os dados em um Server Component e repassá-los:

tsx
// app/feed/page.tsx (Server Component)
import { Feed } from './Feed';

export default async function FeedPage() {
  const items = await fetchFeedItems();

  return <Feed items={items} />;
}

Next.js Pages Router

O Pages Router funciona sem configuração extra. O componente renderiza no SSR e hidrata no cliente:

tsx
// pages/feed.tsx
import { Reel } from '@reelkit/react';
import type { GetServerSideProps } from 'next';

interface Props {
  items: FeedItem[];
}

export const getServerSideProps: GetServerSideProps<Props> = async () => {
  const items = await fetchFeedItems();
  return { props: { items } };
};

export default function FeedPage({ items }: Props) {
  return (
    <Reel
      count={items.length}
      size={[400, 700]}
      itemBuilder={(index) => <Slide data={items[index]} />}
    />
  );
}

Tamanho responsivo com SSR

Simplesmente não passe a prop size. Sem ela, o Reel mede o próprio contêiner no cliente via ResizeObserver. Durante o SSR o slider renderiza um contêiner vazio; na hidratação ele mede e exibe os slides na hora:

tsx
'use client';

import { Reel } from '@reelkit/react';

export function FullScreenFeed({ items }: { items: FeedItem[] }) {
  return (
    <Reel
      count={items.length}
      style={{ width: '100%', height: '100dvh' }}
      itemBuilder={(index) => <Slide data={items[index]} />}
    />
  );
}
Como funciona a medição automática

Quando size não é informado, o contêiner precisa ganhar tamanho pelo CSS (flex/grid do elemento pai, largura/altura explícitas ou porcentagens). O slider não mostra nada até concluir a primeira medição; depois disso preenche as dimensões medidas e acompanha sozinho os redimensionamentos seguintes.

Tamanho explícito (abordagem manual)

Para controle no nível do pixel, passe a prop size explicitamente. Como window.innerWidth não existe durante o SSR, defina um valor padrão e atualize na montagem:

tsx
'use client';

import { useState, useEffect } from 'react';
import { Reel } from '@reelkit/react';

// Default size for SSR — matches common mobile viewport
const DEFAULT_SIZE: [number, number] = [390, 844];

export function FullScreenFeed({ items }: { items: FeedItem[] }) {
  const [size, setSize] = useState<[number, number]>(DEFAULT_SIZE);

  useEffect(() => {
    const update = () =>
      setSize([window.innerWidth, window.innerHeight]);

    update();
    window.addEventListener('resize', update);
    return () => window.removeEventListener('resize', update);
  }, []);

  return (
    <Reel
      count={items.length}
      size={size}
      itemBuilder={(index) => <Slide data={items[index]} />}
    />
  );
}
Dica

Escolha um tamanho padrão próximo da tela mais comum entre seus leitores (comece pelo celular). Se a tela real for diferente, o slider se reajusta na hidratação, sem atraso perceptível.

Componentes de overlay

ReelPlayerOverlay, LightboxOverlay e StoriesOverlay não renderizam nada quando isOpen={false}, então já são seguros no SSR. Eles só montam seu portal ao abrir, o que normalmente parte de uma interação no cliente:

tsx
'use client';

import { useState } from 'react';
import { ReelPlayerOverlay } from '@reelkit/react-reel-player';

export function VideoFeed({ content }: { content: ContentItem[] }) {
  const [isOpen, setIsOpen] = useState(false);
  const [startIndex, setStartIndex] = useState(0);

  return (
    <>
      <div className="grid grid-cols-3 gap-1">
        {content.map((item, i) => (
          <button
            key={i}
            onClick={() => { setStartIndex(i); setIsOpen(true); }}
          >
            <img src={item.thumbnail} alt="" />
          </button>
        ))}
      </div>

      <ReelPlayerOverlay
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        content={content}
        initialIndex={startIndex}
      />
    </>
  );
}

Angular SSR

Todos os componentes Angular são seguros no SSR com @angular/ssr. O controlador do slider é criado em ngOnInit, e o trabalho que precisa do navegador, como medir o contêiner, é pulado no servidor via isPlatformBrowser. Componentes de overlay não renderizam nada quando isOpen=false, então não produzem marcação alguma no servidor.

typescript
import { Component, signal } from '@angular/core';
import {
  RkReelPlayerOverlayComponent,
} from '@reelkit/angular-reel-player';

@Component({
  selector: 'app-feed',
  standalone: true,
  imports: [RkReelPlayerOverlayComponent],
  template: `
    <rk-reel-player-overlay
      [isOpen]="isOpen()"
      [content]="content"
      (closed)="isOpen.set(false)"
    />
  `,
})
export class FeedComponent {
  isOpen = signal(false);
  content = [/* ... */];
}

Nuxt 3

Os componentes Vue do ReelKit são importados e renderizados no servidor sem erros. O Reel envia do servidor um contêiner vazio e desenha os slides depois da hidratação. Quando o Reel mede a si mesmo, esse contêiner hidrata sem divergência a partir do @reelkit/vue 0.9.1; nas versões anteriores ele ficava sem altura e nenhum slide aparecia. Com um size explícito, a primeira renderização no cliente já traz slides, o que gera uma divergência na hidratação. Nesse caso, ou numa versão anterior, envolva o Reel em um <ClientOnly> ou use o sufixo .client.vue:

vue
<!-- pages/feed.vue -->
<script setup lang="ts">
const items = await useFetch('/api/feed');
</script>

<template>
  <ClientOnly>
    <Feed :items="items.data.value" />
  </ClientOnly>
</template>

O componente Feed usa o Reel normalmente:

vue
<!-- components/Feed.vue -->
<script setup lang="ts">
import { Reel, ReelIndicator } from '@reelkit/vue';

defineProps<{ items: FeedItem[] }>();
</script>

<template>
  <Reel :count="items.length" direction="vertical" enable-wheel>
    <template #item="{ index, size }">
      <div :style="{ width: size[0] + 'px', height: size[1] + 'px' }">
        {{ items[index].title }}
      </div>
    </template>
    <ReelIndicator />
  </Reel>
</template>

Tamanho responsivo com SSR

Não passe a prop size e deixe a medição automática agir. O Reel ocupa 100% do elemento pai e, a partir do @reelkit/vue 0.9.1, dispensa o <ClientOnly>: o servidor envia um contêiner vazio no tamanho total, e o cliente o mede e exibe os slides. O exemplo mantém o wrapper, que também funciona nas versões anteriores:

vue
<template>
  <ClientOnly>
    <div style="width: 100%; height: 100dvh">
      <Reel :count="items.length">
        <template #item="{ index, size }">
          <Slide :data="items[index]" :size="size" />
        </template>
      </Reel>
    </div>
  </ClientOnly>
</template>

Componentes de overlay

ReelPlayerOverlay, LightboxOverlay e StoriesOverlay não renderizam nada quando isOpen é falso, então não precisam de <ClientOnly> e não produzem marcação alguma no servidor. Eles só teleportam o conteúdo para o body depois de abertos no cliente.

Usando o core diretamente

Ao usar @reelkit/core direto para integrar um framework próprio, você pode criar o controlador no servidor. Chame attach() e observe() apenas no cliente:

typescript
import { createSliderController } from '@reelkit/core';

// Safe to call on the server — no DOM access
const controller = createSliderController({
  count: 10,
  direction: 'vertical',
});

// Only call on the client — attaches DOM event listeners
if (typeof window !== 'undefined') {
  controller.attach(element);
  controller.observe();
}

Resumo

O que já funciona sem ajustes
  • Importar qualquer pacote do reelkit no servidor
  • Renderizar os componentes de slider durante o SSR (gera HTML estático válido)
  • Criar controladores no servidor
  • Componentes de overlay com isOpen=false
O que exige atenção
  • Omita size para medir automaticamente, ou defina um padrão ao usar dimensões baseadas na janela
  • No Nuxt, renderize o Reel do Vue dentro de <ClientOnly> quando ele tiver um size ou no @reelkit/vue anterior a 0.9.1
  • Não chame attach()/observe() no servidor ao usar o core diretamente