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.
| Paquete | Seguro en SSR | Notas |
|---|---|---|
@reelkit/core | Sí | Lógica pura, sin APIs del navegador al importar ni al crear |
@reelkit/react | Sí | Reel y ReelIndicator renderizan HTML válido en el servidor |
@reelkit/angular | Sí | Componentes standalone, compatibles con SSR en Angular Universal |
@reelkit/vue | Sí | Componentes y composables, compatibles con SSR en Nuxt 3 |
@reelkit/react-reel-player | Sí | No renderiza nada mientras está cerrado (isOpen=false) |
@reelkit/react-lightbox | Sí | No renderiza nada mientras está cerrado (isOpen=false) |
@reelkit/angular-reel-player | Sí | No renderiza nada mientras está cerrado (isOpen=false) |
@reelkit/angular-lightbox | Sí | No renderiza nada mientras está cerrado (isOpen=false) |
@reelkit/stories-core | Sí | Independiente del framework, sin acceso al DOM |
@reelkit/react-stories-player | Sí | No 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:
'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:
// 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:
// 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:
'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:
'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:
'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.
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:
<!-- 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:
<!-- 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:
<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:
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
sizepara 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