Renderizado en el servidor

Todos los paquetes de reelkit funcionan en el servidor. Impórtalos y renderízalos con Next.js, Remix, Angular Universal o cualquier configuración de SSR.

Cómo funciona

El controlador del slider del core es lógica pura y no accede al DOM al crearse. Los listeners de gestos, los eventos de teclado y las animaciones se conectan solo en los hooks del ciclo de vida del cliente.

Durante el SSR, el componente Reel renderiza un contenedor estático con los slides visibles iniciales (normalmente 3: anterior, actual y siguiente). Al hidratarse, conecta los controladores de gestos, teclado y rueda para que todo sea interactivo.

PaqueteSeguro en SSRNotas
@reelkit/coreLógica pura, sin APIs del navegador al importar ni al crear
@reelkit/reactReel y ReelIndicator renderizan HTML válido en el servidor
@reelkit/angularComponentes standalone, compatibles con SSR en Angular Universal
@reelkit/vueComponentes y composables, compatibles con SSR en Nuxt 3
@reelkit/react-reel-playerNo renderiza nada mientras está cerrado (isOpen=false)
@reelkit/react-lightboxNo renderiza nada mientras está cerrado (isOpen=false)
@reelkit/angular-reel-playerNo renderiza nada mientras está cerrado (isOpen=false)
@reelkit/angular-lightboxNo renderiza nada mientras está cerrado (isOpen=false)
@reelkit/stories-coreIndependiente del framework, sin acceso al DOM
@reelkit/react-stories-playerNo renderiza nada mientras está cerrado (isOpen=false)

Next.js App Router

Reel usa eventos y refs del navegador, así que se ejecuta como Client Component. Añade la directiva "use client" al principio del archivo 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>
  );
}

Puedes cargar los datos en un Server Component y pasarlos hacia abajo:

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

Pages Router funciona sin configuración adicional. El componente se renderiza durante el SSR y se hidrata en el 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]} />}
    />
  );
}

Tamaño adaptable con SSR

Omite por completo la prop size. Sin size, Reel mide su contenedor en el cliente con ResizeObserver. Durante el SSR el slider renderiza un contenedor vacío; al hidratarse lo mide y renderiza los slides al momento:

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]} />}
    />
  );
}
Cómo funciona el tamaño automático

Sin size, el tamaño del contenedor lo tiene que dar el CSS (un padre flex o grid, un ancho y alto explícitos o porcentajes). El slider no renderiza nada hasta que termina la primera medición; después ocupa las dimensiones medidas y se adapta solo a los cambios de tamaño posteriores.

Tamaño explícito (manual)

Para controlar el tamaño al píxel, pasa una prop size explícita. Como window.innerWidth no existe durante el SSR, da un valor por defecto y actualízalo al montar:

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]} />}
    />
  );
}
Consejo

Elige un tamaño por defecto que coincida con el viewport más habitual (por ejemplo, pensando primero en el móvil). Si el viewport real es distinto, el slider se reajusta al instante al hidratarse.

Componentes de overlay

ReelPlayerOverlay y LightboxOverlay no renderizan nada con isOpen={false}, así que son seguros en SSR por defecto. Solo montan su portal al abrirse, algo que normalmente ocurre tras una interacción del usuario en el 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 Universal / SSR

Todos los componentes de Angular son seguros en SSR. El controlador del slider aplaza el acceso a las APIs del navegador hasta afterRenderEffect. Los componentes overlay no renderizan nada con isOpen=false, así que no generan marcado durante el renderizado en el 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

Los componentes de ReelKit para Vue funcionan con Nuxt 3 sin configuración. Como Reel usa APIs del navegador (eventos táctiles, ResizeObserver), envuélvelo en un componente <ClientOnly> o usa el sufijo .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>

El componente Feed usa Reel con normalidad:

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>

Tamaño adaptable con SSR

Omite la prop size para que el tamaño se mida solo. Reel ocupa el 100 % de su padre. Durante el SSR renderiza un contenedor vacío; al hidratarse lo mide y renderiza los slides:

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>

Usar el core directamente

Si usas @reelkit/core directamente para integrarlo con otro framework, puedes crear el controlador en el servidor. Llama a attach() y observe() en el 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();
}

Resumen

Lo que funciona sin configuración
  • Importar cualquier paquete de reelkit en el servidor
  • Renderizar los componentes del slider durante el SSR (genera HTML estático válido)
  • Crear controladores en el servidor
  • Los componentes overlay con isOpen=false
Qué tener en cuenta
  • Omite size para medir el tamaño de forma automática, o da un valor por defecto si usas dimensiones basadas en el viewport
  • No llames a attach()/observe() en el servidor cuando uses el core directamente